diff options
106 files changed, 4175 insertions, 1673 deletions
diff --git a/Android.mk b/Android.mk index c838600c0a71..270ee2e6b2ff 100644 --- a/Android.mk +++ b/Android.mk @@ -110,6 +110,7 @@ LOCAL_SRC_FILES += \ 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 a621508c22c3..6550007ccb40 100644 --- a/api/current.txt +++ b/api/current.txt @@ -179,9 +179,9 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android { 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 @@ package android.database.sqlite { 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 @@ package android.net { 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 f3f0432614eb..e81f799fda28 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 @@ public final class Backup { 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 final class Backup { 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 @@ public final class Backup { } else if (arg.equals("restore")) { doFullRestore(); } else { - System.err.println("ERROR: invalid operation '" + arg + "'"); + Log.e(TAG, "Invalid operation '" + arg + "'"); } } @@ -80,7 +81,6 @@ public final class Backup { } 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 @@ public final class Backup { } 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 @@ public final class Backup { 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 15a199f30c46..67dd9f8679ea 100644 --- a/cmds/keystore/Android.mk +++ b/cmds/keystore/Android.mk @@ -19,14 +19,14 @@ ifneq ($(TARGET_SIMULATOR),true) 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 e34053be2c51..000000000000 --- 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 000000000000..31db9fdc1c8f --- /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 5ef51e9cd7d0..5ae3d24acee0 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 index e8afb5a945b2..dcd3bcb8fc01 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 @@ int main(int argc, char **argv) } 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 ef2e54a925d8..a6658cc461f6 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -138,6 +138,25 @@ public class ActivityManager { } } + /** @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 2a0d79866db3..85f40c92963a 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1483,6 +1483,26 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM 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 @@ class ActivityManagerProxy implements IActivityManager 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 @@ class ActivityManagerProxy implements IActivityManager 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 85e59b308448..955cef2014f2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1980,7 +1980,8 @@ public final class ActivityThread { 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 @@ public final class ActivityThread { // 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 @@ public final class ActivityThread { 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 1f53c0e36070..e2588cfb5eb1 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -347,6 +347,9 @@ public interface IActivityManager extends IInterface { 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 @@ public interface IActivityManager extends IInterface { 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 8c3155963d6e..05a68a8af31f 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -68,6 +68,7 @@ public interface IApplicationThread extends IInterface { 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 52fc623b3649..8af78fadfc44 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -79,4 +79,23 @@ oneway interface IBackupAgent { */ 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 dc60e2438629..17f8adbb0ccf 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -179,10 +179,18 @@ public abstract class BackupAgent extends ContextWrapper { 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 @@ public abstract class BackupAgent extends ContextWrapper { 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 @@ public abstract class BackupAgent extends ContextWrapper { } } + @Override public void doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder) throws RemoteException { @@ -261,5 +271,25 @@ public abstract class BackupAgent extends ContextWrapper { } } } + + @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 9850566ae969..dfb0dd7f5f04 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 @@ package android.app.backup; * @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"; - - public static String FULL_BACKUP_INTENT_ACTION = "fullback"; - public static String FULL_RESTORE_INTENT_ACTION = "fullrest"; - public static String CONF_TOKEN_INTENT_EXTRA = "conftoken"; + static final String TAG = "FullBackup"; + + 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 f0a1f2a4978b..4dca5936dbc3 100644 --- a/core/java/android/app/backup/FullBackupAgent.java +++ b/core/java/android/app/backup/FullBackupAgent.java @@ -28,6 +28,9 @@ import libcore.io.OsConstants; 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 @@ public class FullBackupAgent extends BackupAgent { 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 @@ public class FullBackupAgent extends BackupAgent { } } + /** + * Dummy -- We're never used for restore of an incremental dataset + */ + @Override + public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) + throws IOException { + } + + /** + * Restore the described file from the given pipe. + */ @Override - public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) { + 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 94e31a8d18a5..bac874e56b7a 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -147,6 +147,14 @@ interface IBackupManager { 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 854d41092192..dca53a8e2d49 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -113,8 +113,13 @@ public class CompatibilityInfo implements Parcelable { 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 @@ public class CompatibilityInfo implements Parcelable { } } 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 @@ public class CompatibilityInfo implements Parcelable { 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 b6aca2b29795..9c09e811e876 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -24,8 +24,8 @@ import android.util.Log; 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 @@ public class SQLiteQueryBuilder 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 @@ public class SQLiteQueryBuilder } /** + * 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 @@ public class SQLiteQueryBuilder } } - 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 @@ public class SQLiteQueryBuilder 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 @@ public class SQLiteQueryBuilder } 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 @@ public class SQLiteQueryBuilder 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 419288b8fe82..c72c4b0fad5e 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -22,7 +22,6 @@ import android.os.Binder; 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 @@ import java.net.UnknownHostException; * 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 @@ public class ConnectivityManager * 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 @@ public class ConnectivityManager * <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 class ConnectivityManager 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 @@ public class ConnectivityManager } } + /** {@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 @@ public class ConnectivityManager } } - /** @hide */ + /** {@hide} */ public LinkProperties getActiveLinkProperties() { try { return mService.getActiveLinkProperties(); @@ -309,7 +317,7 @@ public class ConnectivityManager } } - /** @hide */ + /** {@hide} */ public LinkProperties getLinkProperties(int networkType) { try { return mService.getLinkProperties(networkType); @@ -479,19 +487,11 @@ public class ConnectivityManager } /** - * 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 8be492c8e077..647a60a97d73 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -33,13 +33,11 @@ interface IConnectivityManager 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 000000000000..92301510cc31 --- /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 d9351ee9d3a4..c1f353045706 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 @@ interface INetworkPolicyManager { 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 5f5e11c96a2b..537750a79afb 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -74,7 +74,9 @@ public class NetworkInfo implements Parcelable { /** IP traffic not available. */ DISCONNECTED, /** Attempt to connect failed. */ - FAILED + FAILED, + /** Access to this network is blocked. */ + BLOCKED } /** @@ -96,6 +98,7 @@ public class NetworkInfo implements Parcelable { 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 @@ public class NetworkInfo implements Parcelable { 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 1913aa738d89..dd7c1b0fab9c 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -19,6 +19,8 @@ package android.net; import android.content.Context; import android.os.RemoteException; +import java.io.PrintWriter; + /** * Manager for creating and modifying network policy rules. * @@ -28,12 +30,13 @@ public class NetworkPolicyManager { /** 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 @@ public class NetworkPolicyManager { /** * 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 @@ public class NetworkPolicyManager { 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 017e5e367c69..c9db697f9011 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -982,10 +982,21 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit */ 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 class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit 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 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } 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 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit 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 1004b5f09170..57cda97f8235 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -199,6 +199,9 @@ public class HTML5VideoFullScreen extends HTML5VideoView 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 25921bc86a52..ef1906c1ea2c 100644 --- a/core/java/android/webkit/HTML5VideoInline.java +++ b/core/java/android/webkit/HTML5VideoInline.java @@ -12,10 +12,15 @@ import android.opengl.GLES20; */ 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 @@ public class HTML5VideoInline extends HTML5VideoView{ 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 @@ public class HTML5VideoInline extends HTML5VideoView{ // 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 c05498a0fb49..5983a4444e96 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -287,7 +287,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { return false; } - public SurfaceTexture getSurfaceTexture() { + public SurfaceTexture getSurfaceTexture(int videoLayerId) { return null; } @@ -315,4 +315,14 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { // 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 7d8669bffe7f..d0237b5eddeb 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -106,12 +106,14 @@ class HTML5VideoViewProxy extends Handler 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 @@ class HTML5VideoViewProxy extends Handler 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 e41dd1c2c39b..0573d889685c 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -906,7 +906,7 @@ class ZoomManager { // 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 @@ class ZoomManager { // 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 @@ class ZoomManager { // 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 9933d6872cd1..586ece841fc5 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -93,6 +93,7 @@ public class SearchView extends LinearLayout { private boolean mClearingFocus; private int mMaxWidth; private boolean mVoiceButtonEnabled; + private CharSequence mUserQuery; private SearchableInfo mSearchable; private Bundle mAppSearchData; @@ -372,6 +373,7 @@ public class SearchView extends LinearLayout { 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 @@ public class SearchView extends LinearLayout { private void onTextChanged(CharSequence newText) { CharSequence text = mQueryTextView.getText(); + mUserQuery = text; boolean hasText = !TextUtils.isEmpty(text); if (isSubmitButtonEnabled()) { updateSubmitButton(hasText); @@ -1124,7 +1127,7 @@ public class SearchView extends LinearLayout { 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 18c3b24e9062..5886c64ecbb7 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8787,10 +8787,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 beacf75b9895..479788d39d0a 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 @@ package com.android.internal.view.menu; 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 @@ public class ActionMenuItemView extends LinearLayout // 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 @@ public class ActionMenuItemView extends LinearLayout mImageButton.setVisibility(GONE); } - mTextButton.setVisibility(icon == null || mItemData.showsTextAsAction() ? VISIBLE : GONE); + updateTextButtonVisibility(); } - + public boolean hasText() { return mTextButton.getVisibility() != GONE; } @@ -128,10 +135,9 @@ public class ActionMenuItemView extends LinearLayout // 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 c82323eedb13..deed1c5e76d5 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,27 +130,24 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi 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); - } - } else { - mTitleView.setText(mTitle); - mSubtitleView.setText(mSubtitle); - mSubtitleView.setVisibility(mSubtitle != null ? VISIBLE : GONE); - if (mTitleLayout.getParent() == null) { - addView(mTitleLayout); + if (mSubtitleStyleRes != 0) { + mSubtitleView.setTextAppearance(mContext, mSubtitleStyleRes); } } + + 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); + } } public void initForMode(final ActionMode mode) { @@ -228,6 +225,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi mAnimateInOnLayout = false; } + @Override public boolean showOverflowMenu() { if (mMenuPresenter != null) { return mMenuPresenter.showOverflowMenu(); @@ -235,6 +233,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi return false; } + @Override public boolean hideOverflowMenu() { if (mMenuPresenter != null) { return mMenuPresenter.hideOverflowMenu(); @@ -242,6 +241,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi 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 0dc0422967af..b3666cbaad6d 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.ServiceManager; 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.List; 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 @@ public class LockPatternUtils { 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 @@ public class LockPatternUtils { // 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 @@ public class LockPatternUtils { * @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 e539cd20e232..e2832ed774f2 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 android_view_GLES20Canvas_setupShadow(JNIEnv* env, jobject clazz, 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 @@ static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, #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 renderText(OpenGLRenderer* renderer, const jchar* text, int count, 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 @@ static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, #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 Binary files differnew file mode 100644 index 000000000000..5579443710b2 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_menu_selectall_holo_dark.png 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 Binary files differnew file mode 100644 index 000000000000..667491475d2d --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_menu_selectall_holo_light.png 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 Binary files differnew file mode 100644 index 000000000000..caec299c39f6 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_menu_selectall_holo_dark.png 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 Binary files differnew file mode 100644 index 000000000000..434f5d1bcc2d --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_menu_selectall_holo_light.png 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 000000000000..a8d39cfec1ff --- /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 e325b8d770e8..db7621158a59 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 41a566c61901..4c3cfc117ea6 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 816546bfd8f2..b8a44436c64e 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 0a614b2dd0b9..4f39da429bd2 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 000000000000..4a1d18cb5c05 --- /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 000000000000..96db035d3e44 --- /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 index 33e9dd7fabc6..33e9dd7fabc6 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java +++ b/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 index 7f13791a1ca6..abd7d9af224a 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java @@ -120,6 +120,9 @@ public class BluetoothStressTest extends InstrumentationTestCase { */ 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 class BluetoothStressTest extends InstrumentationTestCase { */ 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 index 64d2c1257ea0..64d2c1257ea0 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java +++ b/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 index f1dd8fef5ead..f1dd8fef5ead 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 6dc5c2168b7e..6084dd2c493a 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 c5aa52b07059..000000000000 --- 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 baab2e89bd3e..605680a1a7ee 100644 --- a/include/media/AudioRecord.h +++ b/include/media/AudioRecord.h @@ -130,7 +130,7 @@ public: * 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: 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 @@ private: 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 de928da0ce6a..df30e8c9f0d7 100644 --- a/include/media/AudioTrack.h +++ b/include/media/AudioTrack.h @@ -69,8 +69,8 @@ public: 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 @@ public: * 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: 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 d8fdc27efcfb..4037c464a072 100644 --- a/include/media/IAudioFlinger.h +++ b/include/media/IAudioFlinger.h @@ -48,8 +48,8 @@ public: 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 @@ public: 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 @@ public: */ 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 1827c3ea2ba9..072329d3f95a 100644 --- a/include/private/media/AudioTrackShared.h +++ b/include/private/media/AudioTrackShared.h @@ -82,7 +82,7 @@ struct audio_track_cblk_t // 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 @@ struct audio_track_cblk_t 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 7183688bd356..9058cae3b0da 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -32,6 +32,8 @@ import java.util.ArrayList; * 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 class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 @@ public class KeyStore { 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 6630a4f1c148..4582aa0ccfe8 100755 --- a/keystore/tests/src/android/security/KeyStoreTest.java +++ b/keystore/tests/src/android/security/KeyStoreTest.java @@ -20,6 +20,9 @@ import android.app.Activity; 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 @@ import android.test.suitebuilder.annotation.MediumTest; @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 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { @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 @@ public class KeyStoreTest extends ActivityUnitTestCase<Activity> { 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 class KeyStoreTest extends ActivityUnitTestCase<Activity> { } 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 class KeyStoreTest extends ActivityUnitTestCase<Activity> { } 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 f8582d8fff9e..afab26a94fec 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -1151,6 +1151,7 @@ void DisplayListRenderer::drawPoints(float* points, int count, SkPaint* paint) { 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 1ca0a19f14f3..9bf3de81ba93 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -35,6 +35,7 @@ namespace uirenderer { #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 @@ Font::~Font() { } for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { - CachedGlyphInfo* glyph = mCachedGlyphs.valueAt(i); - delete glyph; + delete mCachedGlyphs.valueAt(i); } } @@ -134,48 +134,49 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, } -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 @@ void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t 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 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp 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 @@ void FontRenderer::precacheLatin(SkPaint* paint) { 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 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch } 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 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch 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 @@ bool FontRenderer::renderText(SkPaint* paint, const Rect* clip, const char *text 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 95f714fb003f..24ed6fa4756f 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 @@ public: * 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 @@ protected: 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 @@ protected: 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 @@ protected: uint32_t mScaleX; }; +/////////////////////////////////////////////////////////////////////////////// +// Renderer +/////////////////////////////////////////////////////////////////////////////// + class FontRenderer { public: FontRenderer(); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 6243b015e61b..45f4a427e29f 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -2073,11 +2073,6 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, 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 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float // 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 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float } 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 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float 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 e2dbba0f7828..549d6e9e888b 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -123,8 +123,6 @@ public: 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 2d8b6f3950f6..7c105181d77e 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 d46686d90845..28dba13dec35 100644 --- a/libs/hwui/TextDropShadowCache.h +++ b/libs/hwui/TextDropShadowCache.h @@ -73,7 +73,6 @@ struct ShadowText { 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 e15875f380b7..f933199fe8d1 100644 --- a/libs/utils/BackupHelpers.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -503,6 +503,16 @@ int write_tarfile(const String8& packageName, const String8& domain, 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 446e3dfca8cb..f6c4cc70a3cb 100644 --- a/media/libmedia/AudioRecord.cpp +++ b/media/libmedia/AudioRecord.cpp @@ -88,7 +88,7 @@ AudioRecord::AudioRecord( 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 @@ AudioRecord::AudioRecord( 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 @@ status_t AudioRecord::set( 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 @@ status_t AudioRecord::set( 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 @@ status_t AudioRecord::set( 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 @@ status_t AudioRecord::set( 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 @@ status_t AudioRecord::set( // 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 @@ unsigned int AudioRecord::getInputFramesLost() // 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 @@ status_t AudioRecord::openRecord_l( sp<IAudioRecord> record = audioFlinger->openRecord(getpid(), input, sampleRate, format, - channelCount, + channelMask, frameCount, ((uint16_t)flags) << 16, &mSessionId, @@ -589,7 +589,7 @@ audio_io_handle_t AudioRecord::getInput_l() { mInput = AudioSystem::getInput(mInputSource, mCblk->sampleRate, - mFormat, mChannels, + mFormat, mChannelMask, (audio_in_acoustics_t)mFlags); return mInput; } @@ -756,7 +756,7 @@ status_t AudioRecord::restoreRecord_l(audio_track_cblk_t*& cblk) // 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 7520ed92349e..ea44f87fb647 100644 --- a/media/libmedia/AudioTrack.cpp +++ b/media/libmedia/AudioTrack.cpp @@ -87,7 +87,7 @@ AudioTrack::AudioTrack( int streamType, uint32_t sampleRate, int format, - int channels, + int channelMask, int frameCount, uint32_t flags, callback_t cbf, @@ -96,7 +96,7 @@ AudioTrack::AudioTrack( 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 @@ AudioTrack::AudioTrack( 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 @@ AudioTrack::AudioTrack( 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 @@ status_t AudioTrack::set( int streamType, uint32_t sampleRate, int format, - int channels, + int channelMask, int frameCount, uint32_t flags, callback_t cbf, @@ -180,8 +180,8 @@ status_t AudioTrack::set( 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 @@ status_t AudioTrack::set( 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 @@ status_t AudioTrack::set( // 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 @@ status_t AudioTrack::set( 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() 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::attachAuxEffect(int effectId) 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 @@ status_t AudioTrack::createTrack_l( } } 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 @@ status_t AudioTrack::createTrack_l( streamType, sampleRate, format, - channelCount, + channelMask, frameCount, ((uint16_t)flags) << 16, sharedBuffer, @@ -1164,7 +1165,7 @@ status_t AudioTrack::restoreTrack_l(audio_track_cblk_t*& cblk, bool fromStart) 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 158d2f5190a3..4a1296209ab5 100644 --- a/media/libmedia/IAudioFlinger.cpp +++ b/media/libmedia/IAudioFlinger.cpp @@ -82,8 +82,8 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 d51c946d7011..eae93ff00bf1 100644 --- a/media/libmediaplayerservice/MediaPlayerService.cpp +++ b/media/libmediaplayerservice/MediaPlayerService.cpp @@ -1164,7 +1164,7 @@ sp<IMemory> MediaPlayerService::decode(const char* url, uint32_t *pSampleRate, i 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 4c66a2d2f66e..7eb6d22bc9d3 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends /** 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends 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 @@ public class VideoEditorPerformance extends 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 109cfff8ced2..a4564e6cc72f 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 a1f9a4ab94ca..ca99ae18a1e0 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 4b42067c9525..52bfc2850681 100644 --- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java +++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java @@ -98,6 +98,8 @@ public class BackupRestoreConfirmation extends Activity { 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 45bb2b6355e5..0c4ef7d35463 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 @@ import android.util.Log; */ 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { // 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); - - 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.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); + try { + FileOutputStream filestream = new FileOutputStream(stage); + BufferedOutputStream bufstream = new BufferedOutputStream(filestream); + DataOutputStream out = new DataOutputStream(bufstream); + + out.writeInt(FULL_BACKUP_VERSION); + + 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.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 @@ public class SettingsBackupAgent extends BackupAgentHelper { } 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + @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 @@ public class SettingsBackupAgent extends BackupAgentHelper { } 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { } 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { 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 0e75fbceed4a..3e7d86a38bd1 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 @@ public class SettingsHelper { * "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 @@ public class SettingsHelper { 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 ca56c4fb53c4..57b6b5e9418e 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 com.android.internal.widget.PasswordEntryKeyboardView; 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 @@ public class PasswordUnlockScreen extends LinearLayout implements KeyguardScreen 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 018fe0cddc8b..a6854973bab1 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.Context; 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 @@ class PatternUnlockScreen extends LinearLayoutWithDefaultTouchRecepient 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 053854f7613c..f8066240d6e2 100644 --- a/services/audioflinger/AudioFlinger.cpp +++ b/services/audioflinger/AudioFlinger.cpp @@ -360,8 +360,8 @@ sp<IAudioTrack> AudioFlinger::createTrack( 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 @@ sp<IAudioTrack> AudioFlinger::createTrack( 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 @@ int AudioFlinger::channelCount(int output) const 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 @@ int AudioFlinger::ThreadBase::channelCount() const return (int)mChannelCount; } -int AudioFlinger::ThreadBase::format() const +uint32_t AudioFlinger::ThreadBase::format() const { return mFormat; } @@ -1002,6 +1002,8 @@ status_t AudioFlinger::ThreadBase::dumpBase(int fd, const Vector<String16>& args 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 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> 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 @@ status_t AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16> 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 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra 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 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra 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 @@ sp<AudioFlinger::PlaybackThread::Track> AudioFlinger::PlaybackThread::createTra } 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 @@ void AudioFlinger::PlaybackThread::audioConfigChanged_l(int event, int param) { 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::audioConfigChanged_l(int event, int param) { 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 @@ uint32_t AudioFlinger::MixerThread::prepareTracks_l(const SortedVector< wp<Track 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 @@ void AudioFlinger::DuplicatingThread::addOutputTrack(MixerThread *thread) this, mSampleRate, mFormat, - mChannelCount, + mChannelMask, frameCount); if (outputTrack->cblk() != NULL) { thread->setStreamVolume(AUDIO_STREAM_CNT, 1.0f); @@ -2751,8 +2756,8 @@ AudioFlinger::ThreadBase::TrackBase::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, @@ -2772,6 +2777,7 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( // 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 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( // 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 @@ AudioFlinger::ThreadBase::TrackBase::TrackBase( // 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::sampleRate() const { } 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 @@ void* AudioFlinger::ThreadBase::TrackBase::getBuffer(uint32_t offset, uint32_t f 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 @@ AudioFlinger::PlaybackThread::Track::Track( 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 @@ AudioFlinger::PlaybackThread::Track::Track( 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::destroy() 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 @@ AudioFlinger::RecordThread::RecordTrack::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) : 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::stop() 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 @@ AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( 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 @@ AudioFlinger::PlaybackThread::OutputTrack::OutputTrack( 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 @@ bool AudioFlinger::PlaybackThread::OutputTrack::write(int16_t* data, uint32_t fr { 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 @@ sp<IAudioRecord> AudioFlinger::openRecord( 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 @@ sp<IAudioRecord> AudioFlinger::openRecord( } // 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 @@ status_t AudioFlinger::RecordThread::dump(int fd, const Vector<String16>& args) 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 @@ void AudioFlinger::RecordThread::audioConfigChanged_l(int event, int param) { 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 @@ void AudioFlinger::RecordThread::readInputParameters() 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 0698dcb957f8..f3371bffd6af 100644 --- a/services/audioflinger/AudioFlinger.h +++ b/services/audioflinger/AudioFlinger.h @@ -76,8 +76,8 @@ public: 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: 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 @@ private: // 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 50dcda7a1aff..6e9319d81699 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 @@ AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate) 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 @@ status_t AudioMixer::setParameter(int target, int name, void *value) 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 88408a7a7789..75c91700e352 100644 --- a/services/audioflinger/AudioMixer.h +++ b/services/audioflinger/AudioMixer.h @@ -61,7 +61,7 @@ public: // set Parameter names // for target TRACK - CHANNEL_COUNT = 0x4000, + CHANNEL_MASK = 0x4000, FORMAT = 0x4001, MAIN_BUFFER = 0x4002, AUX_BUFFER = 0x4003, @@ -150,6 +150,7 @@ private: 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 455b4c209aa4..cd58b9b98f9a 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -39,6 +39,7 @@ import android.content.IntentFilter; 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.Signature; 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 com.android.server.PackageManagerBackupAgent.Metadata; 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 @@ class BackupManagerService extends IBackupManager.Stub { 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 @@ class BackupManagerService extends IBackupManager.Stub { 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 @@ class BackupManagerService extends IBackupManager.Stub { 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 @@ class BackupManagerService extends IBackupManager.Stub { // 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 @@ class BackupManagerService extends IBackupManager.Stub { 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 @@ class BackupManagerService extends IBackupManager.Stub { } + // ----- 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,43 +3494,97 @@ class BackupManagerService extends IBackupManager.Stub { 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) { synchronized (params.latch) { while (params.latch.get() == false) { @@ -2661,9 +3626,10 @@ class BackupManagerService extends IBackupManager.Stub { 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 a564c2d4bac1..dd76eb8c705c 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.ConnectivityManager; 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.provider.Settings; 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.FileDescriptor; 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { } 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** + * 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(); + 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 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @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 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { } 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 @@ public class ConnectivityService extends IConnectivityManager.Stub { } 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 92d76beeeee1..78636f30e8af 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -1925,9 +1925,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // 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 54555bb19689..99c8af62d1d6 100644 --- a/services/java/com/android/server/SystemBackupAgent.java +++ b/services/java/com/android/server/SystemBackupAgent.java @@ -37,16 +37,25 @@ import java.io.IOException; 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 @@ public class SystemBackupAgent extends BackupAgentHelper { } 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 @@ public class SystemBackupAgent extends BackupAgentHelper { (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 5355d4402466..4cd601feb682 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.pm.IPackageManager; 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 @@ class ServerThread extends Thread { 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 @@ class ServerThread extends Thread { 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 cf5592c09e93..b463e560aca4 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -968,10 +968,12 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 final class ActivityManagerService extends ActivityManagerNative } } + 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 d5ac19eb46ba..af6930752772 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -485,6 +485,13 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 6590b915af81..7e7310649572 100644 --- a/services/java/com/android/server/am/BackupRecord.java +++ b/services/java/com/android/server/am/BackupRecord.java @@ -26,6 +26,7 @@ class BackupRecord { 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 000000000000..0442bda40d51 --- /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 1faf8da0f47f..1277bca5ed51 100644 --- a/services/java/com/android/server/am/CompatModePackages.java +++ b/services/java/com/android/server/am/CompatModePackages.java @@ -3,8 +3,10 @@ package com.android.server.am; 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 @@ public class CompatModePackages { 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 @@ public class CompatModePackages { 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 class CompatModePackages { } } - 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 @@ public class CompatModePackages { : 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 @@ public class CompatModePackages { 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 @@ public class CompatModePackages { 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 @@ public class CompatModePackages { } 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 @@ public class CompatModePackages { 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 @@ public class CompatModePackages { } 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 e8c87e130ea8..e61a7f4e10d3 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -34,6 +34,7 @@ class TaskRecord extends ThumbnailHolder { 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 @@ class TaskRecord extends ThumbnailHolder { 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 d083d01fbe9b..1ae828452cf3 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.BroadcastReceiver; 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.SparseArray; 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + @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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } 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 f8d14265217c..295d569c8f84 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 000000000000..fe8879321265 --- /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 000000000000..cf1171fec645 --- /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 ca33d32c2da7..d1ee4f6bfc55 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.replay; 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.text.format.DateUtils; 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 @@ public class ThrottleServiceTest extends AndroidTestCase { private static final String TEST_IFACE = "test0"; - private WatchingContext mWatchingContext; + private BroadcastInterceptingContext mWatchingContext; private INetworkManagementService mMockNMService; private TrustedTime mMockTime; @@ -76,7 +69,7 @@ public class ThrottleServiceTest extends AndroidTestCase { 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 @@ public class ThrottleServiceTest extends AndroidTestCase { 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 3636baa03cb0..e8d4f9850975 100644 --- a/telephony/java/com/android/internal/telephony/DataConnection.java +++ b/telephony/java/com/android/internal/telephony/DataConnection.java @@ -62,6 +62,7 @@ import java.util.concurrent.atomic.AtomicInteger; */ 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { * @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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { } 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { * 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { * 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { 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 @@ public abstract class DataConnection extends StateMachine { 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 a0d9b0f97ed6..62b90aef26cb 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionAc.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java @@ -28,7 +28,7 @@ import android.os.Message; * 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 @@ public class DataConnectionAc extends AsyncChannel { 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 0621cfd3bd5f..2c04b30f1cf0 100644 --- a/telephony/java/com/android/internal/telephony/RIL.java +++ b/telephony/java/com/android/internal/telephony/RIL.java @@ -2888,7 +2888,7 @@ public final class RIL extends BaseCommands implements CommandsInterface { 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 @@ public final class RIL extends BaseCommands implements CommandsInterface { 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 e328b5ba5f7f..b3d889376786 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 c4807ff584b5..f254e3c4d40f 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> |