diff options
99 files changed, 2738 insertions, 1739 deletions
diff --git a/api/current.xml b/api/current.xml index 2729ff4..43e8aa8 100644 --- a/api/current.xml +++ b/api/current.xml @@ -113183,6 +113183,28 @@ visibility="public" > </method> +<method name="getGlobalClassInitCount" + return="int" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getGlobalClassInitTime" + return="int" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getGlobalExternalAllocCount" return="int" abstract="false" @@ -113429,6 +113451,28 @@ visibility="public" > </method> +<method name="resetGlobalClassInitCount" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="resetGlobalClassInitTime" + return="void" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="resetGlobalExternalAllocCount" return="void" abstract="false" @@ -211739,6 +211783,17 @@ <parameter name="measureSpec" type="int"> </parameter> </method> +<method name="resume" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="seekTo" return="void" abstract="false" @@ -211852,6 +211907,17 @@ visibility="public" > </method> +<method name="suspend" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> </class> <class name="ViewAnimator" extends="android.widget.FrameLayout" @@ -215803,7 +215869,7 @@ synchronized="false" static="true" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > </method> @@ -215886,7 +215952,7 @@ value=""/sdcard/dmtrace.trace"" static="true" final="true" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > </field> @@ -215923,6 +215989,28 @@ visibility="public" > </field> +<field name="KIND_GLOBAL_CLASS_INIT_COUNT" + type="int" + transient="false" + volatile="false" + value="32" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="KIND_GLOBAL_CLASS_INIT_TIME" + type="int" + transient="false" + volatile="false" + value="64" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="KIND_GLOBAL_EXT_ALLOCATED_BYTES" type="int" transient="false" @@ -216022,6 +216110,28 @@ visibility="public" > </field> +<field name="KIND_THREAD_CLASS_INIT_COUNT" + type="int" + transient="false" + volatile="false" + value="2097152" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="KIND_THREAD_CLASS_INIT_TIME" + type="int" + transient="false" + volatile="false" + value="4194304" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="KIND_THREAD_EXT_ALLOCATED_BYTES" type="int" transient="false" diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index d640de1..b6c9de4 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -88,6 +88,8 @@ public class Am { if (op.equals("start")) { runStart(); + } else if (op.equals("startservice")) { + runStartService(); } else if (op.equals("instrument")) { runInstrument(); } else if (op.equals("broadcast")) { @@ -183,6 +185,15 @@ public class Am { return intent; } + private void runStartService() throws Exception { + Intent intent = makeIntent(); + System.out.println("Starting service: " + intent); + ComponentName cn = mAm.startService(null, intent, intent.getType()); + if (cn == null) { + System.err.println("Error: Not found; no service started."); + } + } + private void runStart() throws Exception { Intent intent = makeIntent(); System.out.println("Starting: " + intent); @@ -496,6 +507,8 @@ public class Am { " start an Activity: am start [-D] <INTENT>\n" + " -D: enable debugging\n" + "\n" + + " start a Service: am startservice <INTENT>\n" + + "\n" + " send a broadcast Intent: am broadcast <INTENT>\n" + "\n" + " start an Instrumentation: am instrument [flags] <COMPONENT>\n" + diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c index 78f90a1..adec5a4 100644 --- a/cmds/dumpstate/dumpstate.c +++ b/cmds/dumpstate/dumpstate.c @@ -96,6 +96,7 @@ static void dumpstate() { run_command("NETWORK INTERFACES", 10, "netcfg", NULL); dump_file("NETWORK ROUTES", "/proc/net/route"); + dump_file("ARP CACHE", "/proc/net/arp"); #ifdef FWDUMP_bcm4329 run_command("DUMP WIFI FIRMWARE LOG", 60, diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h index 92ae310..66f3676 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -68,7 +68,7 @@ /* other handy constants */ #define PROTECTED_DIR_PREFIX "/data/app-private/" -#define SDCARD_DIR_PREFIX "/asec/" +#define SDCARD_DIR_PREFIX "/mnt/asec/" #define DALVIK_CACHE_PREFIX "/data/dalvik-cache/" #define DALVIK_CACHE_POSTFIX "/classes.dex" diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index 68d8bb0..52f767e 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -17,6 +17,8 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar +LOCAL_MODULE_TAGS := debug + LOCAL_MODULE:= stagefright include $(BUILD_EXECUTABLE) @@ -39,6 +41,8 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar +LOCAL_MODULE_TAGS := debug + LOCAL_MODULE:= record include $(BUILD_EXECUTABLE) @@ -61,6 +65,8 @@ LOCAL_C_INCLUDES:= \ LOCAL_CFLAGS += -Wno-multichar +LOCAL_MODULE_TAGS := debug + LOCAL_MODULE:= audioloop include $(BUILD_EXECUTABLE) diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 43a0f30..3161826 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -43,36 +43,92 @@ import java.util.Map; import com.google.android.collect.Maps; /** - * A class that helps with interactions with the AccountManager Service. It provides - * methods to allow for account, password, and authtoken management for all accounts on the - * device. One accesses the {@link AccountManager} by calling: - * <pre> - * AccountManager accountManager = AccountManager.get(context); - * </pre> + * This class provides access to a centralized registry of the user's + * online accounts. With this service, users only need to enter their + * credentials (username and password) once for any account, granting + * applications access to online resources with "one-click" approval. * - * <p> - * The AccountManager Service provides storage for the accounts known to the system, - * provides methods to manage them, and allows the registration of authenticators to - * which operations such as addAccount and getAuthToken are delegated. - * <p> - * Many of the calls take an {@link AccountManagerCallback} and {@link Handler} as parameters. - * These calls return immediately but run asynchronously. If a callback is provided then - * {@link AccountManagerCallback#run} will be invoked wen the request completes, successfully - * or not. An {@link AccountManagerFuture} is returned by these requests and also passed into the - * callback. The result if retrieved by calling {@link AccountManagerFuture#getResult()} which - * either returns the result or throws an exception as appropriate. - * <p> - * The asynchronous request can be made blocking by not providing a callback and instead - * calling {@link AccountManagerFuture#getResult()} on the future that is returned. This will - * cause the running thread to block until the result is returned. Keep in mind that one - * should not block the main thread in this way. Instead one should either use a callback, - * thus making the call asynchronous, or make the blocking call on a separate thread. - * getResult() will throw an {@link IllegalStateException} if you call it from the main thread - * before the request has completed, i.e. before the callback has been invoked. - * <p> - * If one wants to ensure that the callback is invoked from a specific handler then they should - * pass the handler to the request. This makes it easier to ensure thread-safety by running - * all of one's logic from a single handler. + * <p>Different online services have different ways of handling accounts and + * authentication, so the account manager uses pluggable <em>authenticator</em> + * modules for different <em>account types</em>. The authenticators (which + * may be written by third parties) handle the actual details of validating + * account credentials and storing account information. For example, Google, + * Facebook, and Microsoft Exchange each have their own authenticator. + * + * <p>Many servers support some notion of an <em>authentication token</em>, + * which can be used to authenticate a request to the server without sending + * the user's actual password. (Auth tokens are normally created with a + * separate request which does include the user's credentials.) AccountManager + * can generate these auth tokens for applications, so the application doesn't + * need to handle passwords directly. Auth tokens are normally reusable, and + * cached by AccountManager, but must be refreshed periodically. It's the + * responsibility of applications to <em>invalidate</em> auth tokens when they + * stop working so the AccountManager knows it needs to regenerate them. + * + * <p>Applications accessing a server normally go through these steps: + * + * <ul> + * <li>Get an instance of AccountManager using {@link #get(Context)}. + * + * <li>List the available accounts using {@link #getAccountsByType} or + * {@link #getAccountsByTypeAndFeatures}. Normally applications will only + * be interested in accounts with one particular <em>type</em>, which + * identifies the authenticator. Account <em>features</em> are used to + * identify particular account subtypes and capabilities. Both the account + * type and features are authenticator-specific strings, and must be known by + * the application in coordination with its preferred authenticators. + * + * <li>Select one or more of the available accounts, possibly by asking the + * user for their preference. If no suitable accounts are available, + * {@link #addAccount} may be called to prompt the user to create an + * account of the appropriate type. + * + * <li>Request an auth token for the selected account(s) using one of the + * {@link #getAuthToken} methods or related helpers. Refer to the description + * of each method for exact usage and error handling details. + * + * <li>Make the request using the auth token. The form of the auth token, + * the format of the request, and the protocol used are all specific to the + * service you are accessing. The application makes the request itself, using + * whatever network and protocol libraries are useful. + * + * <li><b>Important:</b> If the request fails with an authentication error, + * it could be that a cached auth token is stale and no longer honored by + * the server. The application must call {@link #invalidateAuthToken} to remove + * the token from the cache, otherwise requests will continue failing! After + * invalidating the auth token, immediately go back to the "Request an auth + * token" step above. If the process fails the second time, then it can be + * treated as a "genuine" authentication failure and the user notified or other + * appropriate actions taken. + * </ul> + * + * <p>Some AccountManager methods may require interaction with the user to + * prompt for credentials, present options, or ask the user to add an account. + * The caller may choose whether to allow AccountManager to directly launch the + * necessary user interface and wait for the user, or to return an Intent which + * the caller may use to launch the interface, or (in some cases) to install a + * notification which the user can select at any time to launch the interface. + * To have AccountManager launch the interface directly, the caller must supply + * the current foreground {@link Activity} context. + * + * <p>Many AccountManager methods take {@link AccountManagerCallback} and + * {@link Handler} as parameters. These methods return immediately but + * run asynchronously. If a callback is provided then + * {@link AccountManagerCallback#run} will be invoked on the Handler's + * thread when the request completes, successfully or not. + * An {@link AccountManagerFuture} is returned by these requests and also + * supplied to the callback (if any). The result is retrieved by calling + * {@link AccountManagerFuture#getResult()} which waits for the operation + * to complete (if necessary) and either returns the result or throws an + * exception if an error occurred during the operation. + * To make the request synchronously, call + * {@link AccountManagerFuture#getResult()} immediately on receiving the + * future from the method. No callback need be supplied. + * + * <p>Requests which may block, including + * {@link AccountManagerFuture#getResult()}, must never be called on + * the application's main event thread. These operations throw + * {@link IllegalStateException} if they are used on the main thread. */ public class AccountManager { private static final String TAG = "AccountManager"; @@ -85,34 +141,65 @@ public class AccountManager { public static final int ERROR_CODE_BAD_ARGUMENTS = 7; public static final int ERROR_CODE_BAD_REQUEST = 8; - public static final String KEY_ACCOUNTS = "accounts"; - public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types"; - public static final String KEY_USERDATA = "userdata"; - public static final String KEY_AUTHTOKEN = "authtoken"; - public static final String KEY_PASSWORD = "password"; + /** + * The Bundle key used for the {@link String} account name in results + * from methods which return information about a particular account. + */ public static final String KEY_ACCOUNT_NAME = "authAccount"; + + /** + * The Bundle key used for the {@link String} account type in results + * from methods which return information about a particular account. + */ public static final String KEY_ACCOUNT_TYPE = "accountType"; - public static final String KEY_ERROR_CODE = "errorCode"; - public static final String KEY_ERROR_MESSAGE = "errorMessage"; + + /** + * The Bundle key used for the auth token value in results + * from {@link #getAuthToken} and friends. + */ + public static final String KEY_AUTHTOKEN = "authtoken"; + + /** + * The Bundle key used for an {@link Intent} in results from methods that + * may require the caller to interact with the user. The Intent can + * be used to start the corresponding user interface activity. + */ public static final String KEY_INTENT = "intent"; - public static final String KEY_BOOLEAN_RESULT = "booleanResult"; + + /** + * The Bundle key used to supply the password directly in options to + * {@link #confirmCredentials}, rather than prompting the user with + * the standard password prompt. + */ + public static final String KEY_PASSWORD = "password"; + + public static final String KEY_ACCOUNTS = "accounts"; public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse"; public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse"; + public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types"; public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage"; public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey"; + public static final String KEY_BOOLEAN_RESULT = "booleanResult"; + public static final String KEY_ERROR_CODE = "errorCode"; + public static final String KEY_ERROR_MESSAGE = "errorMessage"; + public static final String KEY_USERDATA = "userdata"; + public static final String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator"; public static final String AUTHENTICATOR_META_DATA_NAME = - "android.accounts.AccountAuthenticator"; + "android.accounts.AccountAuthenticator"; public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator"; private final Context mContext; private final IAccountManager mService; private final Handler mMainHandler; + /** * Action sent as a broadcast Intent by the AccountsService - * when accounts are added to and/or removed from the device's - * database. + * when accounts are added, accounts are removed, or an + * account's credentials (saved password, etc) are changed. + * + * @see #addOnAccountsUpdatedListener */ public static final String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED"; @@ -136,26 +223,36 @@ public class AccountManager { } /** - * Retrieve an AccountManager instance that is associated with the context that is passed in. - * Certain calls such as {@link #addOnAccountsUpdatedListener} use this context internally, - * so the caller must take care to use a {@link Context} whose lifetime is associated with - * the listener registration. + * Gets an AccountManager instance associated with a Context. + * The {@link Context} will be used as long as the AccountManager is + * active, so make sure to use a {@link Context} whose lifetime is + * commensurate with any listeners registered to + * {@link #addOnAccountsUpdatedListener} or similar methods. + * + * <p>It is safe to call this method from the main thread. + * + * <p>No permission is required to call this method. + * * @param context The {@link Context} to use when necessary - * @return an {@link AccountManager} instance that is associated with context + * @return An {@link AccountManager} instance */ public static AccountManager get(Context context) { return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); } /** - * Get the password that is associated with the account. Returns null if the account does - * not exist. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission - * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running - * with the same UID as the Authenticator for the account. + * Gets the saved password associated with the account. + * This is intended for authenticators and related code; applications + * should get an auth token instead. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} + * and to have the same UID as the account's authenticator. + * + * @param account The account to query for a password + * @return The account's password, null if none or if the account doesn't exist */ public String getPassword(final Account account) { try { @@ -167,14 +264,19 @@ public class AccountManager { } /** - * Get the user data named by "key" that is associated with the account. - * Returns null if the account does not exist or if it does not have a value for key. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission - * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running - * with the same UID as the Authenticator for the account. + * Gets the user data named by "key" associated with the account. + * This is intended for authenticators and related code to store + * arbitrary metadata along with accounts. The meaning of the keys + * and values is up to the authenticator for the account. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} + * and to have the same UID as the account's authenticator. + * + * @param account The account to query for user data + * @return The user data, null if the account or key doesn't exist */ public String getUserData(final Account account, final String key) { try { @@ -186,14 +288,15 @@ public class AccountManager { } /** - * Query the AccountManager Service for an array that contains a - * {@link AuthenticatorDescription} for each registered authenticator. - * @return an array that contains all the authenticators known to the AccountManager service. - * This array will be empty if there are no authenticators and will never return null. - * <p> - * It is safe to call this method from the main thread. - * <p> - * No permission is required to make this call. + * Lists the currently registered authenticators. + * + * <p>It is safe to call this method from the main thread. + * + * <p>No permission is required to call this method. + * + * @return An array of {@link AuthenticatorDescription} for every + * authenticator known to the AccountManager service. Empty (never + * null) if no authenticators are known. */ public AuthenticatorDescription[] getAuthenticatorTypes() { try { @@ -205,13 +308,16 @@ public class AccountManager { } /** - * Query the AccountManager Service for all accounts. - * @return an array that contains all the accounts known to the AccountManager service. - * This array will be empty if there are no accounts and will never return null. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS} + * Lists all accounts of any type registered on the device. + * Equivalent to getAccountsByType(null). + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#GET_ACCOUNTS}. + * + * @return An array of {@link Account}, one for each account. Empty + * (never null) if no accounts have been added. */ public Account[] getAccounts() { try { @@ -223,15 +329,20 @@ public class AccountManager { } /** - * Query the AccountManager for the set of accounts that have a given type. If null - * is passed as the type than all accounts are returned. - * @param type the account type by which to filter, or null to get all accounts - * @return an array that contains the accounts that match the specified type. This array - * will be empty if no accounts match. It will never return null. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS} + * Lists all accounts of a particular type. The account type is a + * string token corresponding to the authenticator and useful domain + * of the account. For example, there are types corresponding to Google + * and Facebook. The exact string token to use will be published somewhere + * associated with the authenticator in question. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#GET_ACCOUNTS}. + * + * @param type The type of accounts to return, null to retrieve all accounts + * @return An array of {@link Account}, one per matching account. Empty + * (never null) if no accounts of the specified type have been added. */ public Account[] getAccountsByType(String type) { try { @@ -243,45 +354,27 @@ public class AccountManager { } /** - * Tests that the given account has the specified features. If this account does not exist - * then this call returns false. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Boolean result = hasFeatures(account, features, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * hasFeatures(account, features, new AccountManagerCallback<Boolean>() { - * public void run(AccountManagerFuture<Boolean> future) { - * Boolean result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}. + * Finds out whether a particular account has all the specified features. + * Account features are authenticator-specific string tokens identifying + * boolean account properties. For example, features are used to tell + * whether Google accounts have a particular service (such as Google + * Calendar or Google Talk) enabled. The feature names and their meanings + * are published somewhere associated with the authenticator in question. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#GET_ACCOUNTS}. * * @param account The {@link Account} to test - * @param features the features for which to test - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Boolean} that is true if the account exists and has the - * specified features. + * @param features An array of the account features to check + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Boolean, + * true if the account exists and has all of the specified features. */ public AccountManagerFuture<Boolean> hasFeatures(final Account account, final String[] features, @@ -300,18 +393,73 @@ public class AccountManager { } /** - * Add an account to the AccountManager's set of known accounts. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission - * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running - * with the same UID as the Authenticator for the account. - * @param account The account to add - * @param password The password to associate with the account. May be null. - * @param userdata A bundle of key/value pairs to set as the account's userdata. May be null. - * @return true if the account was sucessfully added, false otherwise, for example, - * if the account already exists or if the account is null + * Lists all accounts of a type which have certain features. The account + * type identifies the authenticator (see {@link #getAccountsByType}). + * Account features are authenticator-specific string tokens identifying + * boolean account properties (see {@link #hasFeatures}). + * + * <p>Unlike {@link #getAccountsByType}, this method calls the authenticator, + * which may contact the server or do other work to check account features, + * so the method returns an {@link AccountManagerFuture}. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#GET_ACCOUNTS}. + * + * @param type The type of accounts to return, must not be null + * @param features An array of the account features to require, + * may be null or empty + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to an array of + * {@link Account}, one per account of the specified type which + * matches the requested features. + */ + public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( + final String type, final String[] features, + AccountManagerCallback<Account[]> callback, Handler handler) { + return new Future2Task<Account[]>(handler, callback) { + public void doWork() throws RemoteException { + if (type == null) { + Log.e(TAG, "Type is null"); + set(new Account[0]); + return; + } + mService.getAccountsByFeatures(mResponse, type, features); + } + public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { + if (!bundle.containsKey(KEY_ACCOUNTS)) { + throw new AuthenticatorException("no result in response"); + } + final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS); + Account[] descs = new Account[parcelables.length]; + for (int i = 0; i < parcelables.length; i++) { + descs[i] = (Account) parcelables[i]; + } + return descs; + } + }.start(); + } + + /** + * Adds an account directly to the AccountManager. Normally used by sign-up + * wizards associated with authenticators, not directly by applications. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} + * and to have the same UID as the added account's authenticator. + * + * @param account The {@link Account} to add + * @param password The password to associate with the account, null for none + * @param userdata String values to use for the account's userdata, null for none + * @return Whether the account was successfully added. False if the account + * already exists, the account is null, or another error occurs. */ public boolean addAccountExplicitly(Account account, String password, Bundle userdata) { try { @@ -323,43 +471,25 @@ public class AccountManager { } /** - * Removes the given account. If this account does not exist then this call has no effect. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Boolean result = removeAccount(account, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * removeAccount(account, new AccountManagerCallback<Boolean>() { - * public void run(AccountManagerFuture<Boolean> future) { - * Boolean result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * Removes an account from the AccountManager. Does nothing if the account + * does not exist. Does not delete the account from the server. + * The authenticator may have its own policies preventing account + * deletion, in which case the account will not be deleted. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. * * @param account The {@link Account} to remove - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Boolean} that is true if the account is successfully removed - * or false if the authenticator refuses to remove the account. + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Boolean, + * true if the account has been successfully removed, + * false if the authenticator forbids deleting this account. */ public AccountManagerFuture<Boolean> removeAccount(final Account account, AccountManagerCallback<Boolean> callback, Handler handler) { @@ -377,14 +507,19 @@ public class AccountManager { } /** - * Removes the given authtoken. If this authtoken does not exist for the given account type - * then this call has no effect. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. - * @param accountType the account type of the authtoken to invalidate - * @param authToken the authtoken to invalidate + * Removes an auth token from the AccountManager's cache. Does nothing if + * the auth token is not currently in the cache. Applications must call this + * method when the auth token is found to have expired or otherwise become + * invalid for authenticating requests. The AccountManager does not validate + * or expire cached auth tokens otherwise. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param accountType The account type of the auth token to invalidate + * @param authToken The auth token to invalidate */ public void invalidateAuthToken(final String accountType, final String authToken) { try { @@ -396,20 +531,21 @@ public class AccountManager { } /** - * Gets the authtoken named by "authTokenType" for the specified account if it is cached - * by the AccountManager. If no authtoken is cached then null is returned rather than - * asking the authenticaticor to generate one. If the account or the - * authtoken do not exist then null is returned. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission - * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running - * with the same UID as the Authenticator for the account. - * @param account the account whose authtoken is to be retrieved, must not be null - * @param authTokenType the type of authtoken to retrieve - * @return an authtoken for the given account and authTokenType, if one is cached by the - * AccountManager, null otherwise. + * Gets an auth token from the AccountManager's cache. If no auth + * token is cached for this account, null will be returned -- a new + * auth token will not be generated, and the server will not be contacted. + * Intended for use by the authenticator, not directly by applications. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} + * and to have the same UID as the account's authenticator. + * + * @param account The account to fetch an auth token for + * @param authTokenType The type of auth token to fetch, see {#getAuthToken} + * @return The cached auth token for this account and type, or null if + * no auth token is cached or the account does not exist. */ public String peekAuthToken(final Account account, final String authTokenType) { if (account == null) { @@ -428,16 +564,19 @@ public class AccountManager { } /** - * Sets the password for the account. The password may be null. If the account does not exist - * then this call has no affect. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission - * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running - * with the same UID as the Authenticator for the account. - * @param account the account whose password is to be set. Must not be null. - * @param password the password to set for the account. May be null. + * Sets or forgets a saved password. This modifies the local copy of the + * password used to automatically authenticate the user; it does + * not change the user's account password on the server. Intended for use + * by the authenticator, not directly by applications. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} + * and have the same UID as the account's authenticator. + * + * @param account The account to set a password for + * @param password The password to set, null to clear the password */ public void setPassword(final Account account, final String password) { if (account == null) { @@ -453,13 +592,18 @@ public class AccountManager { } /** - * Sets the password for account to null. If the account does not exist then this call - * has no effect. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. - * @param account the account whose password is to be cleared. Must not be null. + * Forgets a saved password. This erases the local copy of the password; + * it does not change the user's account password on the server. + * Has the same effect as setPassword(account, null) but requires fewer + * permissions, and may be used by applications or management interfaces + * to "sign out" from an account. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS} + * + * @param account The account whose password to clear */ public void clearPassword(final Account account) { if (account == null) { @@ -475,17 +619,19 @@ public class AccountManager { } /** - * Sets account's userdata named "key" to the specified value. If the account does not - * exist then this call has no effect. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission - * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running - * with the same UID as the Authenticator for the account. - * @param account the account whose userdata is to be set. Must not be null. - * @param key the key of the userdata to set. Must not be null. - * @param value the value to set. May be null. + * Sets one userdata key for an account. Intended by use for the + * authenticator to stash state for itself, not directly by applications. + * The meaning of the keys and values is up to the authenticator. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} + * and to have the same UID as the account's authenticator. + * + * @param account The account to set the userdata for + * @param key The userdata key to set. Must not be null + * @param value The value to set, null to clear this userdata key */ public void setUserData(final Account account, final String key, final String value) { if (account == null) { @@ -505,17 +651,20 @@ public class AccountManager { } /** - * Sets the authtoken named by "authTokenType" to the value specified by authToken. + * Adds an auth token to the AccountManager cache for an account. * If the account does not exist then this call has no effect. - * <p> - * It is safe to call this method from the main thread. - * <p> - * Requires that the caller has permission - * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running - * with the same UID as the Authenticator for the account. - * @param account the account whose authtoken is to be set. Must not be null. - * @param authTokenType the type of the authtoken to set. Must not be null. - * @param authToken the authToken to set. May be null. + * Replaces any previous auth token for this account and auth token type. + * Intended for use by the authenticator, not directly by applications. + * + * <p>It is safe to call this method from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} + * and to have the same UID as the account's authenticator. + * + * @param account The account to set an auth token for + * @param authTokenType The type of the auth token, see {#getAuthToken} + * @param authToken The auth token to add to the cache */ public void setAuthToken(Account account, final String authTokenType, final String authToken) { try { @@ -527,25 +676,27 @@ public class AccountManager { } /** - * Convenience method that makes a blocking call to - * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)} - * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result. - * <p> - * It is not safe to call this method from the main thread. See {@link #getAuthToken}. - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. - * @param account the account whose authtoken is to be retrieved, must not be null - * @param authTokenType the type of authtoken to retrieve - * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification - * for the account if no authtoken is cached by the AccountManager and the the authenticator - * does not have valid credentials to get an authtoken. - * @return an authtoken for the given account and authTokenType, if one is cached by the - * AccountManager, null otherwise. - * @throws AuthenticatorException if the authenticator is not present, unreachable or returns - * an invalid response. - * @throws OperationCanceledException if the request is canceled for any reason - * @throws java.io.IOException if the authenticator experiences an IOException while attempting - * to communicate with its backend server. + * This convenience helper synchronously gets an auth token with + * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}. + * + * <p>This method may block while a network request completes, and must + * never be made from the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#USE_CREDENTIALS}. + * + * @param account The account to fetch an auth token for + * @param authTokenType The auth token type, see {#link getAuthToken} + * @param notifyAuthFailure If true, display a notification and return null + * if authentication fails; if false, prompt and wait for the user to + * re-enter correct credentials before returning + * @return An auth token of the specified type for this account, or null + * if authentication fails or none can be fetched. + * @throws AuthenticatorException if the authenticator failed to respond + * @throws OperationCanceledException if the request was canceled for any + * reason, including the user canceling a credential request + * @throws java.io.IOException if the authenticator experienced an I/O problem + * creating a new auth token, usually because of network trouble */ public String blockingGetAuthToken(Account account, String authTokenType, boolean notifyAuthFailure) @@ -556,64 +707,57 @@ public class AccountManager { } /** - * Request that an authtoken of the specified type be returned for an account. - * If the Account Manager has a cached authtoken of the requested type then it will - * service the request itself. Otherwise it will pass the request on to the authenticator. - * The authenticator can try to service this request with information it already has stored - * in the AccountManager but may need to launch an activity to prompt the - * user to enter credentials. If it is able to retrieve the authtoken it will be returned - * in the result. - * <p> - * If the authenticator needs to prompt the user for credentials it will return an intent to - * an activity that will do the prompting. The supplied activity will be used to launch the - * intent and the result will come from the launched activity. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Bundle result = getAuthToken( - * account, authTokenType, options, activity, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * getAuthToken(account, authTokenType, options, activity, new AccountManagerCallback<Bundle>() { - * public void run(AccountManagerFuture<Bundle> future) { - * Bundle result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. - * - * @param account The account whose credentials are to be updated. - * @param authTokenType the auth token to retrieve as part of updating the credentials. - * May be null. - * @param options authenticator specific options for the request - * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then - * the intent will be started with this activity. If you do not with to have the intent - * started automatically then use the other form, - * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, android.os.Handler)} - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Bundle} that contains: + * Gets an auth token of the specified type for a particular account, + * prompting the user for credentials if necessary. This method is + * intended for applications running in the foreground where it makes + * sense to ask the user directly for a password. + * + * <p>If a previously generated auth token is cached for this account and + * type, then it will be returned. Otherwise, if we have a saved password + * the server accepts, it will be used to generate a new auth token. + * Otherwise, the user will be asked for a password, which will be sent to + * the server to generate a new auth token. + * + * <p>The value of the auth token type depends on the authenticator. + * Some services use different tokens to access different functionality -- + * for example, Google uses different auth tokens to access Gmail and + * Google Calendar for the same account. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#USE_CREDENTIALS}. + * + * @param account The account to fetch an auth token for + * @param authTokenType The auth token type, an authenticator-dependent + * string token, must not be null + * @param options Authenticator-specific options for the request, + * may be null or empty + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user for a password + * if necessary; used only to call startActivity(); must not be null. + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle with + * at least the following fields: + * <ul> + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied + * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account + * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted + * </ul> + * + * (Other authenticator-specific values may be returned.) If an auth token + * could not be fetched, {@link AccountManagerFuture#getResult()} throws: * <ul> - * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} + * <li> {@link AuthenticatorException} if the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation is canceled for + * any reason, incluidng the user canceling a credential request + * <li> {@link IOException} if the authenticator experienced an I/O problem + * creating a new auth token, usually because of network trouble * </ul> - * If the user presses "back" then the request will be canceled. */ public AccountManagerFuture<Bundle> getAuthToken( final Account account, final String authTokenType, final Bundle options, @@ -630,67 +774,71 @@ public class AccountManager { } /** - * Request that an authtoken of the specified type be returned for an account. - * If the Account Manager has a cached authtoken of the requested type then it will - * service the request itself. Otherwise it will pass the request on to the authenticator. - * The authenticator can try to service this request with information it already has stored - * in the AccountManager but may need to launch an activity to prompt the - * user to enter credentials. If it is able to retrieve the authtoken it will be returned - * in the result. - * <p> - * If the authenticator needs to prompt the user for credentials, rather than returning the - * authtoken it will instead return an intent for - * an activity that will do the prompting. If an intent is returned and notifyAuthFailure - * is true then a notification will be created that launches this intent. This intent can be - * invoked by the caller directly to start the activity that prompts the user for the - * updated credentials. Otherwise this activity will not be run until the user activates - * the notification. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Bundle result = getAuthToken( - * account, authTokenType, notifyAuthFailure, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * getAuthToken(account, authTokenType, notifyAuthFailure, new AccountManagerCallback<Bundle>() { - * public void run(AccountManagerFuture<Bundle> future) { - * Bundle result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. - * - * @param account The account whose credentials are to be updated. - * @param authTokenType the auth token to retrieve as part of updating the credentials. - * May be null. - * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the - * result then a "sign-on needed" notification will be created that will launch this intent. - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Bundle} that contains either: + * Gets an auth token of the specified type for a particular account, + * optionally raising a notification if the user must enter credentials. + * This method is intended for background tasks and services where the + * user should not be immediately interrupted with a password prompt. + * + * <p>If a previously generated auth token is cached for this account and + * type, then it will be returned. Otherwise, if we have saved credentials + * the server accepts, it will be used to generate a new auth token. + * Otherwise, an Intent will be returned which, when started, will prompt + * the user for a password. If the notifyAuthFailure parameter is set, + * the same Intent will be associated with a status bar notification, + * alerting the user that they need to enter a password at some point. + * + * <p>If the intent is left in a notification, you will need to wait until + * the user gets around to entering a password before trying again, + * which could be hours or days or never. When it does happen, the + * account manager will broadcast the {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} + * {@link Intent}, which applications can use to trigger another attempt + * to fetch an auth token. + * + * <p>If notifications are not enabled, it is the application's + * responsibility to launch the returned intent at some point to let + * the user enter credentials. In either case, the result from this + * call will not wait for user action. + * + * <p>The value of the auth token type depends on the authenticator. + * Some services use different tokens to access different functionality -- + * for example, Google uses different auth tokens to access Gmail and + * Google Calendar for the same account. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#USE_CREDENTIALS}. + * + * @param account The account to fetch an auth token for + * @param authTokenType The auth token type, an authenticator-dependent + * string token, must not be null + * @param notifyAuthFailure True to add a notification to prompt the + * user for a password if necessary, false to leave that to the caller + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle with + * at least the following fields on success: * <ul> - * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials - * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} - * if the authenticator is able to retrieve the auth token + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied + * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account + * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted + * </ul> + * + * (Other authenticator-specific values may be returned.) If the user + * must enter credentials, the returned Bundle contains only + * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt. + * + * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * <ul> + * <li> {@link AuthenticatorException} if the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation is canceled for + * any reason, incluidng the user canceling a credential request + * <li> {@link IOException} if the authenticator experienced an I/O problem + * creating a new auth token, usually because of network trouble * </ul> - * If the user presses "back" then the request will be canceled. */ public AccountManagerFuture<Bundle> getAuthToken( final Account account, final String authTokenType, final boolean notifyAuthFailure, @@ -706,57 +854,52 @@ public class AccountManager { } /** - * Request that an account be added with the given accountType. This request - * is processed by the authenticator for the account type. If no authenticator is registered - * in the system then {@link AuthenticatorException} is thrown. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Bundle result = addAccount( - * account, authTokenType, features, options, activity, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * addAccount(account, authTokenType, features, options, activity, new AccountManagerCallback<Bundle>() { - * public void run(AccountManagerFuture<Bundle> future) { - * Bundle result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. - * - * @param accountType The type of account to add. This must not be null. - * @param authTokenType The account that is added should be able to service this auth token - * type. This may be null. - * @param requiredFeatures The account that is added should support these features. - * This array may be null or empty. - * @param addAccountOptions A bundle of authenticator-specific options that is passed on - * to the authenticator. This may be null. - * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then - * the intent will be started with this activity. If activity is null then the result will - * be returned as-is. - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Bundle} that contains either: + * Asks the user to add an account of a specified type. The authenticator + * for this account type processes this request with the appropriate user + * interface. If the user does elect to create a new account, the account + * name is returned. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param accountType The type of account to add; must not be null + * @param authTokenType The type of auth token (see {@link #getAuthToken}) + * this account will need to be able to generate, null for none + * @param requiredFeatures The features (see {@link #hasFeatures}) this + * account must have, null for none + * @param addAccountOptions Authenticator-specific options for the request, + * may be null or empty + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user to create an + * account; used only to call startActivity(); if null, the prompt + * will not be launched directly, but the necessary {@link Intent} + * will be returned to the caller instead + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle with + * these fields if activity was specified and an account was created: * <ul> - * <li> {@link #KEY_INTENT}, or - * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created + * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account + * </ul> + * + * If no activity was specified, the returned Bundle contains only + * {@link #KEY_INTENT} with the {@link Intent} needed to launch the + * actual account creation process. + * + * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * <ul> + * <li> {@link AuthenticatorException} if no authenticator was registered for + * this account type or the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation was canceled for + * any reason, including the user canceling the creation process + * <li> {@link IOException} if the authenticator experienced an I/O problem + * creating a new account, usually because of network trouble * </ul> */ public AccountManagerFuture<Bundle> addAccount(final String accountType, @@ -778,128 +921,59 @@ public class AccountManager { } /** - * Queries for accounts that match the given account type and feature set. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Account[] result = - * getAccountsByTypeAndFeatures(accountType, features, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * getAccountsByTypeAndFeatures(accountType, features, new AccountManagerCallback<Account[]>() { - * public void run(AccountManagerFuture<Account[]> future) { - * Account[] result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS}. - * - * @param type The type of {@link Account} to return. If null is passed in then an empty - * array will be returned. - * @param features the features with which to filter the accounts list. Each returned account - * will have all specified features. This may be null, which will mean the account list will - * not be filtered by features, making this functionally identical to - * {@link #getAccountsByType(String)}. - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a an {@link Account} array that contains accounts of the specified - * type that match all the requested features. - */ - public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( - final String type, final String[] features, - AccountManagerCallback<Account[]> callback, Handler handler) { - return new Future2Task<Account[]>(handler, callback) { - public void doWork() throws RemoteException { - if (type == null) { - Log.e(TAG, "Type is null"); - set(new Account[0]); - return; - } - mService.getAccountsByFeatures(mResponse, type, features); - } - public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { - if (!bundle.containsKey(KEY_ACCOUNTS)) { - throw new AuthenticatorException("no result in response"); - } - final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS); - Account[] descs = new Account[parcelables.length]; - for (int i = 0; i < parcelables.length; i++) { - descs[i] = (Account) parcelables[i]; - } - return descs; - } - }.start(); - } - - /** - * Requests that the authenticator checks that the user knows the credentials for the account. - * This is typically done by returning an intent to an activity that prompts the user to - * enter the credentials. This request - * is processed by the authenticator for the account. If no matching authenticator is - * registered in the system then {@link AuthenticatorException} is thrown. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Bundle result = confirmCredentials( - * account, options, activity, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * confirmCredentials(account, options, activity, new AccountManagerCallback<Bundle>() { - * public void run(AccountManagerFuture<Bundle> future) { - * Bundle result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. - * - * @param account The account whose credentials are to be checked - * @param options authenticator specific options for the request - * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then - * the intent will be started with this activity. If activity is null then the result will - * be returned as-is. - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Bundle} that contains either: + * Confirms that the user knows the password for an account to make extra + * sure they are the owner of the account. The user-entered password can + * be supplied directly, otherwise the authenticator for this account type + * prompts the user with the appropriate interface. This method is + * intended for applications which want extra assurance; for example, the + * phone lock screen uses this to let the user unlock the phone with an + * account password if they forget the lock pattern. + * + * <p>If the user-entered password matches a saved password for this + * account, the request is considered valid; otherwise the authenticator + * verifies the password (usually by contacting the server). + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param account The account to confirm password knowledge for + * @param options Authenticator-specific options for the request; + * if the {@link #KEY_PASSWORD} string field is present, the + * authenticator may use it directly rather than prompting the user; + * may be null or empty + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user to enter a + * password; used only to call startActivity(); if null, the prompt + * will not be launched directly, but the necessary {@link Intent} + * will be returned to the caller instead + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle + * with these fields if activity or password was supplied and + * the account was successfully verified: + * <ul> + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created + * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account + * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success + * </ul> + * + * If no activity or password was specified, the returned Bundle contains + * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the + * password prompt. + * + * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: * <ul> - * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials - * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct - * credentials + * <li> {@link AuthenticatorException} if the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation was canceled for + * any reason, including the user canceling the password prompt + * <li> {@link IOException} if the authenticator experienced an I/O problem + * verifying the password, usually because of network trouble * </ul> - * If the user presses "back" then the request will be canceled. */ public AccountManagerFuture<Bundle> confirmCredentials(final Account account, final Bundle options, @@ -914,59 +988,52 @@ public class AccountManager { } /** - * Requests that the authenticator update the the credentials for a user. This is typically - * done by returning an intent to an activity that will prompt the user to update the stored - * credentials for the account. This request - * is processed by the authenticator for the account. If no matching authenticator is - * registered in the system then {@link AuthenticatorException} is thrown. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Bundle result = updateCredentials( - * account, authTokenType, options, activity, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * updateCredentials(account, authTokenType, options, activity, new AccountManagerCallback<Bundle>() { - * public void run(AccountManagerFuture<Bundle> future) { - * Bundle result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. - * - * @param account The account whose credentials are to be updated. - * @param authTokenType the auth token to retrieve as part of updating the credentials. - * May be null. - * @param options authenticator specific options for the request - * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then - * the intent will be started with this activity. If activity is null then the result will - * be returned as-is. - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Bundle} that contains either: + * Asks the user to enter a new password for an account, updating the + * saved credentials for the account. Normally this happens automatically + * when the server rejects credentials during an auth token fetch, but this + * can be invoked directly to ensure we have the correct credentials stored. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param account The account to update credentials for + * @param authTokenType The credentials entered must allow an auth token + * of this type to be created (but no actual auth token is returned); + * may be null + * @param options Authenticator-specific options for the request; + * may be null or empty + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to prompt the user to enter a + * password; used only to call startActivity(); if null, the prompt + * will not be launched directly, but the necessary {@link Intent} + * will be returned to the caller instead + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle + * with these fields if an activity was supplied and the account + * credentials were successfully updated: * <ul> - * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials - * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct - * credentials. + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created + * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account + * </ul> + * + * If no activity was specified, the returned Bundle contains only + * {@link #KEY_INTENT} with the {@link Intent} needed to launch the + * password prompt. + * + * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * <ul> + * <li> {@link AuthenticatorException} if the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation was canceled for + * any reason, including the user canceling the password prompt + * <li> {@link IOException} if the authenticator experienced an I/O problem + * verifying the password, usually because of network trouble * </ul> - * If the user presses "back" then the request will be canceled. */ public AccountManagerFuture<Bundle> updateCredentials(final Account account, final String authTokenType, @@ -982,53 +1049,41 @@ public class AccountManager { } /** - * Request that the properties for an authenticator be updated. This is typically done by - * returning an intent to an activity that will allow the user to make changes. This request - * is processed by the authenticator for the account. If no matching authenticator is - * registered in the system then {@link AuthenticatorException} is thrown. - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Do not block the main thread waiting this method's result. - * <p> - * Not allowed from main thread (but allowed from other threads): - * <pre> - * Bundle result = editProperties(accountType, activity, callback, handler).getResult(); - * </pre> - * Allowed from main thread: - * <pre> - * editProperties(accountType, activity, new AccountManagerCallback<Bundle>() { - * public void run(AccountManagerFuture<Bundle> future) { - * Bundle result = future.getResult(); - * // use result - * } - * }, handler); - * </pre> - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. - * - * @param accountType The account type of the authenticator whose properties are to be edited. - * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then - * the intent will be started with this activity. If activity is null then the result will - * be returned as-is. - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Bundle} that contains either: + * Offers the user an opportunity to change an authenticator's settings. + * These properties are for the authenticator in general, not a particular + * account. Not all authenticators support this method. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param accountType The account type associated with the authenticator + * to adjust + * @param activity The {@link Activity} context to use for launching a new + * authenticator-defined sub-Activity to adjust authenticator settings; + * used only to call startActivity(); if null, the settings dialog will + * not be launched directly, but the necessary {@link Intent} will be + * returned to the caller instead + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle + * which is empty if properties were edited successfully, or + * if no activity was specified, contains only {@link #KEY_INTENT} + * needed to launch the authenticator's settings dialog. + * + * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: * <ul> - * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials - * <li> nothing, returned if the edit completes successfully + * <li> {@link AuthenticatorException} if no authenticator was registered for + * this account type or the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation was canceled for + * any reason, including the user canceling the settings dialog + * <li> {@link IOException} if the authenticator experienced an I/O problem + * updating settings, usually because of network trouble * </ul> - * If the user presses "back" then the request will be canceled. */ public AccountManagerFuture<Bundle> editProperties(final String accountType, final Activity activity, final AccountManagerCallback<Bundle> callback, @@ -1475,57 +1530,68 @@ public class AccountManager { } /** - * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures}, - * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)}, - * and {@link #addAccount}. It first gets the list of accounts that match accountType and the - * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType - * feature set, and addAccountOptions. If there is exactly one then - * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is - * called with that account. If there are more than one then a chooser activity is launched - * to prompt the user to select one of them and then the authtoken is retrieved for it, - * <p> - * This call returns immediately but runs asynchronously and the result is accessed via the - * {@link AccountManagerFuture} that is returned. This future is also passed as the sole - * parameter to the {@link AccountManagerCallback}. If the caller wished to use this - * method asynchronously then they will generally pass in a callback object that will get - * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then - * they will generally pass null for the callback and instead call - * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, - * which will then block until the request completes. - * <p> - * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. - * - * @param accountType the accountType to query; this must be non-null - * @param authTokenType the type of authtoken to retrieve; this must be non-null - * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}. - * @param activityForPrompting The activity used to start any account management - * activities that are required to fulfill this request. This may be null. - * @param addAccountOptions authenticator-specific options used if an account needs to be added - * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken - * @param callback A callback to invoke when the request completes. If null then - * no callback is invoked. - * @param handler The {@link Handler} to use to invoke the callback. If null then the - * main thread's {@link Handler} is used. - * @return an {@link AccountManagerFuture} that represents the future result of the call. - * The future result is a {@link Bundle} that contains either: + * This convenience helper combines the functionality of + * {@link #getAccountsByTypeAndFeatures}, {@link #getAuthToken}, and + * {@link #addAccount}. + * + * <p>This method gets a list of the accounts matching the + * specified type and feature set; if there is exactly one, it is + * used; if there are more than one, the user is prompted to pick one; + * if there are none, the user is prompted to add one. Finally, + * an auth token is acquired for the chosen account. + * + * <p>This method may be called from any thread, but the returned + * {@link AccountManagerFuture} must not be used on the main thread. + * + * <p>This method requires the caller to hold the permission + * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. + * + * @param accountType The account type required + * (see {@link #getAccountsByType}), must not be null + * @param authTokenType The desired auth token type + * (see {@link #getAuthToken}), must not be null + * @param features Required features for the account + * (see {@link #getAccountsByTypeAndFeatures}), may be null or empty + * @param activity The {@link Activity} context to use for launching new + * sub-Activities to prompt to add an account, select an account, + * and/or enter a password, as necessary; used only to call + * startActivity(); should not be null + * @param addAccountOptions Authenticator-specific options to use for + * adding new accounts; may be null or empty + * @param getAuthTokenOptions Authenticator-specific options to use for + * getting auth tokens; may be null or empty + * @param callback Callback to invoke when the request completes, + * null for no callback + * @param handler {@link Handler} identifying the callback thread, + * null for the main thread + * @return An {@link AccountManagerFuture} which resolves to a Bundle with + * at least the following fields: * <ul> - * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to - * fulfill the request. - * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the - * request completes successfully. + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account + * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account + * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted + * </ul> + * + * <p>If an error occurred, {@link AccountManagerFuture#getResult()} throws: + * <ul> + * <li> {@link AuthenticatorException} if no authenticator was registered for + * this account type or the authenticator failed to respond + * <li> {@link OperationCanceledException} if the operation was canceled for + * any reason, including the user canceling any operation + * <li> {@link IOException} if the authenticator experienced an I/O problem + * updating settings, usually because of network trouble * </ul> - * If the user presses "back" then the request will be canceled. */ public AccountManagerFuture<Bundle> getAuthTokenByFeatures( final String accountType, final String authTokenType, final String[] features, - final Activity activityForPrompting, final Bundle addAccountOptions, + final Activity activity, final Bundle addAccountOptions, final Bundle getAuthTokenOptions, final AccountManagerCallback<Bundle> callback, final Handler handler) { if (accountType == null) throw new IllegalArgumentException("account type is null"); if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); final GetAuthTokenByTypeAndFeaturesTask task = new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, - activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler); + activity, addAccountOptions, getAuthTokenOptions, callback, handler); task.start(); return task; } @@ -1552,18 +1618,26 @@ public class AccountManager { }; /** - * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. - * The listener is guaranteed to be invoked on the thread of the Handler that is passed - * in or the main thread's Handler if handler is null. - * <p> - * You must remove this listener before the context that was used to retrieve this - * {@link AccountManager} instance goes away. This generally means when the Activity - * or Service you are running is stopped. - * @param listener the listener to add - * @param handler the Handler whose thread will be used to invoke the listener. If null - * the AccountManager context's main thread will be used. - * @param updateImmediately if true then the listener will be invoked as a result of this - * call. + * Adds an {@link OnAccountsUpdateListener} to this instance of the + * {@link AccountManager}. This listener will be notified whenever the + * list of accounts on the device changes. + * + * <p>As long as this listener is present, the AccountManager instance + * will not be garbage-collected, and neither will the {@link Context} + * used to retrieve it, which may be a large Activity instance. To avoid + * memory leaks, you must remove this listener before then. Normally + * listeners are added in an Activity or Service's {@link Activity#onCreate} + * and removed in {@link Activity#onDestroy}. + * + * <p>It is safe to call this method from the main thread. + * + * <p>No permission is required to call this method. + * + * @param listener The listener to send notifications to + * @param handler {@link Handler} identifying the thread to use + * for notifications, null for the main thread + * @param updateImmediately If true, the listener will be invoked + * (on the handler thread) right away with the current account list * @throws IllegalArgumentException if listener is null * @throws IllegalStateException if listener was already added */ @@ -1596,9 +1670,15 @@ public class AccountManager { } /** - * Remove an {@link OnAccountsUpdateListener} that was previously registered with - * {@link #addOnAccountsUpdatedListener}. - * @param listener the listener to remove + * Removes an {@link OnAccountsUpdateListener} previously registered with + * {@link #addOnAccountsUpdatedListener}. The listener will no longer + * receive notifications of account changes. + * + * <p>It is safe to call this method from the main thread. + * + * <p>No permission is required to call this method. + * + * @param listener The previously added listener to remove * @throws IllegalArgumentException if listener is null * @throws IllegalStateException if listener was not already added */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 0355016..0756c71 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -77,9 +77,12 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.net.URL; import java.util.ArrayList; +import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -460,6 +463,7 @@ public final class ActivityThread { mClassLoader = ApplicationLoaders.getDefault().getClassLoader( zip, mDataDir, mBaseClassLoader); + initializeJavaContextClassLoader(); } else { if (mBaseClassLoader == null) { mClassLoader = ClassLoader.getSystemClassLoader(); @@ -471,6 +475,120 @@ public final class ActivityThread { } } + /** + * Setup value for Thread.getContextClassLoader(). If the + * package will not run in in a VM with other packages, we set + * the Java context ClassLoader to the + * PackageInfo.getClassLoader value. However, if this VM can + * contain multiple packages, we intead set the Java context + * ClassLoader to a proxy that will warn about the use of Java + * context ClassLoaders and then fall through to use the + * system ClassLoader. + * + * <p> Note that this is similar to but not the same as the + * android.content.Context.getClassLoader(). While both + * context class loaders are typically set to the + * PathClassLoader used to load the package archive in the + * single application per VM case, a single Android process + * may contain several Contexts executing on one thread with + * their own logical ClassLoaders while the Java context + * ClassLoader is a thread local. This is why in the case when + * we have multiple packages per VM we do not set the Java + * context ClassLoader to an arbitrary but instead warn the + * user to set their own if we detect that they are using a + * Java library that expects it to be set. + */ + private void initializeJavaContextClassLoader() { + IPackageManager pm = getPackageManager(); + android.content.pm.PackageInfo pi; + try { + pi = pm.getPackageInfo(mPackageName, 0); + } catch (RemoteException e) { + throw new AssertionError(e); + } + /* + * Two possible indications that this package could be + * sharing its virtual machine with other packages: + * + * 1.) the sharedUserId attribute is set in the manifest, + * indicating a request to share a VM with other + * packages with the same sharedUserId. + * + * 2.) the application element of the manifest has an + * attribute specifying a non-default process name, + * indicating the desire to run in another packages VM. + */ + boolean sharedUserIdSet = (pi.sharedUserId != null); + boolean processNameNotDefault = + (pi.applicationInfo != null && + !mPackageName.equals(pi.applicationInfo.processName)); + boolean sharable = (sharedUserIdSet || processNameNotDefault); + ClassLoader contextClassLoader = + (sharable) + ? new WarningContextClassLoader() + : mClassLoader; + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + + private static class WarningContextClassLoader extends ClassLoader { + + private static boolean warned = false; + + private void warn(String methodName) { + if (warned) { + return; + } + warned = true; + Thread.currentThread().setContextClassLoader(getParent()); + Log.w(TAG, "ClassLoader." + methodName + ": " + + "The class loader returned by " + + "Thread.getContextClassLoader() may fail for processes " + + "that host multiple applications. You should explicitly " + + "specify a context class loader. For example: " + + "Thread.setContextClassLoader(getClass().getClassLoader());"); + } + + @Override public URL getResource(String resName) { + warn("getResource"); + return getParent().getResource(resName); + } + + @Override public Enumeration<URL> getResources(String resName) throws IOException { + warn("getResources"); + return getParent().getResources(resName); + } + + @Override public InputStream getResourceAsStream(String resName) { + warn("getResourceAsStream"); + return getParent().getResourceAsStream(resName); + } + + @Override public Class<?> loadClass(String className) throws ClassNotFoundException { + warn("loadClass"); + return getParent().loadClass(className); + } + + @Override public void setClassAssertionStatus(String cname, boolean enable) { + warn("setClassAssertionStatus"); + getParent().setClassAssertionStatus(cname, enable); + } + + @Override public void setPackageAssertionStatus(String pname, boolean enable) { + warn("setPackageAssertionStatus"); + getParent().setPackageAssertionStatus(pname, enable); + } + + @Override public void setDefaultAssertionStatus(boolean enable) { + warn("setDefaultAssertionStatus"); + getParent().setDefaultAssertionStatus(enable); + } + + @Override public void clearAssertionStatus() { + warn("clearAssertionStatus"); + getParent().clearAssertionStatus(); + } + } + public String getAppDir() { return mAppDir; } diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java index 4bf59eb..07c08ff 100644 --- a/core/java/android/backup/BackupManager.java +++ b/core/java/android/backup/BackupManager.java @@ -47,9 +47,6 @@ import android.util.Log; public class BackupManager { private static final String TAG = "BackupManager"; - /** @hide TODO: REMOVE THIS */ - public static final boolean EVEN_THINK_ABOUT_DOING_RESTORE = true; - private Context mContext; private static IBackupManager sService; @@ -78,9 +75,6 @@ public class BackupManager { * {@link android.app.BackupAgent} subclass will be scheduled when you call this method. */ public void dataChanged() { - if (!EVEN_THINK_ABOUT_DOING_RESTORE) { - return; - } checkServiceBinder(); if (sService != null) { try { @@ -100,9 +94,6 @@ public class BackupManager { * permission if the package named in the argument is not the caller's own. */ public static void dataChanged(String packageName) { - if (!EVEN_THINK_ABOUT_DOING_RESTORE) { - return; - } checkServiceBinder(); if (sService != null) { try { @@ -118,9 +109,6 @@ public class BackupManager { * {@link android.backup.RestoreSession} class for documentation on that process. */ public RestoreSession beginRestoreSession() { - if (!EVEN_THINK_ABOUT_DOING_RESTORE) { - return null; - } RestoreSession session = null; checkServiceBinder(); if (sService != null) { diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index cf9c58f..e77e76f 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -664,6 +664,10 @@ public final class BluetoothDevice implements Parcelable { * determine which channel to connect to. * <p>The remote device will be authenticated and communication on this * socket will be encrypted. + * <p>Hint: If you are connecting to a Bluetooth serial board then try + * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB. + * However if you are connecting to an Android peer then please generate + * your own unique UUID. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @param uuid service record uuid to lookup RFCOMM channel diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index f793a00..399a87d 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -309,7 +309,7 @@ interface IPackageManager { * MountService uses this to call into the package manager to update * status of sdcard. */ - void updateExternalMediaStatus(boolean mounted); + boolean updateExternalMediaStatus(boolean mounted); String nextPackageToClean(String lastPackage); diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java index 9aa23fe..915c5d7 100644 --- a/core/java/android/net/InterfaceConfiguration.java +++ b/core/java/android/net/InterfaceConfiguration.java @@ -45,10 +45,10 @@ public class InterfaceConfiguration implements Parcelable { } private static void putAddress(StringBuffer buf, int addr) { - buf.append(addr & 0xff).append('.'). - append((addr >>>= 8) & 0xff).append('.'). - append((addr >>>= 8) & 0xff).append('.'). - append((addr >>>= 8) & 0xff); + buf.append((addr >> 24) & 0xff).append('.'). + append((addr >> 16) & 0xff).append('.'). + append((addr >> 8) & 0xff).append('.'). + append(addr & 0xff); } /** Implement the Parcelable interface {@hide} */ diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index 9ee251e..a4c595d 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -541,6 +541,14 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo public static int getGlobalFreedSize() { return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES); } + public static int getGlobalClassInitCount() { + /* number of classes that have been successfully initialized */ + return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT); + } + public static int getGlobalClassInitTime() { + /* cumulative elapsed time for class initialization, in usec */ + return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME); + } public static int getGlobalExternalAllocCount() { return VMDebug.getAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS); } @@ -584,6 +592,12 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo public static void resetGlobalFreedSize() { VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_FREED_BYTES); } + public static void resetGlobalClassInitCount() { + VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_COUNT); + } + public static void resetGlobalClassInitTime() { + VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_CLASS_INIT_TIME); + } public static void resetGlobalExternalAllocCount() { VMDebug.resetAllocCount(VMDebug.KIND_GLOBAL_EXT_ALLOCATED_OBJECTS); } diff --git a/core/java/android/pim/RecurrenceSet.java b/core/java/android/pim/RecurrenceSet.java index 5d09fb5..635323e 100644 --- a/core/java/android/pim/RecurrenceSet.java +++ b/core/java/android/pim/RecurrenceSet.java @@ -18,7 +18,6 @@ package android.pim; import android.content.ContentValues; import android.database.Cursor; -import android.os.Bundle; import android.provider.Calendar; import android.text.TextUtils; import android.text.format.Time; @@ -26,6 +25,7 @@ import android.util.Config; import android.util.Log; import java.util.List; +import java.util.regex.Pattern; /** * Basic information about a recurrence, following RFC 2445 Section 4.8.5. @@ -36,6 +36,7 @@ public class RecurrenceSet { private final static String TAG = "CalendarProvider"; private final static String RULE_SEPARATOR = "\n"; + private final static String FOLDING_SEPARATOR = "\n "; // TODO: make these final? public EventRecurrence[] rrules = null; @@ -309,7 +310,8 @@ public static boolean populateComponent(ContentValues values, String rdateStr = values.getAsString(Calendar.Events.RDATE); String exruleStr = values.getAsString(Calendar.Events.EXRULE); String exdateStr = values.getAsString(Calendar.Events.EXDATE); - boolean allDay = values.getAsInteger(Calendar.Events.ALL_DAY) == 1; + Integer allDayInteger = values.getAsInteger(Calendar.Events.ALL_DAY); + boolean allDay = (null != allDayInteger) ? (allDayInteger == 1) : false; if ((dtstart == -1) || (TextUtils.isEmpty(duration))|| @@ -361,7 +363,7 @@ public static boolean populateComponent(ContentValues values, if (TextUtils.isEmpty(ruleStr)) { return; } - String[] rrules = ruleStr.split(RULE_SEPARATOR); + String[] rrules = getRuleStrings(ruleStr); for (String rrule : rrules) { ICalendar.Property prop = new ICalendar.Property(propertyName); prop.setValue(rrule); @@ -369,6 +371,52 @@ public static boolean populateComponent(ContentValues values, } } + private static String[] getRuleStrings(String ruleStr) { + if (null == ruleStr) { + return new String[0]; + } + String unfoldedRuleStr = unfold(ruleStr); + String[] split = unfoldedRuleStr.split(RULE_SEPARATOR); + int count = split.length; + for (int n = 0; n < count; n++) { + split[n] = fold(split[n]); + } + return split; + } + + + private static final Pattern IGNORABLE_ICAL_WHITESPACE_RE = + Pattern.compile("(?:\\r\\n?|\\n)[ \t]"); + + private static final Pattern FOLD_RE = Pattern.compile(".{75}"); + + /** + * fold and unfolds ical content lines as per RFC 2445 section 4.1. + * + * <h3>4.1 Content Lines</h3> + * + * <p>The iCalendar object is organized into individual lines of text, called + * content lines. Content lines are delimited by a line break, which is a CRLF + * sequence (US-ASCII decimal 13, followed by US-ASCII decimal 10). + * + * <p>Lines of text SHOULD NOT be longer than 75 octets, excluding the line + * break. Long content lines SHOULD be split into a multiple line + * representations using a line "folding" technique. That is, a long line can + * be split between any two characters by inserting a CRLF immediately + * followed by a single linear white space character (i.e., SPACE, US-ASCII + * decimal 32 or HTAB, US-ASCII decimal 9). Any sequence of CRLF followed + * immediately by a single linear white space character is ignored (i.e., + * removed) when processing the content type. + */ + public static String fold(String unfoldedIcalContent) { + return FOLD_RE.matcher(unfoldedIcalContent).replaceAll("$0\r\n "); + } + + public static String unfold(String foldedIcalContent) { + return IGNORABLE_ICAL_WHITESPACE_RE.matcher( + foldedIcalContent).replaceAll(""); + } + private static void addPropertyForDateStr(ICalendar.Component component, String propertyName, String dateStr) { diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java index 2eb25954..194fe33 100644 --- a/core/java/android/pim/vcard/VCardComposer.java +++ b/core/java/android/pim/vcard/VCardComposer.java @@ -26,8 +26,6 @@ import android.database.sqlite.SQLiteException; import android.net.Uri; import android.os.RemoteException; import android.pim.vcard.exception.VCardException; -import android.provider.CallLog; -import android.provider.CallLog.Calls; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; @@ -44,8 +42,6 @@ import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; -import android.text.TextUtils; -import android.text.format.Time; import android.util.CharsetUtils; import android.util.Log; @@ -60,7 +56,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -125,12 +120,6 @@ public class VCardComposer { public static final String VCARD_TYPE_STRING_DOCOMO = "docomo"; - // Property for call log entry - private static final String VCARD_PROPERTY_X_TIMESTAMP = "X-IRMC-CALL-DATETIME"; - private static final String VCARD_PROPERTY_CALLTYPE_INCOMING = "INCOMING"; - private static final String VCARD_PROPERTY_CALLTYPE_OUTGOING = "OUTGOING"; - private static final String VCARD_PROPERTY_CALLTYPE_MISSED = "MISSED"; - private static final String SHIFT_JIS = "SHIFT_JIS"; private static final String UTF_8 = "UTF-8"; @@ -275,30 +264,14 @@ public class VCardComposer { private final String mCharsetString; private boolean mTerminateIsCalled; - final private List<OneEntryHandler> mHandlerList; + private final List<OneEntryHandler> mHandlerList; private String mErrorReason = NO_ERROR; - private boolean mIsCallLogComposer; - private static final String[] sContactsProjection = new String[] { Contacts._ID, }; - /** The projection to use when querying the call log table */ - private static final String[] sCallLogProjection = new String[] { - Calls.NUMBER, Calls.DATE, Calls.TYPE, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE, - Calls.CACHED_NUMBER_LABEL - }; - private static final int NUMBER_COLUMN_INDEX = 0; - private static final int DATE_COLUMN_INDEX = 1; - private static final int CALL_TYPE_COLUMN_INDEX = 2; - private static final int CALLER_NAME_COLUMN_INDEX = 3; - private static final int CALLER_NUMBERTYPE_COLUMN_INDEX = 4; - private static final int CALLER_NUMBERLABEL_COLUMN_INDEX = 5; - - private static final String FLAG_TIMEZONE_UTC = "Z"; - public VCardComposer(Context context) { this(context, VCardConfig.VCARD_TYPE_DEFAULT, true); } @@ -377,6 +350,7 @@ public class VCardComposer { if (contentUri == null) { return false; } + if (mCareHandlerErrors) { List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( mHandlerList.size()); @@ -396,10 +370,7 @@ public class VCardComposer { } final String[] projection; - if (CallLog.Calls.CONTENT_URI.equals(contentUri)) { - projection = sCallLogProjection; - mIsCallLogComposer = true; - } else if (Contacts.CONTENT_URI.equals(contentUri) || + if (Contacts.CONTENT_URI.equals(contentUri) || CONTACTS_TEST_CONTENT_URI.equals(contentUri)) { projection = sContactsProjection; } else { @@ -426,11 +397,7 @@ public class VCardComposer { return false; } - if (mIsCallLogComposer) { - mIdColumn = -1; - } else { - mIdColumn = mCursor.getColumnIndex(Contacts._ID); - } + mIdColumn = mCursor.getColumnIndex(Contacts._ID); return true; } @@ -448,19 +415,14 @@ public class VCardComposer { mErrorReason = FAILURE_REASON_NOT_INITIALIZED; return false; } - String name = null; String vcard; try { - if (mIsCallLogComposer) { - vcard = createOneCallLogEntryInternal(); + if (mIdColumn >= 0) { + vcard = createOneEntryInternal(mCursor.getString(mIdColumn), + getEntityIteratorMethod); } else { - if (mIdColumn >= 0) { - vcard = createOneEntryInternal(mCursor.getString(mIdColumn), - getEntityIteratorMethod); - } else { - Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn); - return true; - } + Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn); + return true; } } catch (VCardException e) { Log.e(LOG_TAG, "VCardException has been thrown: " + e.getMessage()); @@ -468,7 +430,7 @@ public class VCardComposer { } catch (OutOfMemoryError error) { // Maybe some data (e.g. photo) is too big to have in memory. But it // should be rare. - Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry: " + name); + Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry."); System.gc(); // TODO: should tell users what happened? return true; @@ -630,99 +592,4 @@ public class VCardComposer { public String getErrorReason() { return mErrorReason; } - - /** - * This static function is to compose vCard for phone own number - */ - public String composeVCardForPhoneOwnNumber(int phonetype, String phoneName, - String phoneNumber, boolean vcardVer21) { - final int vcardType = (vcardVer21 ? - VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8 : - VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8); - final VCardBuilder builder = new VCardBuilder(vcardType); - boolean needCharset = false; - if (!(VCardUtils.containsOnlyPrintableAscii(phoneName))) { - needCharset = true; - } - builder.appendLine(VCardConstants.PROPERTY_FN, phoneName, needCharset, false); - builder.appendLine(VCardConstants.PROPERTY_N, phoneName, needCharset, false); - - if (!TextUtils.isEmpty(phoneNumber)) { - String label = Integer.toString(phonetype); - builder.appendTelLine(phonetype, label, phoneNumber, false); - } - - return builder.toString(); - } - - /** - * Format according to RFC 2445 DATETIME type. - * The format is: ("%Y%m%dT%H%M%SZ"). - */ - private final String toRfc2455Format(final long millSecs) { - Time startDate = new Time(); - startDate.set(millSecs); - String date = startDate.format2445(); - return date + FLAG_TIMEZONE_UTC; - } - - /** - * Try to append the property line for a call history time stamp field if possible. - * Do nothing if the call log type gotton from the database is invalid. - */ - private void tryAppendCallHistoryTimeStampField(final VCardBuilder builder) { - // Extension for call history as defined in - // in the Specification for Ic Mobile Communcation - ver 1.1, - // Oct 2000. This is used to send the details of the call - // history - missed, incoming, outgoing along with date and time - // to the requesting device (For example, transferring phone book - // when connected over bluetooth) - // - // e.g. "X-IRMC-CALL-DATETIME;MISSED:20050320T100000Z" - final int callLogType = mCursor.getInt(CALL_TYPE_COLUMN_INDEX); - final String callLogTypeStr; - switch (callLogType) { - case Calls.INCOMING_TYPE: { - callLogTypeStr = VCARD_PROPERTY_CALLTYPE_INCOMING; - break; - } - case Calls.OUTGOING_TYPE: { - callLogTypeStr = VCARD_PROPERTY_CALLTYPE_OUTGOING; - break; - } - case Calls.MISSED_TYPE: { - callLogTypeStr = VCARD_PROPERTY_CALLTYPE_MISSED; - break; - } - default: { - Log.w(LOG_TAG, "Call log type not correct."); - return; - } - } - - final long dateAsLong = mCursor.getLong(DATE_COLUMN_INDEX); - builder.appendLine(VCARD_PROPERTY_X_TIMESTAMP, - Arrays.asList(callLogTypeStr), toRfc2455Format(dateAsLong)); - } - - private String createOneCallLogEntryInternal() { - final VCardBuilder builder = new VCardBuilder(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); - String name = mCursor.getString(CALLER_NAME_COLUMN_INDEX); - if (TextUtils.isEmpty(name)) { - name = mCursor.getString(NUMBER_COLUMN_INDEX); - } - final boolean needCharset = !(VCardUtils.containsOnlyPrintableAscii(name)); - builder.appendLine(VCardConstants.PROPERTY_FN, name, needCharset, false); - builder.appendLine(VCardConstants.PROPERTY_N, name, needCharset, false); - - final String number = mCursor.getString(NUMBER_COLUMN_INDEX); - final int type = mCursor.getInt(CALLER_NUMBERTYPE_COLUMN_INDEX); - String label = mCursor.getString(CALLER_NUMBERLABEL_COLUMN_INDEX); - if (TextUtils.isEmpty(label)) { - label = Integer.toString(type); - } - builder.appendTelLine(type, label, number, false); - tryAppendCallHistoryTimeStampField(builder); - return builder.toString(); - } } diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 2eebe77..caa3144 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -16,20 +16,14 @@ package android.server.search; -import android.app.ActivityManagerNative; -import android.app.IActivityWatcher; +import com.android.internal.content.PackageMonitor; + import android.app.ISearchManager; -import android.app.ISearchManagerCallback; import android.app.SearchManager; import android.app.SearchableInfo; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.os.Handler; -import android.os.RemoteException; import android.util.Log; import java.util.List; @@ -42,13 +36,11 @@ public class SearchManagerService extends ISearchManager.Stub { // general debugging support private static final String TAG = "SearchManagerService"; - private static final boolean DBG = false; // Context that the service is running in. private final Context mContext; - // This field is initialized in ensureSearchablesCreated(), and then never modified. - // Only accessed by ensureSearchablesCreated() and getSearchables() + // This field is initialized lazily in getSearchables(), and then never modified. private Searchables mSearchables; /** @@ -61,58 +53,28 @@ public class SearchManagerService extends ISearchManager.Stub { mContext = context; } - private synchronized void ensureSearchablesCreated() { - if (mSearchables != null) return; // already created - - mSearchables = new Searchables(mContext); - mSearchables.buildSearchableList(); - - IntentFilter packageFilter = new IntentFilter(); - packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - packageFilter.addDataScheme("package"); - mContext.registerReceiver(mPackageChangedReceiver, packageFilter); - // Register for events related to sdcard installation. - IntentFilter sdFilter = new IntentFilter(); - sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); - sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - mContext.registerReceiver(mPackageChangedReceiver, sdFilter); - } - private synchronized Searchables getSearchables() { - ensureSearchablesCreated(); + if (mSearchables == null) { + mSearchables = new Searchables(mContext); + mSearchables.buildSearchableList(); + new MyPackageMonitor().register(mContext, true); + } return mSearchables; } /** * Refreshes the "searchables" list when packages are added/removed. */ - private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() { + class MyPackageMonitor extends PackageMonitor { @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (Intent.ACTION_PACKAGE_ADDED.equals(action) || - Intent.ACTION_PACKAGE_REMOVED.equals(action) || - Intent.ACTION_PACKAGE_CHANGED.equals(action) || - Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action) || - Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { - if (DBG) Log.d(TAG, "Got " + action); - // Update list of searchable activities - getSearchables().buildSearchableList(); - broadcastSearchablesChanged(); - } + public void onSomePackagesChanged() { + // Update list of searchable activities + getSearchables().buildSearchableList(); + // Inform all listeners that the list of searchables has been updated. + Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + mContext.sendBroadcast(intent); } - }; - - /** - * Informs all listeners that the list of searchables has been updated. - */ - void broadcastSearchablesChanged() { - Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - mContext.sendBroadcast(intent); } // diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 1023036..38ac9b7 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1936,6 +1936,11 @@ public abstract class Layout { public static final int DIR_LEFT_TO_RIGHT = 1; public static final int DIR_RIGHT_TO_LEFT = -1; + + /* package */ static final int DIR_REQUEST_LTR = 1; + /* package */ static final int DIR_REQUEST_RTL = -1; + /* package */ static final int DIR_REQUEST_DEFAULT_LTR = 2; + /* package */ static final int DIR_REQUEST_DEFAULT_RTL = -2; public enum Alignment { ALIGN_NORMAL, diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 6c89f92..600ec7e 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -234,215 +234,9 @@ extends Layout } if (!easy) { - AndroidCharacter.getDirectionalities(chs, chdirs, end - start); - - /* - * Determine primary paragraph direction - */ - - for (int j = start; j < end; j++) { - int d = chdirs[j - start]; - - if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) { - dir = DIR_LEFT_TO_RIGHT; - break; - } - if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { - dir = DIR_RIGHT_TO_LEFT; - break; - } - } - - /* - * XXX Explicit overrides should go here - */ - - /* - * Weak type resolution - */ - - final byte SOR = dir == DIR_LEFT_TO_RIGHT ? - Character.DIRECTIONALITY_LEFT_TO_RIGHT : - Character.DIRECTIONALITY_RIGHT_TO_LEFT; - - // dump(chdirs, n, "initial"); - - // W1 non spacing marks - for (int j = 0; j < n; j++) { - if (chdirs[j] == Character.NON_SPACING_MARK) { - if (j == 0) - chdirs[j] = SOR; - else - chdirs[j] = chdirs[j - 1]; - } - } - - // dump(chdirs, n, "W1"); - - // W2 european numbers - byte cur = SOR; - for (int j = 0; j < n; j++) { - byte d = chdirs[j]; - - if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || - d == Character.DIRECTIONALITY_RIGHT_TO_LEFT || - d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) - cur = d; - else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) { - if (cur == - Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) - chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER; - } - } - - // dump(chdirs, n, "W2"); - - // W3 arabic letters - for (int j = 0; j < n; j++) { - if (chdirs[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) - chdirs[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT; - } - - // dump(chdirs, n, "W3"); - - // W4 single separator between numbers - for (int j = 1; j < n - 1; j++) { - byte d = chdirs[j]; - byte prev = chdirs[j - 1]; - byte next = chdirs[j + 1]; - - if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) { - if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER && - next == Character.DIRECTIONALITY_EUROPEAN_NUMBER) - chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; - } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) { - if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER && - next == Character.DIRECTIONALITY_EUROPEAN_NUMBER) - chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; - if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER && - next == Character.DIRECTIONALITY_ARABIC_NUMBER) - chdirs[j] = Character.DIRECTIONALITY_ARABIC_NUMBER; - } - } - - // dump(chdirs, n, "W4"); - - // W5 european number terminators - boolean adjacent = false; - for (int j = 0; j < n; j++) { - byte d = chdirs[j]; - - if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) - adjacent = true; - else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent) - chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; - else - adjacent = false; - } - - //dump(chdirs, n, "W5"); - - // W5 european number terminators part 2, - // W6 separators and terminators - adjacent = false; - for (int j = n - 1; j >= 0; j--) { - byte d = chdirs[j]; - - if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) - adjacent = true; - else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) { - if (adjacent) - chdirs[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; - else - chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS; - } - else { - adjacent = false; - - if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR || - d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR || - d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR || - d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR) - chdirs[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS; - } - } - - // dump(chdirs, n, "W6"); - - // W7 strong direction of european numbers - cur = SOR; - for (int j = 0; j < n; j++) { - byte d = chdirs[j]; - - if (d == SOR || - d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || - d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) - cur = d; - - if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) - chdirs[j] = cur; - } - - // dump(chdirs, n, "W7"); - - // N1, N2 neutrals - cur = SOR; - for (int j = 0; j < n; j++) { - byte d = chdirs[j]; - - if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || - d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { - cur = d; - } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER || - d == Character.DIRECTIONALITY_ARABIC_NUMBER) { - cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT; - } else { - byte dd = SOR; - int k; - - for (k = j + 1; k < n; k++) { - dd = chdirs[k]; - - if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT || - dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { - break; - } - if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER || - dd == Character.DIRECTIONALITY_ARABIC_NUMBER) { - dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT; - break; - } - } - - for (int y = j; y < k; y++) { - if (dd == cur) - chdirs[y] = cur; - else - chdirs[y] = SOR; - } - - j = k - 1; - } - } - - // dump(chdirs, n, "final"); - - // extra: enforce that all tabs and surrogate characters go the - // primary direction - // TODO: actually do directions right for surrogates - - for (int j = 0; j < n; j++) { - char c = chs[j]; - - if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) { - chdirs[j] = SOR; - } - } - - // extra: enforce that object replacements go to the - // primary direction - // and that none of the underlying characters are treated - // as viable breakpoints + // Ensure that none of the underlying characters are treated + // as viable breakpoints, and that the entire run gets the + // same bidi direction. if (source instanceof Spanned) { Spanned sp = (Spanned) source; @@ -453,12 +247,14 @@ extends Layout int b = sp.getSpanEnd(spans[y]); for (int x = a; x < b; x++) { - chdirs[x - start] = SOR; chs[x - start] = '\uFFFC'; } } } + // XXX put override flags, etc. into chdirs + dir = bidi(dir, chs, chdirs, n, false); + // Do mirroring for right-to-left segments for (int i = 0; i < n; i++) { @@ -810,6 +606,239 @@ extends Layout } } + /** + * Runs the unicode bidi algorithm on the first n chars in chs, returning + * the char dirs in chInfo and the base line direction of the first + * paragraph. + * + * XXX change result from dirs to levels + * + * @param dir the direction flag, either DIR_REQUEST_LTR, + * DIR_REQUEST_RTL, DIR_REQUEST_DEFAULT_LTR, or DIR_REQUEST_DEFAULT_RTL. + * @param chs the text to examine + * @param chInfo on input, if hasInfo is true, override and other flags + * representing out-of-band embedding information. On output, the generated + * dirs of the text. + * @param n the length of the text/information in chs and chInfo + * @param hasInfo true if chInfo has input information, otherwise the + * input data in chInfo is ignored. + * @return the resolved direction level of the first paragraph, either + * DIR_LEFT_TO_RIGHT or DIR_RIGHT_TO_LEFT. + */ + /* package */ static int bidi(int dir, char[] chs, byte[] chInfo, int n, + boolean hasInfo) { + + AndroidCharacter.getDirectionalities(chs, chInfo, n); + + /* + * Determine primary paragraph direction if not specified + */ + if (dir != DIR_REQUEST_LTR && dir != DIR_REQUEST_RTL) { + // set up default + dir = dir >= 0 ? DIR_LEFT_TO_RIGHT : DIR_RIGHT_TO_LEFT; + for (int j = 0; j < n; j++) { + int d = chInfo[j]; + + if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT) { + dir = DIR_LEFT_TO_RIGHT; + break; + } + if (d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { + dir = DIR_RIGHT_TO_LEFT; + break; + } + } + } + + final byte SOR = dir == DIR_LEFT_TO_RIGHT ? + Character.DIRECTIONALITY_LEFT_TO_RIGHT : + Character.DIRECTIONALITY_RIGHT_TO_LEFT; + + /* + * XXX Explicit overrides should go here + */ + + /* + * Weak type resolution + */ + + // dump(chdirs, n, "initial"); + + // W1 non spacing marks + for (int j = 0; j < n; j++) { + if (chInfo[j] == Character.NON_SPACING_MARK) { + if (j == 0) + chInfo[j] = SOR; + else + chInfo[j] = chInfo[j - 1]; + } + } + + // dump(chdirs, n, "W1"); + + // W2 european numbers + byte cur = SOR; + for (int j = 0; j < n; j++) { + byte d = chInfo[j]; + + if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || + d == Character.DIRECTIONALITY_RIGHT_TO_LEFT || + d == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) + cur = d; + else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) { + if (cur == + Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) + chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER; + } + } + + // dump(chdirs, n, "W2"); + + // W3 arabic letters + for (int j = 0; j < n; j++) { + if (chInfo[j] == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) + chInfo[j] = Character.DIRECTIONALITY_RIGHT_TO_LEFT; + } + + // dump(chdirs, n, "W3"); + + // W4 single separator between numbers + for (int j = 1; j < n - 1; j++) { + byte d = chInfo[j]; + byte prev = chInfo[j - 1]; + byte next = chInfo[j + 1]; + + if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR) { + if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER && + next == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; + } else if (d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR) { + if (prev == Character.DIRECTIONALITY_EUROPEAN_NUMBER && + next == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; + if (prev == Character.DIRECTIONALITY_ARABIC_NUMBER && + next == Character.DIRECTIONALITY_ARABIC_NUMBER) + chInfo[j] = Character.DIRECTIONALITY_ARABIC_NUMBER; + } + } + + // dump(chdirs, n, "W4"); + + // W5 european number terminators + boolean adjacent = false; + for (int j = 0; j < n; j++) { + byte d = chInfo[j]; + + if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + adjacent = true; + else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR && adjacent) + chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; + else + adjacent = false; + } + + //dump(chdirs, n, "W5"); + + // W5 european number terminators part 2, + // W6 separators and terminators + adjacent = false; + for (int j = n - 1; j >= 0; j--) { + byte d = chInfo[j]; + + if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + adjacent = true; + else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR) { + if (adjacent) + chInfo[j] = Character.DIRECTIONALITY_EUROPEAN_NUMBER; + else + chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS; + } + else { + adjacent = false; + + if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR || + d == Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR || + d == Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR || + d == Character.DIRECTIONALITY_SEGMENT_SEPARATOR) + chInfo[j] = Character.DIRECTIONALITY_OTHER_NEUTRALS; + } + } + + // dump(chdirs, n, "W6"); + + // W7 strong direction of european numbers + cur = SOR; + for (int j = 0; j < n; j++) { + byte d = chInfo[j]; + + if (d == SOR || + d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || + d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) + cur = d; + + if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER) + chInfo[j] = cur; + } + + // dump(chdirs, n, "W7"); + + // N1, N2 neutrals + cur = SOR; + for (int j = 0; j < n; j++) { + byte d = chInfo[j]; + + if (d == Character.DIRECTIONALITY_LEFT_TO_RIGHT || + d == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { + cur = d; + } else if (d == Character.DIRECTIONALITY_EUROPEAN_NUMBER || + d == Character.DIRECTIONALITY_ARABIC_NUMBER) { + cur = Character.DIRECTIONALITY_RIGHT_TO_LEFT; + } else { + byte dd = SOR; + int k; + + for (k = j + 1; k < n; k++) { + dd = chInfo[k]; + + if (dd == Character.DIRECTIONALITY_LEFT_TO_RIGHT || + dd == Character.DIRECTIONALITY_RIGHT_TO_LEFT) { + break; + } + if (dd == Character.DIRECTIONALITY_EUROPEAN_NUMBER || + dd == Character.DIRECTIONALITY_ARABIC_NUMBER) { + dd = Character.DIRECTIONALITY_RIGHT_TO_LEFT; + break; + } + } + + for (int y = j; y < k; y++) { + if (dd == cur) + chInfo[y] = cur; + else + chInfo[y] = SOR; + } + + j = k - 1; + } + } + + // dump(chdirs, n, "final"); + + // extra: enforce that all tabs and surrogate characters go the + // primary direction + // TODO: actually do directions right for surrogates + + for (int j = 0; j < n; j++) { + char c = chs[j]; + + if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) { + chInfo[j] = SOR; + } + } + + return dir; + } + private static final char FIRST_CJK = '\u2E80'; /** * Returns true if the specified character is one of those specified @@ -1012,12 +1041,12 @@ extends Layout int extra; if (needMultiply) { - // XXX: this looks like it is using the +0.5 and the cast to int - // to do rounding, but this I expect this isn't doing the intended - // thing when spacingmult < 1. An intended extra of, say, -1.2 - // will get 'rounded' to -.7 and then truncated to 0. - extra = (int) ((below - above) * (spacingmult - 1) - + spacingadd + 0.5); + double ex = (below - above) * (spacingmult - 1) + spacingadd; + if (ex >= 0) { + extra = (int)(ex + 0.5); + } else { + extra = -(int)(-ex + 0.5); + } } else { extra = 0; } diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java index cdcb662..df7d0c4 100644 --- a/core/java/android/webkit/PluginManager.java +++ b/core/java/android/webkit/PluginManager.java @@ -165,6 +165,7 @@ public class PluginManager { continue; } +/* temporarily disable signatures checking // check to ensure the plugin is properly signed Signature signatures[] = pkgInfo.signatures; if (signatures == null) { @@ -184,7 +185,7 @@ public class PluginManager { continue; } } - +*/ // determine the type of plugin from the manifest if (serviceInfo.metaData == null) { Log.e(LOGTAG, "The plugin '" + serviceInfo.name + "' has no type defined"); diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index aa9062b..bf63607 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -24,6 +24,7 @@ import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; +import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; @@ -73,7 +74,8 @@ public class CheckedTextView extends TextView implements Checkable { public void toggle() { setChecked(!mChecked); } - + + @ViewDebug.ExportedProperty public boolean isChecked() { return mChecked; } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 98b0976..bf02ad3 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -26,6 +26,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.view.Gravity; +import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; /** @@ -98,6 +99,7 @@ public abstract class CompoundButton extends Button implements Checkable { return super.performClick(); } + @ViewDebug.ExportedProperty public boolean isChecked() { return mChecked; } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 1dcb203..6dc9f78 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -36,6 +36,7 @@ import android.graphics.drawable.shapes.Shape; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; +import android.view.ViewDebug; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -335,6 +336,7 @@ public class ProgressBar extends View { * * @return true if the progress bar is in indeterminate mode */ + @ViewDebug.ExportedProperty public synchronized boolean isIndeterminate() { return mIndeterminate; } @@ -607,6 +609,7 @@ public class ProgressBar extends View { * @see #setMax(int) * @see #getMax() */ + @ViewDebug.ExportedProperty public synchronized int getProgress() { return mIndeterminate ? 0 : mProgress; } @@ -623,6 +626,7 @@ public class ProgressBar extends View { * @see #setMax(int) * @see #getMax() */ + @ViewDebug.ExportedProperty public synchronized int getSecondaryProgress() { return mIndeterminate ? 0 : mSecondaryProgress; } @@ -636,6 +640,7 @@ public class ProgressBar extends View { * @see #getProgress() * @see #getSecondaryProgress() */ + @ViewDebug.ExportedProperty public synchronized int getMax() { return mMax; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 7ba0fa1..cea6d3b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5731,6 +5731,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Convenience for {@link Selection#getSelectionStart}. */ + @ViewDebug.ExportedProperty public int getSelectionStart() { return Selection.getSelectionStart(getText()); } @@ -5738,6 +5739,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Convenience for {@link Selection#getSelectionEnd}. */ + @ViewDebug.ExportedProperty public int getSelectionEnd() { return Selection.getSelectionEnd(getText()); } diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index 906bca1..c2517a8 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -62,6 +62,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { private static final int STATE_PLAYING = 3; private static final int STATE_PAUSED = 4; private static final int STATE_PLAYBACK_COMPLETED = 5; + private static final int STATE_SUSPEND = 6; + private static final int STATE_RESUME = 7; // mCurrentState is a VideoView object's current state. // mTargetState is the state that a method caller intends to reach. @@ -87,6 +89,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { private boolean mCanPause; private boolean mCanSeekBack; private boolean mCanSeekForward; + private int mStateWhenSuspended; //state before calling suspend() public VideoView(Context context) { super(context); @@ -466,7 +469,14 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { public void surfaceCreated(SurfaceHolder holder) { mSurfaceHolder = holder; - openVideo(); + //resume() was called before surfaceCreated() + if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND + && mTargetState == STATE_RESUME) { + mMediaPlayer.setDisplay(mSurfaceHolder); + resume(); + } else { + openVideo(); + } } public void surfaceDestroyed(SurfaceHolder holder) @@ -474,7 +484,6 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { // after we return from this we can't use the surface any more mSurfaceHolder = null; if (mMediaController != null) mMediaController.hide(); - release(true); } }; @@ -567,7 +576,36 @@ public class VideoView extends SurfaceView implements MediaPlayerControl { mTargetState = STATE_PAUSED; } - // cache duration as mDuration for faster access + public void suspend() { + if (isInPlaybackState()) { + if (mMediaPlayer.suspend()) { + mStateWhenSuspended = mCurrentState; + mCurrentState = STATE_SUSPEND; + mTargetState = STATE_SUSPEND; + } else { + Log.w(TAG, "Unable to suspend video"); + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + } + } + } + + public void resume() { + if (mSurfaceHolder == null && mCurrentState == STATE_SUSPEND){ + mTargetState = STATE_RESUME; + return; + } + if (mMediaPlayer != null && mCurrentState == STATE_SUSPEND) { + if (mMediaPlayer.resume()) { + mCurrentState = mStateWhenSuspended; + mTargetState = mStateWhenSuspended; + } else { + Log.w(TAG, "Unable to resume video"); + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + } + } + } + + // cache duration as mDuration for faster access public int getDuration() { if (isInPlaybackState()) { if (mDuration > 0) { diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index bc7dbf4..c5db83f 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -36,7 +36,7 @@ public class PackageHelper { public static final int RECOMMEND_INSTALL_EXTERNAL = 2; public static final int RECOMMEND_FAILED_INSUFFICIENT_STORAGE = -1; public static final int RECOMMEND_FAILED_INVALID_APK = -2; - private static final boolean DEBUG_SD_INSTALL = true; + private static final boolean localLOGV = true; private static final String TAG = "PackageHelper"; public static IMountService getMountService() { @@ -58,7 +58,7 @@ public class PackageHelper { if ((len - (mbLen * 1024 * 1024)) > 0) { mbLen++; } - if (DEBUG_SD_INSTALL) Log.i(TAG, "Size of resource " + mbLen); + if (localLOGV) Log.i(TAG, "Size of resource " + mbLen); try { int rc = mountService.createSecureContainer( @@ -68,7 +68,7 @@ public class PackageHelper { return null; } String cachePath = mountService.getSecureContainerPath(cid); - if (DEBUG_SD_INSTALL) Log.i(TAG, "Created secure container " + cid + + if (localLOGV) Log.i(TAG, "Created secure container " + cid + " at " + cachePath); return cachePath; } catch (RemoteException e) { @@ -93,7 +93,7 @@ public class PackageHelper { public static boolean unMountSdDir(String cid) { try { - int rc = getMountService().unmountSecureContainer(cid, false); + int rc = getMountService().unmountSecureContainer(cid, true); if (rc != StorageResultCode.OperationSucceeded) { Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc); return false; @@ -148,7 +148,8 @@ public class PackageHelper { public static boolean destroySdDir(String cid) { try { - int rc = getMountService().destroySecureContainer(cid, false); + if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid); + int rc = getMountService().destroySecureContainer(cid, true); if (rc != StorageResultCode.OperationSucceeded) { Log.i(TAG, "Failed to destroy container " + cid); return false; diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index f074b80..9713c27 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -104,14 +104,24 @@ public class LockPatternUtils { private static String sLockPatternFilename; private static String sLockPasswordFilename; + DevicePolicyManager getDevicePolicyManager() { + if (mDevicePolicyManager == null) { + mDevicePolicyManager = + (DevicePolicyManager)mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + if (mDevicePolicyManager == null) { + Log.e(TAG, "Can't get DevicePolicyManagerService: is it running?", + new IllegalStateException("Stack trace:")); + } + } + return mDevicePolicyManager; + } /** * @param contentResolver Used to look up and save settings. */ public LockPatternUtils(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - mDevicePolicyManager = - (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE); + mDevicePolicyManager = getDevicePolicyManager(); // Initialize the location of gesture lock file if (sLockPatternFilename == null) { sLockPatternFilename = android.os.Environment.getDataDirectory() @@ -123,7 +133,7 @@ public class LockPatternUtils { } public int getRequestedMinimumPasswordLength() { - return mDevicePolicyManager.getPasswordMinimumLength(null); + return getDevicePolicyManager().getPasswordMinimumLength(null); } /** @@ -133,7 +143,7 @@ public class LockPatternUtils { * @return */ public int getRequestedPasswordMode() { - int policyMode = mDevicePolicyManager.getPasswordQuality(null); + int policyMode = getDevicePolicyManager().getPasswordQuality(null); switch (policyMode) { case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: return MODE_PASSWORD; @@ -151,11 +161,11 @@ public class LockPatternUtils { * @return */ public void reportFailedPasswordAttempt() { - mDevicePolicyManager.reportFailedPasswordAttempt(); + getDevicePolicyManager().reportFailedPasswordAttempt(); } public void reportSuccessfulPasswordAttempt() { - mDevicePolicyManager.reportSuccessfulPasswordAttempt(); + getDevicePolicyManager().reportSuccessfulPasswordAttempt(); } public void setActivePasswordState(int mode, int length) { @@ -171,7 +181,7 @@ public class LockPatternUtils { policyMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC; break; } - mDevicePolicyManager.setActivePasswordState(policyMode, length); + getDevicePolicyManager().setActivePasswordState(policyMode, length); } /** @@ -279,7 +289,7 @@ public class LockPatternUtils { saveLockPattern(null); setLong(PASSWORD_TYPE_KEY, MODE_PATTERN); } - + /** * Save a lock pattern. * @param pattern The new pattern to save. @@ -334,7 +344,7 @@ public class LockPatternUtils { hasNonDigit = true; } } - + // First check if it is sufficient. switch (reqMode) { case MODE_PASSWORD: { @@ -342,19 +352,19 @@ public class LockPatternUtils { return MODE_UNSPECIFIED; } } break; - + case MODE_PIN: case MODE_PATTERN: { // Whatever we have is acceptable; we may need to promote the // mode later. } break; - + default: // If it isn't a mode we specifically know, then fail fast. Log.w(TAG, "adjustPasswordMode: unknown mode " + reqMode); return MODE_UNSPECIFIED; } - + // Do we need to promote? if (hasNonDigit) { if (reqMode < MODE_PASSWORD) { @@ -366,10 +376,10 @@ public class LockPatternUtils { reqMode = MODE_PIN; } } - + return reqMode; } - + /** * Save a lock password. Does not ensure that the pattern is as good * as the requested mode, but will adjust the mode to be as good as the diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index a82a21e..5afa034 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -1466,19 +1466,25 @@ static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { value = bag->map.value; jstring str = NULL; - + // Take care of resolving the found resource to its final value. ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); if (value.dataType == Res_value::TYPE_STRING) { - const char16_t* str16 = res.getTableStringBlock(block)->stringAt(value.data, &strLen); - str = env->NewString(str16, strLen); - if (str == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); - res.unlockBag(startOfBag); - return NULL; + const ResStringPool* pool = res.getTableStringBlock(block); + const char* str8 = pool->string8At(value.data, &strLen); + if (str8 != NULL) { + str = env->NewStringUTF(str8); + } else { + const char16_t* str16 = pool->stringAt(value.data, &strLen); + str = env->NewString(str16, strLen); + if (str == NULL) { + doThrow(env, "java/lang/OutOfMemoryError"); + res.unlockBag(startOfBag); + return NULL; + } } } - + env->SetObjectArrayElement(array, i, str); } res.unlockBag(startOfBag); diff --git a/core/jni/android_util_StringBlock.cpp b/core/jni/android_util_StringBlock.cpp index ffb271c..641fbce 100644 --- a/core/jni/android_util_StringBlock.cpp +++ b/core/jni/android_util_StringBlock.cpp @@ -89,6 +89,11 @@ static jstring android_content_StringBlock_nativeGetString(JNIEnv* env, jobject } size_t len; + const char* str8 = osb->string8At(idx, &len); + if (str8 != NULL) { + return env->NewStringUTF(str8); + } + const char16_t* str = osb->stringAt(idx, &len); if (str == NULL) { doThrow(env, "java/lang/IndexOutOfBoundsException"); diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png Binary files differnew file mode 100644 index 0000000..c6503c7 --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled.9.png diff --git a/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png Binary files differnew file mode 100644 index 0000000..152de8b --- /dev/null +++ b/core/res/res/drawable-hdpi/btn_dropdown_disabled_focused.9.png diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png Binary files differdeleted file mode 100644 index af80855..0000000 --- a/core/res/res/drawable-hdpi/spinnerbox_arrow_first.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png Binary files differdeleted file mode 100644 index dc47275..0000000 --- a/core/res/res/drawable-hdpi/spinnerbox_arrow_last.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png Binary files differdeleted file mode 100644 index 007f279..0000000 --- a/core/res/res/drawable-hdpi/spinnerbox_arrow_middle.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png b/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png Binary files differdeleted file mode 100644 index 24592a3..0000000 --- a/core/res/res/drawable-hdpi/spinnerbox_arrow_single.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png b/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png Binary files differnew file mode 100644 index 0000000..f7464c7 --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_dropdown_disabled.9.png diff --git a/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png b/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png Binary files differnew file mode 100644 index 0000000..ffe219f --- /dev/null +++ b/core/res/res/drawable-mdpi/btn_dropdown_disabled_focused.9.png diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png Binary files differdeleted file mode 100644 index d8e268d..0000000 --- a/core/res/res/drawable-mdpi/spinnerbox_arrow_first.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png Binary files differdeleted file mode 100644 index 087e650..0000000 --- a/core/res/res/drawable-mdpi/spinnerbox_arrow_last.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png Binary files differdeleted file mode 100644 index f1f2ff5..0000000 --- a/core/res/res/drawable-mdpi/spinnerbox_arrow_middle.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png b/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png Binary files differdeleted file mode 100644 index f537b3b..0000000 --- a/core/res/res/drawable-mdpi/spinnerbox_arrow_single.9.png +++ /dev/null diff --git a/core/res/res/drawable/btn_circle.xml b/core/res/res/drawable/btn_circle.xml index 9208010..243f506 100644 --- a/core/res/res/drawable/btn_circle.xml +++ b/core/res/res/drawable/btn_circle.xml @@ -19,6 +19,8 @@ android:drawable="@drawable/btn_circle_normal" /> <item android:state_window_focused="false" android:state_enabled="false" android:drawable="@drawable/btn_circle_disable" /> + <item android:state_pressed="true" android:state_enabled="false" + android:drawable="@drawable/btn_circle_disable" /> <item android:state_pressed="true" android:drawable="@drawable/btn_circle_pressed" /> <item android:state_focused="true" android:state_enabled="true" diff --git a/core/res/res/drawable/btn_dropdown.xml b/core/res/res/drawable/btn_dropdown.xml index 8ec8ece..34a0504 100644 --- a/core/res/res/drawable/btn_dropdown.xml +++ b/core/res/res/drawable/btn_dropdown.xml @@ -15,10 +15,24 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_window_focused="false" android:drawable="@drawable/btn_dropdown_normal" /> - <item android:state_pressed="true" android:drawable="@drawable/btn_dropdown_pressed" /> - <item android:state_focused="true" android:state_pressed="false" + <item + android:state_window_focused="false" android:state_enabled="true" + android:drawable="@drawable/btn_dropdown_normal" /> + <item + android:state_window_focused="false" android:state_enabled="false" + android:drawable="@drawable/btn_dropdown_disabled" /> + <item + android:state_pressed="true" + android:drawable="@drawable/btn_dropdown_pressed" /> + <item + android:state_focused="true" android:state_enabled="true" android:drawable="@drawable/btn_dropdown_selected" /> - <item android:drawable="@drawable/btn_dropdown_normal" /> + <item + android:state_enabled="true" + android:drawable="@drawable/btn_dropdown_normal" /> + <item + android:state_focused="true" + android:drawable="@drawable/btn_dropdown_disabled_focused" /> + <item + android:drawable="@drawable/btn_dropdown_disabled" /> </selector> - diff --git a/core/res/res/drawable/spinnerbox_arrows.xml b/core/res/res/drawable/spinnerbox_arrows.xml deleted file mode 100644 index 276a0f0..0000000 --- a/core/res/res/drawable/spinnerbox_arrows.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/drawable/spinnerbox_arrows.xml -** -** Copyright 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. -*/ ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_single="true" android:drawable="@drawable/spinnerbox_arrow_single" /> - <item android:state_first="true" android:drawable="@drawable/spinnerbox_arrow_first" /> - <item android:state_last="true" android:drawable="@drawable/spinnerbox_arrow_last" /> - <item android:state_middle="true" android:drawable="@drawable/spinnerbox_arrow_middle" /> - <item android:state_pressed="true" android:drawable="@drawable/spinnerbox_arrow_single" /> -</selector> diff --git a/core/res/res/layout/status_bar_expanded.xml b/core/res/res/layout/status_bar_expanded.xml index a0cd11d..30138a7 100644 --- a/core/res/res/layout/status_bar_expanded.xml +++ b/core/res/res/layout/status_bar_expanded.xml @@ -20,9 +20,9 @@ <com.android.server.status.ExpandedView xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:background="@drawable/status_bar_background" android:focusable="true" - android:descendantFocusability="afterDescendants"> + android:descendantFocusability="afterDescendants" + > <LinearLayout android:layout_width="match_parent" @@ -79,19 +79,18 @@ <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_weight="1" > <ScrollView android:id="@+id/scroll" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" + android:layout_height="match_parent" android:fadingEdge="none" > <com.android.server.status.NotificationLinearLayout android:id="@+id/notificationLinearLayout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_weight="1" android:orientation="vertical" > @@ -136,7 +135,7 @@ <ImageView android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:src="@drawable/title_bar_shadow" android:scaleType="fitXY" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f1501ae..5d2d272 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -779,18 +779,18 @@ applications can use this to read phone owner data.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_readCalendar">read calendar data</string> + <string name="permlab_readCalendar">read calendar events</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_readCalendar">Allows an application to read all of the calendar events stored on your phone. Malicious applications can use this to send your calendar events to other people.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_writeCalendar">write calendar data</string> + <string name="permlab_writeCalendar">add or modify calendar events and send email to guests</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_writeCalendar">Allows an application to modify the - calendar events stored on your phone. Malicious - applications can use this to erase or modify your calendar data.</string> + <string name="permdesc_writeCalendar">Allows an application to add or change the + events on your calendar, which may send email to guests. Malicious applications can use this + to erase or modify your calendar events or to send email to guests.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_accessMockLocation">mock location sources for testing</string> diff --git a/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java new file mode 100644 index 0000000..ccd0dae --- /dev/null +++ b/core/tests/coretests/src/android/text/StaticLayoutBidiTest.java @@ -0,0 +1,106 @@ +/* + * 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. + */ + +package android.text; + +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import junit.framework.TestCase; + +/** + * Tests StaticLayout bidi implementation. + */ +public class StaticLayoutBidiTest extends TestCase { + + public static final int REQ_DL = 2; // Layout.DIR_REQUEST_DEFAULT_LTR; + public static final int REQ_DR = -2; // Layout.DIR_REQUEST_DEFAULT_RTL; + public static final int REQ_L = 1; // Layout.DIR_REQUEST_LTR; + public static final int REQ_R = -1; // Layout.DIR_REQUEST_RTL; + public static final int L = Layout.DIR_LEFT_TO_RIGHT; + public static final int R = Layout.DIR_RIGHT_TO_LEFT; + + public static final String SP = " "; + public static final String ALEF = "\u05d0"; + public static final String BET = "\u05d1"; + public static final String GIMEL = "\u05d2"; + public static final String DALET = "\u05d3"; + + @SmallTest + public void testAllLtr() { + expectBidi(REQ_DL, "a test", "000000", L); + } + + @SmallTest + public void testLtrRtl() { + expectBidi(REQ_DL, "abc " + ALEF + BET + GIMEL, "0000111", L); + } + + @SmallTest + public void testAllRtl() { + expectBidi(REQ_DL, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", R); + } + + @SmallTest + public void testRtlLtr() { + expectBidi(REQ_DL, ALEF + BET + GIMEL + " abc", "1111000", R); + } + + @SmallTest + public void testRAllLtr() { + expectBidi(REQ_R, "a test", "000000", R); + } + + @SmallTest + public void testRLtrRtl() { + expectBidi(REQ_R, "abc " + ALEF + BET + GIMEL, "0001111", R); + } + + @SmallTest + public void testLAllRtl() { + expectBidi(REQ_L, ALEF + SP + ALEF + BET + GIMEL + DALET, "111111", L); + } + + @SmallTest + public void testLRtlLtr() { + expectBidi(REQ_L, ALEF + BET + GIMEL + " abc", "1110000", L); + } + + private void expectBidi(int dir, String text, + String expectedLevels, int expectedDir) { + char[] chs = text.toCharArray(); + int n = chs.length; + byte[] chInfo = new byte[n]; + + int resultDir = StaticLayout.bidi(dir, chs, chInfo, n, false); + + { + StringBuilder sb = new StringBuilder("xdirs:"); + for (int i = 0; i < n; ++i) { + sb.append(" ").append(String.valueOf(chInfo[i])); + } + Log.i("BIDI", sb.toString()); + } + + char[] resultLevelChars = new char[n]; + for (int i = 0; i < n; ++i) { + resultLevelChars[i] = (char)('0' + chInfo[i]); + } + String resultLevels = new String(resultLevelChars); + assertEquals("direction", expectedDir, resultDir); + assertEquals("levels", expectedLevels, resultLevels); + } +} diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java index 1e4db3d..7511ec1 100644 --- a/core/tests/coretests/src/android/text/StaticLayoutTest.java +++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java @@ -213,13 +213,13 @@ public class StaticLayoutTest extends TestCase { } public int scale(float height) { - int altVal = (int)(height * sMult + sAdd + 0.5); // existing impl + int altVal = (int)(height * sMult + sAdd + 0.5); int rndVal = Math.round(height * sMult + sAdd); if (altVal != rndVal) { Log.i("Scale", "expected scale: " + rndVal + " != returned scale: " + altVal); } - return altVal; // existing implementation + return rndVal; } } diff --git a/include/media/stagefright/AudioPlayer.h b/include/media/stagefright/AudioPlayer.h index 8e5f05f..ea15a5c 100644 --- a/include/media/stagefright/AudioPlayer.h +++ b/include/media/stagefright/AudioPlayer.h @@ -61,7 +61,7 @@ public: status_t seekTo(int64_t time_us); bool isSeeking(); - bool reachedEOS(); + bool reachedEOS(status_t *finalStatus); private: sp<MediaSource> mSource; @@ -81,6 +81,7 @@ private: bool mSeeking; bool mReachedEOS; + status_t mFinalStatus; int64_t mSeekTimeUs; bool mStarted; diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h index 974075d..24c2f46 100644 --- a/include/media/stagefright/OMXCodec.h +++ b/include/media/stagefright/OMXCodec.h @@ -132,6 +132,7 @@ private: PortStatus mPortStatus[2]; bool mInitialBufferSubmit; bool mSignalledEOS; + status_t mFinalStatus; bool mNoMoreOutputData; bool mOutputPortSettingsHaveChanged; int64_t mSeekTimeUs; diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index 13ea27e..cd657e8 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -447,6 +447,8 @@ public: } const char16_t* stringAt(size_t idx, size_t* outLen) const; + const char* string8At(size_t idx, size_t* outLen) const; + const ResStringPool_span* styleAt(const ResStringPool_ref& ref) const; const ResStringPool_span* styleAt(size_t idx) const; diff --git a/libs/audioflinger/AudioPolicyManagerBase.cpp b/libs/audioflinger/AudioPolicyManagerBase.cpp index 096aa73..42b6508 100644 --- a/libs/audioflinger/AudioPolicyManagerBase.cpp +++ b/libs/audioflinger/AudioPolicyManagerBase.cpp @@ -295,13 +295,31 @@ void AudioPolicyManagerBase::setPhoneState(int state) if (oldState == AudioSystem::MODE_IN_CALL && newDevice == 0) { newDevice = hwOutputDesc->device(); } + + // when changing from ring tone to in call mode, mute the ringing tone + // immediately and delay the route change to avoid sending the ring tone + // tail into the earpiece or headset. + int delayMs = 0; + if (state == AudioSystem::MODE_IN_CALL && oldState == AudioSystem::MODE_RINGTONE) { + // delay the device change command by twice the output latency to have some margin + // and be sure that audio buffers not yet affected by the mute are out when + // we actually apply the route change + delayMs = hwOutputDesc->mLatency*2; + setStreamMute(AudioSystem::RING, true, mHardwareOutput); + } + // change routing is necessary - setOutputDevice(mHardwareOutput, newDevice, force); + setOutputDevice(mHardwareOutput, newDevice, force, delayMs); // if entering in call state, handle special case of active streams // pertaining to sonification strategy see handleIncallSonification() if (state == AudioSystem::MODE_IN_CALL) { LOGV("setPhoneState() in call state management: new state is %d", state); + // unmute the ringing tone after a sufficient delay if it was muted before + // setting output device above + if (oldState == AudioSystem::MODE_RINGTONE) { + setStreamMute(AudioSystem::RING, false, mHardwareOutput, MUTE_TIME_MS); + } for (int stream = 0; stream < AudioSystem::NUM_STREAM_TYPES; stream++) { handleIncallSonification(stream, true, true); } @@ -1207,10 +1225,10 @@ status_t AudioPolicyManagerBase::handleA2dpDisconnection(AudioSystem::audio_devi return INVALID_OPERATION; } - // mute media during 2 seconds to avoid outputing sound on hardware output while music stream + // mute media strategy to avoid outputting sound on hardware output while music stream // is switched from A2DP output and before music is paused by music application setStrategyMute(STRATEGY_MEDIA, true, mHardwareOutput); - setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, 2000); + setStrategyMute(STRATEGY_MEDIA, false, mHardwareOutput, MUTE_TIME_MS); if (!a2dpUsedForSonification()) { // unmute music on A2DP output if a notification or ringtone is playing diff --git a/libs/surfaceflinger/Android.mk b/libs/surfaceflinger/Android.mk index 395a937..86eb78d 100644 --- a/libs/surfaceflinger/Android.mk +++ b/libs/surfaceflinger/Android.mk @@ -35,7 +35,6 @@ LOCAL_SHARED_LIBRARIES := \ libpixelflinger \ libhardware \ libutils \ - libskia \ libEGL \ libGLESv1_CM \ libbinder \ diff --git a/libs/surfaceflinger/LayerBase.h b/libs/surfaceflinger/LayerBase.h index d1bbd04..6aacd82 100644 --- a/libs/surfaceflinger/LayerBase.h +++ b/libs/surfaceflinger/LayerBase.h @@ -22,6 +22,7 @@ #include <EGL/egl.h> #include <EGL/eglext.h> +#include <GLES/gl.h> #include <utils/RefBase.h> diff --git a/libs/surfaceflinger/SurfaceFlinger.cpp b/libs/surfaceflinger/SurfaceFlinger.cpp index 2d6152e..b408779 100644 --- a/libs/surfaceflinger/SurfaceFlinger.cpp +++ b/libs/surfaceflinger/SurfaceFlinger.cpp @@ -1496,8 +1496,8 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args) layer->needsBlending(), layer->needsDithering(), layer->contentDirty, s.alpha, s.flags, - s.transform[0], s.transform[1], - s.transform[2], s.transform[3]); + s.transform[0][0], s.transform[0][1], + s.transform[1][0], s.transform[1][1]); result.append(buffer); buffer[0] = 0; /*** LayerBaseClient ***/ @@ -1833,27 +1833,25 @@ void GraphicPlane::setDisplayHardware(DisplayHardware *hw) status_t GraphicPlane::orientationToTransfrom( int orientation, int w, int h, Transform* tr) -{ - float a, b, c, d, x, y; +{ + uint32_t flags = 0; switch (orientation) { case ISurfaceComposer::eOrientationDefault: - // make sure the default orientation is optimal - tr->reset(); - return NO_ERROR; + flags = Transform::ROT_0; + break; case ISurfaceComposer::eOrientation90: - a=0; b=-1; c=1; d=0; x=w; y=0; + flags = Transform::ROT_90; break; case ISurfaceComposer::eOrientation180: - a=-1; b=0; c=0; d=-1; x=w; y=h; + flags = Transform::ROT_180; break; case ISurfaceComposer::eOrientation270: - a=0; b=1; c=-1; d=0; x=0; y=h; + flags = Transform::ROT_270; break; default: return BAD_VALUE; } - tr->set(a, b, c, d); - tr->set(x, y); + tr->set(flags, w, h); return NO_ERROR; } @@ -1869,24 +1867,13 @@ status_t GraphicPlane::setOrientation(int orientation) mHeight = int(h); Transform orientationTransform; - if (UNLIKELY(orientation == 42)) { - float a, b, c, d, x, y; - const float r = (3.14159265f / 180.0f) * 42.0f; - const float si = sinf(r); - const float co = cosf(r); - a=co; b=-si; c=si; d=co; - x = si*(h*0.5f) + (1-co)*(w*0.5f); - y =-si*(w*0.5f) + (1-co)*(h*0.5f); - orientationTransform.set(a, b, c, d); - orientationTransform.set(x, y); - } else { - GraphicPlane::orientationToTransfrom(orientation, w, h, - &orientationTransform); - if (orientation & ISurfaceComposer::eOrientationSwapMask) { - mWidth = int(h); - mHeight = int(w); - } + GraphicPlane::orientationToTransfrom(orientation, w, h, + &orientationTransform); + if (orientation & ISurfaceComposer::eOrientationSwapMask) { + mWidth = int(h); + mHeight = int(w); } + mOrientation = orientation; mGlobalTransform = mDisplayTransform * orientationTransform; return NO_ERROR; diff --git a/libs/surfaceflinger/Transform.cpp b/libs/surfaceflinger/Transform.cpp index ab6f7ba..b2d5856 100644 --- a/libs/surfaceflinger/Transform.cpp +++ b/libs/surfaceflinger/Transform.cpp @@ -14,180 +14,257 @@ * limitations under the License. */ -#include <ui/Region.h> +#include <math.h> -#include <private/pixelflinger/ggl_fixed.h> +#include <cutils/compiler.h> +#include <utils/String8.h> +#include <ui/Region.h> #include "Transform.h" // --------------------------------------------------------------------------- -#define LIKELY( exp ) (__builtin_expect( (exp) != 0, true )) -#define UNLIKELY( exp ) (__builtin_expect( (exp) != 0, false )) +namespace android { // --------------------------------------------------------------------------- -namespace android { +template <typename T> inline T min(T a, T b) { + return a<b ? a : b; +} +template <typename T> inline T min(T a, T b, T c) { + return min(a, min(b, c)); +} +template <typename T> inline T min(T a, T b, T c, T d) { + return min(a, b, min(c, d)); +} + +template <typename T> inline T max(T a, T b) { + return a>b ? a : b; +} +template <typename T> inline T max(T a, T b, T c) { + return max(a, max(b, c)); +} +template <typename T> inline T max(T a, T b, T c, T d) { + return max(a, b, max(c, d)); +} // --------------------------------------------------------------------------- -Transform::Transform() - : mType(0) -{ - mTransform.reset(); +Transform::Transform() { + reset(); } Transform::Transform(const Transform& other) - : mTransform(other.mTransform), mType(other.mType) -{ + : mMatrix(other.mMatrix), mType(other.mType) { } -Transform::Transform(int32_t flags) { - mTransform.reset(); - int sx = (flags & FLIP_H) ? -1 : 1; - int sy = (flags & FLIP_V) ? -1 : 1; - if (flags & ROT_90) { - this->set(0, -sy, sx, 0); - } else { - this->set(sx, 0, 0, sy); - } +Transform::Transform(uint32_t orientation) { + set(orientation, 0, 0); } Transform::~Transform() { } + +bool Transform::absIsOne(float f) { + return fabs(f) == 1.0f; +} + +bool Transform::isZero(float f) { + return fabs(f) == 0.0f; +} + +bool Transform::absEqual(float a, float b) { + return fabs(a) == fabs(b); +} + Transform Transform::operator * (const Transform& rhs) const { - if (LIKELY(mType == 0)) + if (CC_LIKELY(mType == IDENTITY)) return rhs; Transform r(*this); - r.mTransform.preConcat(rhs.mTransform); + if (rhs.mType == IDENTITY) + return r; + + // TODO: we could use mType to optimize the matrix multiply + const mat33& A(mMatrix); + const mat33& B(rhs.mMatrix); + mat33& D(r.mMatrix); + for (int i=0 ; i<3 ; i++) { + const float v0 = A[0][i]; + const float v1 = A[1][i]; + const float v2 = A[2][i]; + D[0][i] = v0*B[0][0] + v1*B[0][1] + v2*B[0][2]; + D[1][i] = v0*B[1][0] + v1*B[1][1] + v2*B[1][2]; + D[2][i] = v0*B[2][0] + v1*B[2][1] + v2*B[2][2]; + } r.mType |= rhs.mType; - return r; -} -float Transform::operator [] (int i) const -{ - float r = 0; - switch(i) { - case 0: r = SkScalarToFloat( mTransform[SkMatrix::kMScaleX] ); break; - case 1: r = SkScalarToFloat( mTransform[SkMatrix::kMSkewX] ); break; - case 2: r = SkScalarToFloat( mTransform[SkMatrix::kMSkewY] ); break; - case 3: r = SkScalarToFloat( mTransform[SkMatrix::kMScaleY] ); break; - } + // TODO: we could recompute this value from r and rhs + r.mType &= 0xFF; + r.mType |= UNKNOWN_TYPE; return r; } -uint8_t Transform::type() const -{ - if (UNLIKELY(mType & 0x80000000)) { - mType = mTransform.getType(); - } - return uint8_t(mType & 0xFF); +float const* Transform::operator [] (int i) const { + return mMatrix[i].v; } bool Transform::transformed() const { - return type() > SkMatrix::kTranslate_Mask; + return type() > TRANSLATE; } int Transform::tx() const { - return SkScalarRound( mTransform[SkMatrix::kMTransX] ); + return floorf(mMatrix[2][0] + 0.5f); } int Transform::ty() const { - return SkScalarRound( mTransform[SkMatrix::kMTransY] ); + return floorf(mMatrix[2][1] + 0.5f); } void Transform::reset() { - mTransform.reset(); - mType = 0; + mType = IDENTITY; + for(int i=0 ; i<3 ; i++) { + vec3& v(mMatrix[i]); + for (int j=0 ; j<3 ; j++) + v[j] = ((i==j) ? 1.0f : 0.0f); + } } -void Transform::set( float xx, float xy, - float yx, float yy) +void Transform::set(float tx, float ty) { - mTransform.set(SkMatrix::kMScaleX, SkFloatToScalar(xx)); - mTransform.set(SkMatrix::kMSkewX, SkFloatToScalar(xy)); - mTransform.set(SkMatrix::kMSkewY, SkFloatToScalar(yx)); - mTransform.set(SkMatrix::kMScaleY, SkFloatToScalar(yy)); - mType |= 0x80000000; + mMatrix[2][0] = tx; + mMatrix[2][1] = ty; + mMatrix[2][2] = 1.0f; + + if (isZero(tx) && isZero(ty)) { + mType &= ~TRANSLATE; + } else { + mType |= TRANSLATE; + } } -void Transform::set(float radian, float x, float y) -{ - float r00 = cosf(radian); float r01 = -sinf(radian); - float r10 = sinf(radian); float r11 = cosf(radian); - mTransform.set(SkMatrix::kMScaleX, SkFloatToScalar(r00)); - mTransform.set(SkMatrix::kMSkewX, SkFloatToScalar(r01)); - mTransform.set(SkMatrix::kMSkewY, SkFloatToScalar(r10)); - mTransform.set(SkMatrix::kMScaleY, SkFloatToScalar(r11)); - mTransform.set(SkMatrix::kMTransX, SkIntToScalar(x - r00*x - r01*y)); - mTransform.set(SkMatrix::kMTransY, SkIntToScalar(y - r10*x - r11*y)); - mType |= 0x80000000 | SkMatrix::kTranslate_Mask; -} - -void Transform::scale(float s, float x, float y) +void Transform::set(float a, float b, float c, float d) { - mTransform.postScale(s, s, x, y); - mType |= 0x80000000; + mat33& M(mMatrix); + M[0][0] = a; M[1][0] = b; + M[0][1] = c; M[1][1] = d; + M[0][2] = 0; M[1][2] = 0; + mType = UNKNOWN_TYPE; } -void Transform::set(int tx, int ty) +void Transform::set(uint32_t flags, float w, float h) { - if (tx | ty) { - mTransform.set(SkMatrix::kMTransX, SkIntToScalar(tx)); - mTransform.set(SkMatrix::kMTransY, SkIntToScalar(ty)); - mType |= SkMatrix::kTranslate_Mask; + mType = flags << 8; + float sx = (flags & FLIP_H) ? -1 : 1; + float sy = (flags & FLIP_V) ? -1 : 1; + float a=0, b=0, c=0, d=0, x=0, y=0; + int xmask = 0; + + // computation of x,y + // x y + // 0 0 0 + // w 0 ROT90 + // w h FLIPH|FLIPV + // 0 h FLIPH|FLIPV|ROT90 + + if (flags & ROT_90) { + mType |= ROTATE; + b = -sy; + c = sx; + xmask = 1; } else { - mTransform.set(SkMatrix::kMTransX, 0); - mTransform.set(SkMatrix::kMTransY, 0); - mType &= ~SkMatrix::kTranslate_Mask; + a = sx; + d = sy; } + + if (flags & FLIP_H) { + mType ^= SCALE; + xmask ^= 1; + } + + if (flags & FLIP_V) { + mType ^= SCALE; + y = h; + } + + if ((flags & ROT_180) == ROT_180) { + mType |= ROTATE; + } + + if (xmask) { + x = w; + } + + if (!isZero(x) || !isZero(y)) { + mType |= TRANSLATE; + } + + mat33& M(mMatrix); + M[0][0] = a; M[1][0] = b; M[2][0] = x; + M[0][1] = c; M[1][1] = d; M[2][1] = y; + M[0][2] = 0; M[1][2] = 0; M[2][2] = 1; +} + +Transform::vec2 Transform::transform(const vec2& v) const { + vec2 r; + const mat33& M(mMatrix); + r[0] = M[0][0]*v[0] + M[1][0]*v[1] + M[2][0]; + r[1] = M[0][1]*v[0] + M[1][1]*v[1] + M[2][1]; + return r; } -void Transform::transform(GLfixed* point, int x, int y) const +Transform::vec3 Transform::transform(const vec3& v) const { + vec3 r; + const mat33& M(mMatrix); + r[0] = M[0][0]*v[0] + M[1][0]*v[1] + M[2][0]*v[2]; + r[1] = M[0][1]*v[0] + M[1][1]*v[1] + M[2][1]*v[2]; + r[2] = M[0][2]*v[0] + M[1][2]*v[1] + M[2][2]*v[2]; + return r; +} + +void Transform::transform(fixed1616* point, int x, int y) const { - SkPoint s; - mTransform.mapXY(SkIntToScalar(x), SkIntToScalar(y), &s); - point[0] = SkScalarToFixed(s.fX); - point[1] = SkScalarToFixed(s.fY); + const float toFixed = 65536.0f; + const mat33& M(mMatrix); + vec2 v(x, y); + v = transform(v); + point[0] = v[0] * toFixed; + point[1] = v[1] * toFixed; } Rect Transform::makeBounds(int w, int h) const { - Rect r; - SkRect d, s; - s.set(0, 0, SkIntToScalar(w), SkIntToScalar(h)); - mTransform.mapRect(&d, s); - r.left = SkScalarRound( d.fLeft ); - r.top = SkScalarRound( d.fTop ); - r.right = SkScalarRound( d.fRight ); - r.bottom = SkScalarRound( d.fBottom ); - return r; + return transform( Rect(w, h) ); } Rect Transform::transform(const Rect& bounds) const { Rect r; - SkRect d, s; - s.set( SkIntToScalar( bounds.left ), - SkIntToScalar( bounds.top ), - SkIntToScalar( bounds.right ), - SkIntToScalar( bounds.bottom )); - mTransform.mapRect(&d, s); - r.left = SkScalarRound( d.fLeft ); - r.top = SkScalarRound( d.fTop ); - r.right = SkScalarRound( d.fRight ); - r.bottom = SkScalarRound( d.fBottom ); + vec2 lt( bounds.left, bounds.top ); + vec2 rt( bounds.right, bounds.top ); + vec2 lb( bounds.left, bounds.bottom ); + vec2 rb( bounds.right, bounds.bottom ); + + lt = transform(lt); + rt = transform(rt); + lb = transform(lb); + rb = transform(rb); + + r.left = floorf(min(lt[0], rt[0], lb[0], rb[0]) + 0.5f); + r.top = floorf(min(lt[1], rt[1], lb[1], rb[1]) + 0.5f); + r.right = floorf(max(lt[0], rt[0], lb[0], rb[0]) + 0.5f); + r.bottom = floorf(max(lt[1], rt[1], lb[1], rb[1]) + 0.5f); + return r; } Region Transform::transform(const Region& reg) const { Region out; - if (UNLIKELY(transformed())) { - if (LIKELY(preserveRects())) { + if (CC_UNLIKELY(transformed())) { + if (CC_LIKELY(preserveRects())) { Region::const_iterator it = reg.begin(); Region::const_iterator const end = reg.end(); while (it != end) { @@ -202,31 +279,108 @@ Region Transform::transform(const Region& reg) const return out; } -int32_t Transform::getOrientation() const +uint32_t Transform::type() const { - uint32_t flags = 0; - if (UNLIKELY(transformed())) { - SkScalar a = mTransform[SkMatrix::kMScaleX]; - SkScalar b = mTransform[SkMatrix::kMSkewX]; - SkScalar c = mTransform[SkMatrix::kMSkewY]; - SkScalar d = mTransform[SkMatrix::kMScaleY]; - if (b==0 && c==0 && a && d) { - if (a<0) flags |= FLIP_H; - if (d<0) flags |= FLIP_V; - } else if (b && c && a==0 && d==0) { - flags |= ROT_90; - if (b>0) flags |= FLIP_H; - if (c<0) flags |= FLIP_V; + if (mType & UNKNOWN_TYPE) { + // recompute what this transform is + + const mat33& M(mMatrix); + const float a = M[0][0]; + const float b = M[1][0]; + const float c = M[0][1]; + const float d = M[1][1]; + const float x = M[2][0]; + const float y = M[2][1]; + + bool scale = false; + uint32_t flags = ROT_0; + if (isZero(b) && isZero(c)) { + if (absEqual(a, d)) { + if (a<0) flags |= FLIP_H; + if (d<0) flags |= FLIP_V; + if (!absIsOne(a) || !absIsOne(d)) { + scale = true; + } + } else { + flags = ROT_INVALID; + } + } else if (isZero(a) && isZero(d)) { + if (absEqual(b, c)) { + flags |= ROT_90; + if (b>0) flags |= FLIP_H; + if (c<0) flags |= FLIP_V; + if (!absIsOne(b) || !absIsOne(c)) { + scale = true; + } + } else { + flags = ROT_INVALID; + } + } else { + flags = ROT_INVALID; + } + + mType = flags << 8; + if (flags & ROT_INVALID) { + mType |= UNKNOWN; } else { - flags = 0x80000000; + if ((flags & ROT_90) || ((flags & ROT_180) == ROT_180)) + mType |= ROTATE; + if (flags & FLIP_H) + mType ^= SCALE; + if (flags & FLIP_V) + mType ^= SCALE; + if (scale) + mType |= SCALE; } + + if (!isZero(x) || !isZero(y)) + mType |= TRANSLATE; } - return flags; + return mType; +} + +uint32_t Transform::getType() const { + return type() & 0xFF; +} + +uint32_t Transform::getOrientation() const +{ + return (type() >> 8) & 0xFF; } bool Transform::preserveRects() const { - return mTransform.rectStaysRect(); + return (type() & ROT_INVALID) ? false : true; +} + +void Transform::dump(const char* name) const +{ + type(); // updates the type + + String8 flags, type; + const mat33& m(mMatrix); + uint32_t orient = mType >> 8; + + if (orient&ROT_INVALID) + flags.append("ROT_INVALID "); + if (orient&ROT_90) + flags.append("ROT_90 "); + if (orient&FLIP_V) + flags.append("FLIP_V "); + if (orient&FLIP_H) + flags.append("FLIP_H "); + + if (mType&SCALE) + type.append("SCALE "); + if (mType&ROTATE) + type.append("ROTATE "); + if (mType&TRANSLATE) + type.append("TRANSLATE "); + + LOGD("%s (%s, %s)", name, flags.string(), type.string()); + LOGD("%.2f %.2f %.2f", m[0][0], m[1][0], m[2][0]); + LOGD("%.2f %.2f %.2f", m[0][1], m[1][1], m[2][1]); + LOGD("%.2f %.2f %.2f", m[0][2], m[1][2], m[2][2]); } // --------------------------------------------------------------------------- diff --git a/libs/surfaceflinger/Transform.h b/libs/surfaceflinger/Transform.h index ddab404..51d3e3f 100644 --- a/libs/surfaceflinger/Transform.h +++ b/libs/surfaceflinger/Transform.h @@ -23,10 +23,6 @@ #include <ui/Point.h> #include <ui/Rect.h> -#include <GLES/gl.h> - -#include <core/SkMatrix.h> - namespace android { class Region; @@ -38,9 +34,12 @@ class Transform public: Transform(); Transform(const Transform& other); - Transform(int32_t flags); + explicit Transform(uint32_t orientation); ~Transform(); + typedef int32_t fixed1616; + + // FIXME: must match OVERLAY_TRANSFORM_*, pull from hardware.h enum orientation_flags { ROT_0 = 0x00000000, FLIP_H = 0x00000001, @@ -48,48 +47,79 @@ public: ROT_90 = 0x00000004, ROT_180 = FLIP_H|FLIP_V, ROT_270 = ROT_180|ROT_90, - ROT_INVALID = 0x80000000 + ROT_INVALID = 0x80 }; enum type_mask { IDENTITY = 0, TRANSLATE = 0x1, - SCALE = 0x2, - AFFINE = 0x4, - PERSPECTIVE = 0x8 + ROTATE = 0x2, + SCALE = 0x4, + UNKNOWN = 0x8 }; - bool transformed() const; - int32_t getOrientation() const; - bool preserveRects() const; - + // query the transform + bool transformed() const; + bool preserveRects() const; + uint32_t getType() const; + uint32_t getOrientation() const; + + float const* operator [] (int i) const; // returns column i int tx() const; int ty() const; - + + // modify the transform void reset(); - void set(float xx, float xy, float yx, float yy); - void set(int tx, int ty); - void set(float radian, float x, float y); - void scale(float s, float x, float y); - + void set(float tx, float ty); + void set(float a, float b, float c, float d); + void set(uint32_t flags, float w, float h); + + // transform data Rect makeBounds(int w, int h) const; - void transform(GLfixed* point, int x, int y) const; + void transform(fixed1616* point, int x, int y) const; Region transform(const Region& reg) const; - Rect transform(const Rect& bounds) const; - Transform operator * (const Transform& rhs) const; - float operator [] (int i) const; - - inline uint32_t getType() const { return type(); } - - inline Transform(bool) : mType(0xFF) { }; - -private: - uint8_t type() const; private: - SkMatrix mTransform; - mutable uint32_t mType; + struct vec3 { + float v[3]; + inline vec3() { } + inline vec3(float a, float b, float c) { + v[0] = a; v[1] = b; v[2] = c; + } + inline float operator [] (int i) const { return v[i]; } + inline float& operator [] (int i) { return v[i]; } + }; + struct vec2 { + float v[2]; + inline vec2() { } + inline vec2(float a, float b) { + v[0] = a; v[1] = b; + } + inline float operator [] (int i) const { return v[i]; } + inline float& operator [] (int i) { return v[i]; } + }; + struct mat33 { + vec3 v[3]; + inline const vec3& operator [] (int i) const { return v[i]; } + inline vec3& operator [] (int i) { return v[i]; } + }; + + enum { UNKNOWN_TYPE = 0x80000000 }; + + // assumes the last row is < 0 , 0 , 1 > + vec2 transform(const vec2& v) const; + vec3 transform(const vec3& v) const; + Rect transform(const Rect& bounds) const; + uint32_t type() const; + static bool absIsOne(float f); + static bool absEqual(float a, float b); + static bool isZero(float f); + + void dump(const char* name) const; + + mat33 mMatrix; + mutable uint32_t mType; }; // --------------------------------------------------------------------------- diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index e8bd5cf..38600b9 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -497,6 +497,34 @@ const uint16_t* ResStringPool::stringAt(size_t idx, size_t* outLen) const return NULL; } +const char* ResStringPool::string8At(size_t idx, size_t* outLen) const +{ + if (mError == NO_ERROR && idx < mHeader->stringCount) { + const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0; + const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t)); + if (off < (mStringPoolSize-1)) { + if (isUTF8) { + const uint8_t* strings = (uint8_t*)mStrings; + const uint8_t* str = strings+off; + DECODE_LENGTH(str, sizeof(uint8_t), *outLen) + size_t encLen; + DECODE_LENGTH(str, sizeof(uint8_t), encLen) + if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { + return (const char*)str; + } else { + LOGW("Bad string block: string #%d extends to %d, past end at %d\n", + (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize); + } + } + } else { + LOGW("Bad string block: string #%d entry is at %d, past end at %d\n", + (int)idx, (int)(off*sizeof(uint16_t)), + (int)(mStringPoolSize*sizeof(uint16_t))); + } + } + return NULL; +} + const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const { return styleAt(ref.index); @@ -4018,14 +4046,19 @@ void ResTable::print_value(const Package* pkg, const Res_value& value) const printf("(attribute) 0x%08x\n", value.data); } else if (value.dataType == Res_value::TYPE_STRING) { size_t len; - const char16_t* str = pkg->header->values.stringAt( + const char* str8 = pkg->header->values.string8At( value.data, &len); - if (str == NULL) { - printf("(string) null\n"); + if (str8 != NULL) { + printf("(string8) \"%s\"\n", str8); } else { - printf("(string%d) \"%s\"\n", - pkg->header->values.isUTF8()?8:16, - String8(str, len).string()); + const char16_t* str16 = pkg->header->values.stringAt( + value.data, &len); + if (str16 != NULL) { + printf("(string16) \"%s\"\n", + String8(str16, len).string()); + } else { + printf("(string) null\n"); + } } } else if (value.dataType == Res_value::TYPE_FLOAT) { printf("(float) %g\n", *(const float*)&value.data); diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java index 8e84106..90b50cc 100755 --- a/location/java/com/android/internal/location/GpsLocationProvider.java +++ b/location/java/com/android/internal/location/GpsLocationProvider.java @@ -321,7 +321,9 @@ public class GpsLocationProvider implements LocationProviderInterface { public GpsLocationProvider(Context context, ILocationManager locationManager) { mContext = context; mLocationManager = locationManager; - mNIHandler= new GpsNetInitiatedHandler(context, this); + mNIHandler = new GpsNetInitiatedHandler(context, this); + + mLocation.setExtras(mLocationExtras); // Create a wake lock PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -759,7 +761,7 @@ public class GpsLocationProvider implements LocationProviderInterface { positionMode = GPS_POSITION_MODE_STANDALONE; } - if (!native_start(positionMode, false, mFixInterval)) { + if (!native_start(positionMode, false, 1)) { mStarted = false; Log.e(TAG, "native_start failed in startNavigating()"); return; @@ -870,7 +872,12 @@ public class GpsLocationProvider implements LocationProviderInterface { } if (mStarted && mStatus != LocationProvider.AVAILABLE) { - mAlarmManager.cancel(mTimeoutIntent); + // we still want to time out if we do not receive MIN_FIX_COUNT + // within the time out and we are requesting infrequent fixes + if (mFixInterval < NO_FIX_TIMEOUT) { + mAlarmManager.cancel(mTimeoutIntent); + } + // send an intent to notify that the GPS is receiving fixes. Intent intent = new Intent(GPS_FIX_CHANGE_ACTION); intent.putExtra(EXTRA_ENABLED, true); diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 7997cd6..57f58be 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -38,6 +38,7 @@ AudioPlayer::AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink) mPositionTimeRealUs(-1), mSeeking(false), mReachedEOS(false), + mFinalStatus(OK), mStarted(false), mAudioSink(audioSink) { } @@ -168,6 +169,7 @@ void AudioPlayer::stop() { mPositionTimeRealUs = -1; mSeeking = false; mReachedEOS = false; + mFinalStatus = OK; mStarted = false; } @@ -181,8 +183,11 @@ bool AudioPlayer::isSeeking() { return mSeeking; } -bool AudioPlayer::reachedEOS() { +bool AudioPlayer::reachedEOS(status_t *finalStatus) { + *finalStatus = OK; + Mutex::Autolock autoLock(mLock); + *finalStatus = mFinalStatus; return mReachedEOS; } @@ -245,6 +250,7 @@ size_t AudioPlayer::fillBuffer(void *data, size_t size) { if (err != OK) { mReachedEOS = true; + mFinalStatus = err; break; } diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index b3a73b0..ab65b44 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -434,14 +434,22 @@ void AwesomePlayer::onStreamDone() { } mStreamDoneEventPending = false; - if (mFlags & LOOPING) { + if (mStreamDoneStatus == ERROR_END_OF_STREAM && (mFlags & LOOPING)) { seekTo_l(0); if (mVideoSource != NULL) { postVideoEvent_l(); } } else { - notifyListener_l(MEDIA_PLAYBACK_COMPLETE); + if (mStreamDoneStatus == ERROR_END_OF_STREAM) { + LOGV("MEDIA_PLAYBACK_COMPLETE"); + notifyListener_l(MEDIA_PLAYBACK_COMPLETE); + } else { + LOGV("MEDIA_ERROR %d", mStreamDoneStatus); + + notifyListener_l( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, mStreamDoneStatus); + } pause_l(); @@ -802,7 +810,7 @@ void AwesomePlayer::onVideoEvent() { continue; } - postStreamDoneEvent_l(); + postStreamDoneEvent_l(err); return; } @@ -904,11 +912,13 @@ void AwesomePlayer::postVideoEvent_l(int64_t delayUs) { mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs); } -void AwesomePlayer::postStreamDoneEvent_l() { +void AwesomePlayer::postStreamDoneEvent_l(status_t status) { if (mStreamDoneEventPending) { return; } mStreamDoneEventPending = true; + + mStreamDoneStatus = status; mQueue.postEvent(mStreamDoneEvent); } @@ -947,9 +957,10 @@ void AwesomePlayer::onCheckAudioStatus() { notifyListener_l(MEDIA_SEEK_COMPLETE); } - if (mWatchForAudioEOS && mAudioPlayer->reachedEOS()) { + status_t finalStatus; + if (mWatchForAudioEOS && mAudioPlayer->reachedEOS(&finalStatus)) { mWatchForAudioEOS = false; - postStreamDoneEvent_l(); + postStreamDoneEvent_l(finalStatus); } postCheckAudioStatusEvent_l(); diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index a6053b3..165ac09 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -1450,6 +1450,14 @@ status_t MPEG4Source::read( &sampleIndex, SampleTable::kSyncSample_Flag); if (err != OK) { + if (err == ERROR_OUT_OF_RANGE) { + // An attempt to seek past the end of the stream would + // normally cause this ERROR_OUT_OF_RANGE error. Propagating + // this all the way to the MediaPlayer would cause abnormal + // termination. Legacy behaviour appears to be to behave as if + // we had seeked to the end of stream, ending normally. + err = ERROR_END_OF_STREAM; + } return err; } diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index 4075ec1..974413d 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -1933,6 +1933,7 @@ void OMXCodec::drainInputBuffer(BufferInfo *info) { CODEC_LOGV("signalling end of input stream."); flags |= OMX_BUFFERFLAG_EOS; + mFinalStatus = err; mSignalledEOS = true; } else { mNoMoreOutputData = false; @@ -2411,7 +2412,7 @@ status_t OMXCodec::read( } if (mFilledBuffers.empty()) { - return ERROR_END_OF_STREAM; + return mSignalledEOS ? mFinalStatus : ERROR_END_OF_STREAM; } if (mOutputPortSettingsHaveChanged) { diff --git a/media/libstagefright/Prefetcher.cpp b/media/libstagefright/Prefetcher.cpp index 363e121..9c73f4a 100644 --- a/media/libstagefright/Prefetcher.cpp +++ b/media/libstagefright/Prefetcher.cpp @@ -55,6 +55,7 @@ private: size_t mIndex; bool mStarted; bool mReachedEOS; + status_t mFinalStatus; int64_t mSeekTimeUs; int64_t mCacheDurationUs; bool mPrefetcherStopped; @@ -306,7 +307,7 @@ status_t PrefetchedSource::read( } if (mCachedBuffers.empty()) { - return ERROR_END_OF_STREAM; + return mReachedEOS ? mFinalStatus : ERROR_END_OF_STREAM; } *out = *mCachedBuffers.begin(); @@ -353,6 +354,7 @@ void PrefetchedSource::cacheMore() { if (err != OK) { mReachedEOS = true; + mFinalStatus = err; mCondition.signal(); return; diff --git a/media/libstagefright/include/AwesomePlayer.h b/media/libstagefright/include/AwesomePlayer.h index ce8eeae..3590987 100644 --- a/media/libstagefright/include/AwesomePlayer.h +++ b/media/libstagefright/include/AwesomePlayer.h @@ -145,10 +145,11 @@ private: Condition mPreparedCondition; bool mIsAsyncPrepare; status_t mPrepareResult; + status_t mStreamDoneStatus; void postVideoEvent_l(int64_t delayUs = -1); void postBufferingEvent_l(); - void postStreamDoneEvent_l(); + void postStreamDoneEvent_l(status_t status); void postCheckAudioStatusEvent_l(); status_t getPosition_l(int64_t *positionUs); status_t play_l(); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java index 717f7ba..d7cf069 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaProfileReader.java @@ -1,28 +1,35 @@ package com.android.mediaframeworktest; import android.media.MediaRecorder; +import android.media.EncoderCapabilities; +import android.media.EncoderCapabilities.VideoEncoderCap; +import android.media.EncoderCapabilities.AudioEncoderCap; +import android.media.DecoderCapabilities; +import android.media.DecoderCapabilities.VideoDecoder; +import android.media.DecoderCapabilities.AudioDecoder; + import android.os.SystemProperties; +import java.util.List; import java.util.HashMap; -public class MediaProfileReader { +public class MediaProfileReader +{ + private static final List<VideoDecoder> videoDecoders = DecoderCapabilities.getVideoDecoders(); + private static final List<AudioDecoder> audioDecoders = DecoderCapabilities.getAudioDecoders(); + private static final List<VideoEncoderCap> videoEncoders = EncoderCapabilities.getVideoEncoders(); + private static final List<AudioEncoderCap> audioEncoders = EncoderCapabilities.getAudioEncoders(); + private static final HashMap<Integer, String> encoderMap = new HashMap<Integer, String>(); - public static final HashMap<String, Integer> - OUTPUT_FORMAT_TABLE = new HashMap<String, Integer>(); - public static String MEDIA_ENC_VID = "ro.media.enc.vid."; - public static String MEDIA_AUD_VID = "ro.media.enc.aud."; - public static String[] VIDEO_ENCODER_PROPERTY = {".width", ".height", ".bps", ".fps",}; - public static String[] AUDIO_ENCODER_PROPERTY = {".bps", ".hz", ".ch",}; + static { + initEncoderMap(); + }; - public static String getVideoCodecProperty() { - String s; - s = SystemProperties.get("ro.media.enc.vid.codec"); - return s; + public static List<VideoEncoderCap> getVideoEncoders() { + return videoEncoders; } - public static String getAudioCodecProperty() { - String s; - s = SystemProperties.get("ro.media.enc.aud.codec"); - return s; + public static List<AudioEncoderCap> getAudioEncoders() { + return audioEncoders; } public static String getDeviceType() { @@ -33,78 +40,56 @@ public class MediaProfileReader { } public static boolean getWMAEnable() { - // push all the property into one big table - int wmaEnable = 1; - wmaEnable = SystemProperties.getInt("ro.media.dec.aud.wma.enabled", - wmaEnable); - if (wmaEnable == 1) { - return true; - } else { - return false; + for (AudioDecoder decoder: audioDecoders) { + if (decoder == AudioDecoder.AUDIO_DECODER_WMA) { + return true; + } } + return false; } public static boolean getWMVEnable(){ - int wmvEnable = 1; - wmvEnable = SystemProperties.getInt("ro.media.dec.vid.wmv.enabled", - wmvEnable); - if (wmvEnable == 1) { - return true; - } else { - return false; + for (VideoDecoder decoder: videoDecoders) { + if (decoder == VideoDecoder.VIDEO_DECODER_WMV) { + return true; + } } + return false; } - public static void createVideoProfileTable() { - // push all the property into one big table - String encoderType = getVideoCodecProperty(); - if (encoderType.length() != 0) { - String encoder[] = encoderType.split(","); - for (int i = 0; i < encoder.length; i++) { - for (int j = 0; j < VIDEO_ENCODER_PROPERTY.length; j++) { - String propertyName = MEDIA_ENC_VID + encoder[i] + VIDEO_ENCODER_PROPERTY[j]; - String prop = SystemProperties.get(propertyName); - // push to the table - String propRange[] = prop.split(","); - OUTPUT_FORMAT_TABLE.put((encoder[i] + VIDEO_ENCODER_PROPERTY[j] + "_low"), - Integer.parseInt(propRange[0])); - OUTPUT_FORMAT_TABLE.put((encoder[i] + VIDEO_ENCODER_PROPERTY[j] + "_high"), - Integer.parseInt(propRange[1])); - } - - } + public static String getVideoCodecName(int videoEncoder) { + if (videoEncoder != MediaRecorder.VideoEncoder.H263 && + videoEncoder != MediaRecorder.VideoEncoder.H264 && + videoEncoder != MediaRecorder.VideoEncoder.MPEG_4_SP) { + throw new IllegalArgumentException("Unsupported video encoder " + videoEncoder); } + return encoderMap.get(videoEncoder); } - public static void createAudioProfileTable() { - // push all the property into one big table - String audioType = getAudioCodecProperty(); - String encoder[] = audioType.split(","); - if (audioType.length() != 0) { - for (int i = 0; i < encoder.length; i++) { - for (int j = 0; j < AUDIO_ENCODER_PROPERTY.length; j++) { - String propertyName = MEDIA_AUD_VID + encoder[i] + AUDIO_ENCODER_PROPERTY[j]; - String prop = SystemProperties.get(propertyName); - // push to the table - String propRange[] = prop.split(","); - OUTPUT_FORMAT_TABLE.put((encoder[i] + AUDIO_ENCODER_PROPERTY[j] + "_low"), - Integer.parseInt(propRange[0])); - OUTPUT_FORMAT_TABLE.put((encoder[i] + AUDIO_ENCODER_PROPERTY[j] + "_high"), - Integer.parseInt(propRange[1])); - } - } + public static String getAudioCodecName(int audioEncoder) { + if (audioEncoder != MediaRecorder.AudioEncoder.AMR_NB && + audioEncoder != MediaRecorder.AudioEncoder.AMR_WB && + audioEncoder != MediaRecorder.AudioEncoder.AAC && + audioEncoder != MediaRecorder.AudioEncoder.AAC_PLUS && + audioEncoder != MediaRecorder.AudioEncoder.EAAC_PLUS) { + throw new IllegalArgumentException("Unsupported audio encodeer " + audioEncoder); } + return encoderMap.get(audioEncoder); } - public static void createEncoderTable(){ - OUTPUT_FORMAT_TABLE.put("h263", MediaRecorder.VideoEncoder.H263); - OUTPUT_FORMAT_TABLE.put("h264", MediaRecorder.VideoEncoder.H264); - OUTPUT_FORMAT_TABLE.put("m4v", MediaRecorder.VideoEncoder.MPEG_4_SP); - OUTPUT_FORMAT_TABLE.put("amrnb", MediaRecorder.AudioEncoder.AMR_NB); - OUTPUT_FORMAT_TABLE.put("amrwb", MediaRecorder.AudioEncoder.AMR_WB); - OUTPUT_FORMAT_TABLE.put("aac", MediaRecorder.AudioEncoder.AAC); - OUTPUT_FORMAT_TABLE.put("aacplus", MediaRecorder.AudioEncoder.AAC_PLUS); - OUTPUT_FORMAT_TABLE.put("eaacplus", - MediaRecorder.AudioEncoder.EAAC_PLUS); + private MediaProfileReader() {} // Don't call me + + private static void initEncoderMap() { + // video encoders + encoderMap.put(MediaRecorder.VideoEncoder.H263, "h263"); + encoderMap.put(MediaRecorder.VideoEncoder.H264, "h264"); + encoderMap.put(MediaRecorder.VideoEncoder.MPEG_4_SP, "m4v"); + + // audio encoders + encoderMap.put(MediaRecorder.AudioEncoder.AMR_NB, "amrnb"); + encoderMap.put(MediaRecorder.AudioEncoder.AMR_WB, "amrwb"); + encoderMap.put(MediaRecorder.AudioEncoder.AAC, "aac"); + encoderMap.put(MediaRecorder.AudioEncoder.AAC_PLUS, "aacplus"); + encoderMap.put(MediaRecorder.AudioEncoder.EAAC_PLUS, "eaacplus"); } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java index fdc5970..39caccd 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaRecorderTest.java @@ -25,6 +25,9 @@ import android.content.Context; import android.hardware.Camera; import android.media.MediaPlayer; import android.media.MediaRecorder; +import android.media.EncoderCapabilities; +import android.media.EncoderCapabilities.VideoEncoderCap; +import android.media.EncoderCapabilities.AudioEncoderCap; import android.test.ActivityInstrumentationTestCase; import android.util.Log; import android.view.SurfaceHolder; @@ -33,6 +36,7 @@ import com.android.mediaframeworktest.MediaProfileReader; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.Suppress; +import java.util.List; /** @@ -99,31 +103,29 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase<MediaFram } } - private boolean recordVideoWithPara(String encoder, String audio, String quality){ + private boolean recordVideoWithPara(VideoEncoderCap videoCap, AudioEncoderCap audioCap, boolean highQuality){ boolean recordSuccess = false; - int videoEncoder = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder); - int audioEncoder = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio); - int videoWidth = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".width_" + quality); - int videoHeight = - MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".height_" + quality); - int videoFps = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".fps_" + quality); - int videoBitrate = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(encoder + ".bps_" + quality); - int audioBitrate = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio + ".bps_" + quality); - int audioChannels = MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio + ".ch_" + quality); - int audioSamplingRate = - MediaProfileReader.OUTPUT_FORMAT_TABLE.get(audio + ".hz_" + quality); + int videoEncoder = videoCap.mCodec; + int audioEncoder = audioCap.mCodec; + int videoWidth = highQuality? videoCap.mMaxFrameWidth: videoCap.mMinFrameWidth; + int videoHeight = highQuality? videoCap.mMaxFrameHeight: videoCap.mMinFrameHeight; + int videoFps = highQuality? videoCap.mMaxFrameRate: videoCap.mMinFrameRate; + int videoBitrate = highQuality? videoCap.mMaxBitRate: videoCap.mMinBitRate; + int audioBitrate = highQuality? audioCap.mMaxBitRate: audioCap.mMinBitRate; + int audioChannels = highQuality? audioCap.mMaxChannels: audioCap.mMinChannels ; + int audioSamplingRate = highQuality? audioCap.mMaxSampleRate: audioCap.mMinSampleRate; if (videoFps < MIN_VIDEO_FPS) { videoFps = MIN_VIDEO_FPS; } mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); - String filename = ("/sdcard/" + encoder + "_" + audio + "_" + quality + ".3gp"); + String filename = ("/sdcard/" + videoEncoder + "_" + audioEncoder + "_" + highQuality + ".3gp"); try { Log.v(TAG, "video encoder :" + videoEncoder); Log.v(TAG, "audio encoder :" + audioEncoder); - Log.v(TAG, "quality : " + quality); - Log.v(TAG, "encoder : " + encoder); - Log.v(TAG, "audio : " + audio); + Log.v(TAG, "quality : " + (highQuality?"high": "low")); + Log.v(TAG, "encoder : " + MediaProfileReader.getVideoCodecName(videoEncoder)); + Log.v(TAG, "audio : " + MediaProfileReader.getAudioCodecName(audioEncoder)); Log.v(TAG, "videoWidth : " + videoWidth); Log.v(TAG, "videoHeight : " + videoHeight); Log.v(TAG, "videoFPS : " + videoFps); @@ -434,35 +436,26 @@ public class MediaRecorderTest extends ActivityInstrumentationTestCase<MediaFram boolean recordSuccess = false; String deviceType = MediaProfileReader.getDeviceType(); Log.v(TAG, "deviceType = " + deviceType); - // Test cases are device specified - MediaProfileReader.createVideoProfileTable(); - MediaProfileReader.createAudioProfileTable(); - MediaProfileReader.createEncoderTable(); - String encoderType = MediaProfileReader.getVideoCodecProperty(); - String audioType = MediaProfileReader.getAudioCodecProperty(); - if ((encoderType.length() != 0) || (audioType.length() != 0)) { - String audio[] = audioType.split(","); - String encoder[] = encoderType.split(","); - for (int k = 0; k < 2; k++) { - for (int i = 0; i < encoder.length; i++) { - for (int j = 0; j < audio.length; j++) { - if (k == 0) { - recordSuccess = recordVideoWithPara(encoder[i], audio[j], "high"); - } else { - recordSuccess = recordVideoWithPara(encoder[i], audio[j], "low"); - } - if (!recordSuccess) { - Log.v(TAG, "testDeviceSpecificCodec failed"); - Log.v(TAG, "Encoder = " + encoder[i] + "Audio Encoder = " + audio[j]); - noOfFailure++; - } - // assertTrue((encoder[i] + audio[j]), recordSuccess); + List<VideoEncoderCap> videoEncoders = MediaProfileReader.getVideoEncoders(); + List<AudioEncoderCap> audioEncoders = MediaProfileReader.getAudioEncoders(); + for (int k = 0; k < 2; k++) { + for (VideoEncoderCap videoEncoder: videoEncoders) { + for (AudioEncoderCap audioEncoder: audioEncoders) { + if (k == 0) { + recordSuccess = recordVideoWithPara(videoEncoder, audioEncoder, true); + } else { + recordSuccess = recordVideoWithPara(videoEncoder, audioEncoder, false); + } + if (!recordSuccess) { + Log.v(TAG, "testDeviceSpecificCodec failed"); + Log.v(TAG, "Encoder = " + videoEncoder.mCodec + "Audio Encoder = " + audioEncoder.mCodec); + noOfFailure++; } } } - if (noOfFailure != 0) { - assertTrue("testDeviceSpecificCodec", false); - } + } + if (noOfFailure != 0) { + assertTrue("testDeviceSpecificCodec", false); } } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java index 8750098..4c259f1 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaMetadataRetrieverTest.java @@ -39,9 +39,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { public static void testAlbumArt() throws Exception { Log.v(TAG, "testAlbumArt starts."); MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - MediaProfileReader reader = new MediaProfileReader(); - boolean supportWMA = reader.getWMAEnable(); - boolean supportWMV = reader.getWMVEnable(); + boolean supportWMA = MediaProfileReader.getWMAEnable(); + boolean supportWMV = MediaProfileReader.getWMVEnable(); retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY); for (int i = 0, n = MediaNames.ALBUMART_TEST_FILES.length; i < n; ++i) { try { @@ -74,9 +73,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { @LargeTest public static void testThumbnailCapture() throws Exception { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); - MediaProfileReader reader = new MediaProfileReader(); - boolean supportWMA = reader.getWMAEnable(); - boolean supportWMV = reader.getWMVEnable(); + boolean supportWMA = MediaProfileReader.getWMAEnable(); + boolean supportWMV = MediaProfileReader.getWMVEnable(); Log.v(TAG, "Thumbnail processing starts"); long startedAt = System.currentTimeMillis(); for(int i = 0, n = MediaNames.THUMBNAIL_CAPTURE_TEST_FILES.length; i < n; ++i) { @@ -110,9 +108,8 @@ public class MediaMetadataRetrieverTest extends AndroidTestCase { @LargeTest public static void testMetadataRetrieval() throws Exception { - MediaProfileReader reader = new MediaProfileReader(); - boolean supportWMA = reader.getWMAEnable(); - boolean supportWMV = reader.getWMVEnable(); + boolean supportWMA = MediaProfileReader.getWMAEnable(); + boolean supportWMV = MediaProfileReader.getWMVEnable(); MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setMode(MediaMetadataRetriever.MODE_GET_METADATA_ONLY); for(int i = 0, n = MediaNames.METADATA_RETRIEVAL_TEST_FILES.length; i < n; ++i) { diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java index 95ab684..a79f0cd 100644 --- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java +++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java @@ -39,7 +39,7 @@ import android.provider.Settings; */ public class DefaultContainerService extends IntentService { private static final String TAG = "DefContainer"; - private static final boolean localLOGV = false; + private static final boolean localLOGV = true; private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { /* @@ -211,6 +211,8 @@ public class DefaultContainerService extends IntentService { if (PackageHelper.isContainerMounted(newCid)) { if (localLOGV) Log.i(TAG, "Unmounting " + newCid + " at path " + newCachePath + " after " + errMsg); + // Force a gc to avoid being killed. + Runtime.getRuntime().gc(); PackageHelper.unMountSdDir(newCid); } else { if (localLOGV) Log.i(TAG, "Container " + newCid + " not mounted"); @@ -328,8 +330,8 @@ public class DefaultContainerService extends IntentService { boolean auto = true; // To make final copy long reqInstallSize = pkgLen; - // For dex files - long reqInternalSize = 1 * pkgLen; + // For dex files. Just ignore and fail when extracting. Max limit of 2Gig for now. + long reqInternalSize = 0; boolean intThresholdOk = (pctNandFree >= LOW_NAND_FLASH_TRESHOLD); boolean intAvailOk = ((reqInstallSize + reqInternalSize) < availInternalFlashSize); boolean fitsOnSd = (reqInstallSize < availSDSize) && intThresholdOk && diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 654ca32..a5a518b 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -61,12 +61,12 @@ <!-- user interface sound effects --> <integer name="def_power_sounds_enabled">1</integer> <string name="def_low_battery_sound">/system/media/ui/LowBattery.ogg</string> - <integer name="def_dock_sounds_enabled">1</integer> - <string name="def_desk_dock_sound">/system/media/audio/ui/dock.ogg</string> - <string name="def_desk_undock_sound">/system/media/audio/ui/undock.ogg</string> + <integer name="def_dock_sounds_enabled">0</integer> + <string name="def_desk_dock_sound">/system/media/audio/ui/Dock.ogg</string> + <string name="def_desk_undock_sound">/system/media/audio/ui/Undock.ogg</string> <string name="def_car_dock_sound">/system/media/audio/ui/Dock.ogg</string> <string name="def_car_undock_sound">/system/media/audio/ui/Undock.ogg</string> - <integer name="def_lockscreen_sounds_enabled">1</integer> + <integer name="def_lockscreen_sounds_enabled">0</integer> <string name="def_lock_sound">/system/media/audio/ui/Lock.ogg</string> <string name="def_unlock_sound">/system/media/audio/ui/Unlock.ogg</string> diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index 39ee314..2dc12f6 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -1003,7 +1003,11 @@ class MountService extends IMountService.Stub warnOnNotMounted(); synchronized (mAsecMountSet) { - if (mAsecMountSet.contains(oldId)) { + /* + * Because a mounted container has active internal state which cannot be + * changed while active, we must ensure both ids are not currently mounted. + */ + if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) { return StorageResultCode.OperationFailedStorageMounted; } } diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 958d089..7c555e2 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -256,14 +256,14 @@ class NetworkManagementService extends INetworkManagementService.Stub { Log.e(TAG, "Failed to parse netmask", uhe); cfg.netmask = 0; } - cfg.interfaceFlags = st.nextToken("]"); + cfg.interfaceFlags = st.nextToken("]").trim() +"]"; Log.d(TAG, String.format("flags <%s>", cfg.interfaceFlags)); return cfg; } public void setInterfaceConfig( String iface, InterfaceConfiguration cfg) throws IllegalStateException { - String cmd = String.format("interface setcfg %s %s %s", iface, + String cmd = String.format("interface setcfg %s %s %s %s", iface, intToIpString(cfg.ipAddr), intToIpString(cfg.netmask), cfg.interfaceFlags); mConnector.doCommand(cmd); } diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 4a7fc9c..9e0d623 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -4858,42 +4858,67 @@ class PackageManagerService extends IPackageManager.Stub { String oldCodePath) { String newCacheId = getNextCodePath(oldCodePath, pkgName, "/" + RES_FILE_NAME); String newCachePath = null; - final int RENAME_FAILED = 1; - final int MOUNT_FAILED = 2; - final int PASS = 4; - int errCode = RENAME_FAILED; - String errMsg = "RENAME_FAILED"; - boolean mounted = PackageHelper.isContainerMounted(cid); - if (mounted) { - // Unmount the container - if (!PackageHelper.unMountSdDir(cid)) { - Log.i(TAG, "Failed to unmount " + cid + " before renaming"); + boolean enableRename = false; + if (enableRename) { + if (PackageHelper.isContainerMounted(cid)) { + // Unmount the container + if (!PackageHelper.unMountSdDir(cid)) { + Log.i(TAG, "Failed to unmount " + cid + " before renaming"); + return false; + } + } + if (!PackageHelper.renameSdDir(cid, newCacheId)) { + Log.e(TAG, "Failed to rename " + cid + " to " + newCacheId); return false; } - mounted = false; - } - if (PackageHelper.renameSdDir(cid, newCacheId)) { - errCode = MOUNT_FAILED; - errMsg = "MOUNT_FAILED"; - if ((newCachePath = PackageHelper.mountSdDir(newCacheId, - getEncryptKey(), Process.SYSTEM_UID)) != null) { - errCode = PASS; - errMsg = "PASS"; + if (!PackageHelper.isContainerMounted(newCacheId)) { + Log.w(TAG, "Mounting container " + newCacheId); + newCachePath = PackageHelper.mountSdDir(newCacheId, + getEncryptKey(), Process.SYSTEM_UID); + } else { + newCachePath = PackageHelper.getSdDir(newCacheId); + } + if (newCachePath == null) { + Log.w(TAG, "Failed to get cache path for " + newCacheId); + return false; } - } - if (errCode != PASS) { - Log.i(TAG, "Failed to rename " + cid + " to " + newCacheId + - " at path: " + cachePath + " to new path: " + newCachePath + - "err = " + errMsg); // Mount old container? - return false; + Log.i(TAG, "Succesfully renamed " + cid + + " at path: " + cachePath + " to " + newCacheId + + " at new path: " + newCachePath); + cid = newCacheId; + cachePath = newCachePath; + return true; } else { - Log.i(TAG, "Succesfully renamed " + cid + " to " + newCacheId + - " at path: " + cachePath + " to new path: " + newCachePath); + // STOPSHIP work around for rename + Log.i(TAG, "Copying instead of renaming"); + File srcFile = new File(getCodePath()); + // Create new container + newCachePath = PackageHelper.createSdDir(srcFile, newCacheId, + getEncryptKey(), Process.SYSTEM_UID); + Log.i(TAG, "Created rename container " + newCacheId); + File destFile = new File(newCachePath + "/" + RES_FILE_NAME); + if (!FileUtils.copyFile(srcFile, destFile)) { + Log.e(TAG, "Failed to copy " + srcFile + " to " + destFile); + return false; + } + Log.i(TAG, "Successfully copied resource to " + newCachePath); + if (!PackageHelper.finalizeSdDir(newCacheId)) { + Log.e(TAG, "Failed to finalize " + newCacheId); + PackageHelper.destroySdDir(newCacheId); + return false; + } + Log.i(TAG, "Finalized " + newCacheId); + Runtime.getRuntime().gc(); + // Unmount first + PackageHelper.unMountSdDir(cid); + // Delete old container + PackageHelper.destroySdDir(cid); + // Dont have to mount. Already mounted. + cid = newCacheId; + cachePath = newCachePath; + return true; } - cid = newCacheId; - cachePath = newCachePath; - return true; } int doPostInstall(int status) { @@ -5403,6 +5428,7 @@ class PackageManagerService extends IPackageManager.Stub { res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; return; } + if (!args.doRename(res.returnCode, pkgName, oldCodePath)) { res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; return; @@ -8809,7 +8835,7 @@ class PackageManagerService extends IPackageManager.Stub { } static String getTempContainerId() { - String prefix = "smdl1tmp"; + String prefix = "smdl2tmp"; int tmpIdx = 1; String list[] = PackageHelper.getSecureContainerList(); if (list != null) { @@ -8851,30 +8877,45 @@ class PackageManagerService extends IPackageManager.Stub { return prefix + tmpIdx; } - public void updateExternalMediaStatus(final boolean mediaStatus) { + public boolean updateExternalMediaStatus(final boolean mediaStatus) { synchronized (mPackages) { if (DEBUG_SD_INSTALL) Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus+", mMediaMounted=" + mMediaMounted); if (mediaStatus == mMediaMounted) { - return; + return false; } mMediaMounted = mediaStatus; + final HashMap<SdInstallArgs, String> processCids = + new HashMap<SdInstallArgs, String>(); + final int[] uidArr = getExternalMediaPackages(mediaStatus, processCids); + if (processCids.size() == 0) { + return false; + } // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); - updateExternalMediaStatusInner(mediaStatus); + if (mediaStatus) { + if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); + loadMediaPackages(processCids, uidArr); + startCleaningPackages(); + } else { + if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); + unloadMediaPackages(processCids, uidArr); + } } }); + return true; } } - void updateExternalMediaStatusInner(boolean mediaStatus) { + private int[] getExternalMediaPackages(boolean mediaStatus, + Map<SdInstallArgs, String> processCids) { final String list[] = PackageHelper.getSecureContainerList(); if (list == null || list.length == 0) { - return; + return null; } - HashMap<SdInstallArgs, String> processCids = new HashMap<SdInstallArgs, String>(); + int uidList[] = new int[list.length]; int num = 0; synchronized (mPackages) { @@ -8916,14 +8957,7 @@ class PackageManagerService extends IPackageManager.Stub { } } } - if (mediaStatus) { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Loading packages"); - loadMediaPackages(processCids, uidArr); - startCleaningPackages(); - } else { - if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading packages"); - unloadMediaPackages(processCids, uidArr); - } + return uidArr; } private void sendResourcesChangedBroadcast(boolean mediaStatus, @@ -8943,7 +8977,7 @@ class PackageManagerService extends IPackageManager.Stub { } } - void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) { + private void loadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) { ArrayList<String> pkgList = new ArrayList<String>(); Set<SdInstallArgs> keys = processCids.keySet(); for (SdInstallArgs args : keys) { @@ -8993,7 +9027,7 @@ class PackageManagerService extends IPackageManager.Stub { } } - void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) { + private void unloadMediaPackages(HashMap<SdInstallArgs, String> processCids, int uidArr[]) { if (DEBUG_SD_INSTALL) Log.i(TAG, "unloading media packages"); ArrayList<String> pkgList = new ArrayList<String>(); ArrayList<SdInstallArgs> failedList = new ArrayList<SdInstallArgs>(); diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 1d20074..10d6e3a 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -27,6 +27,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; import android.net.ConnectivityManager; +import android.net.InterfaceConfiguration; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.NetworkInfo; @@ -56,6 +57,7 @@ import java.util.Set; * * TODO - look for parent classes and code sharing */ + public class Tethering extends INetworkManagementEventObserver.Stub { private Notification mTetheringNotification; @@ -80,6 +82,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private String mUpstreamIfaceName; + // turning on/off RNDIS resets the interface generating and extra discon/conn cycle + // count how many to ignore.. Self correcting if you plug/unplug a bunch of times. + private int mUsbResetExpected = 0; + HierarchicalStateMachine mTetherMasterSM; public Tethering(Context context) { @@ -113,7 +119,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { com.android.internal.R.array.config_tether_dhcp_range); if (mDhcpRange.length == 0) { mDhcpRange = new String[2]; - mDhcpRange[0] = new String("169.254.2.1"); + mDhcpRange[0] = new String("169.254.2.2"); mDhcpRange[1] = new String("169.254.2.64"); } else if(mDhcpRange.length == 1) { String[] tmp = new String[2]; @@ -127,16 +133,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mTetherableWifiRegexs = context.getResources().getStringArray( com.android.internal.R.array.config_tether_wifi_regexs); - String[] ifaces = new String[0]; - try { - ifaces = service.listInterfaces(); - } catch (Exception e) { - Log.e(TAG, "Error listing Interfaces :" + e); - } - for (String iface : ifaces) { - interfaceAdded(iface); - } - // TODO - remove and rely on real notifications of the current iface mDnsServers = new String[2]; mDnsServers[0] = "8.8.8.8"; @@ -147,6 +143,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { public void interfaceLinkStatusChanged(String iface, boolean link) { Log.d(TAG, "interfaceLinkStatusChanged " + iface + ", " + link); boolean found = false; + boolean usb = false; for (String regex : mTetherableWifiRegexs) { if (iface.matches(regex)) { found = true; @@ -156,6 +153,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { for (String regex: mTetherableUsbRegexs) { if (iface.matches(regex)) { found = true; + usb = true; break; } } @@ -165,7 +163,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { TetherInterfaceSM sm = mIfaces.get(iface); if (link) { if (sm == null) { - sm = new TetherInterfaceSM(iface); + sm = new TetherInterfaceSM(iface, usb); mIfaces.put(iface, sm); sm.start(); } @@ -179,7 +177,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } public void interfaceAdded(String iface) { + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); boolean found = false; + boolean usb = false; for (String regex : mTetherableWifiRegexs) { if (iface.matches(regex)) { found = true; @@ -189,6 +190,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { for (String regex : mTetherableUsbRegexs) { if (iface.matches(regex)) { found = true; + usb = true; break; } } @@ -196,13 +198,14 @@ public class Tethering extends INetworkManagementEventObserver.Stub { Log.d(TAG, iface + " is not a tetherable iface, ignoring"); return; } + synchronized (mIfaces) { TetherInterfaceSM sm = mIfaces.get(iface); if (sm != null) { Log.e(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } - sm = new TetherInterfaceSM(iface); + sm = new TetherInterfaceSM(iface, usb); mIfaces.put(iface, sm); sm.start(); } @@ -314,8 +317,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { if (tellUser) { for (Object o : availableList) { String s = (String)o; - for (Object matchObject : mTetherableUsbRegexs) { - if (s.matches((String)matchObject)) { + for (String match : mTetherableUsbRegexs) { + if (s.matches(match)) { showTetherAvailableNotification(); return; } @@ -414,20 +417,32 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - - - private class StateReceiver extends BroadcastReceiver { public void onReceive(Context content, Intent intent) { String action = intent.getAction(); if (action.equals(Intent.ACTION_UMS_CONNECTED)) { - Tethering.this.handleTtyConnect(); + Log.w(TAG, "got UMS connected"); + synchronized (Tethering.this) { + if(mUsbResetExpected != 0) { + Log.w(TAG, "mUsbResetExpected == " + mUsbResetExpected + ", ignored"); + mUsbResetExpected--; + return; + } + } + Tethering.this.toggleUsbIfaces(true); // add them } else if (action.equals(Intent.ACTION_UMS_DISCONNECTED)) { - Tethering.this.handleTtyDisconnect(); + Log.w(TAG, "got UMS disconneded broadcast"); + synchronized (Tethering.this) { + if(mUsbResetExpected != 0) { + Log.w(TAG, "mUsbResetExpected == " + mUsbResetExpected + ", ignored"); + mUsbResetExpected--; + return; + } + } + Tethering.this.toggleUsbIfaces(false); // remove them } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = - IConnectivityManager.Stub.asInterface(b); + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); try { NetworkInfo info = service.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_DUN); int msg; @@ -442,6 +457,114 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } + // used on cable insert/remove + private void toggleUsbIfaces(boolean add) { + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + String[] ifaces = new String[0]; + try { + ifaces = service.listInterfaces(); + } catch (Exception e) { + Log.e(TAG, "Error listing Interfaces :" + e); + return; + } + for (String iface : ifaces) { + for (String regex : mTetherableUsbRegexs) { + if (iface.matches(regex)) { + if (add) { + interfaceAdded(iface); + } else { + interfaceRemoved(iface); + } + } + } + } + } + + // toggled when we enter/leave the fully teathered state + private boolean enableRndisUsb(boolean enabled) { + Log.d(TAG, "enableRndisUsb(" + enabled + ")"); + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + try { + if (enabled) { + // turning this on will reset USB and generate two bogus events - ignore them + synchronized (this) { + if (!service.isUsbRNDISStarted()) { + mUsbResetExpected += 2; + service.startUsbRNDIS(); + } + } + } else { + if (service.isUsbRNDISStarted()) { + service.stopUsbRNDIS(); + } + } + } catch (Exception e) { + Log.e(TAG, "Error toggling usb RNDIS :" + e); + return false; + } + return true; + } + + // configured when we start tethering and unconfig'd on error or conclusion + private boolean configureUsb(boolean enabled) { + Log.d(TAG, "configureUsb(" + enabled + ")"); + + IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); + INetworkManagementService service = INetworkManagementService.Stub.asInterface(b); + + // bring toggle the interfaces + String[] ifaces = new String[0]; + try { + ifaces = service.listInterfaces(); + } catch (Exception e) { + Log.e(TAG, "Error listing Interfaces :" + e); + return false; + } + for (String iface : ifaces) { + for (String regex : mTetherableUsbRegexs) { + if (iface.matches(regex)) { + InterfaceConfiguration ifcg = null; + try { + ifcg = service.getInterfaceConfig(iface); + if (ifcg != null) { + ifcg.ipAddr = (169 << 24) + (254 << 16) + (2 << 8) + 1; + ifcg.netmask = (255 << 24) + (255 << 16) + (255 << 8) + 0; + if (enabled) { + ifcg.interfaceFlags = ifcg.interfaceFlags.replace("down", "up"); + } else { + ifcg.interfaceFlags = ifcg.interfaceFlags.replace("up", "down"); + } + service.setInterfaceConfig(iface, ifcg); + } + } catch (Exception e) { + Log.e(TAG, "Error configuring interface " + iface + ", :" + e); + return false; + } + } + } + } + + if (!enabled) { + // turn off ndis + try { + synchronized (this) { + if (service.isUsbRNDISStarted()) { + mUsbResetExpected += 2; + service.stopUsbRNDIS(); + } + } + } catch (Exception e) { + Log.e(TAG, "Error stopping usb RNDIS :" + e); + return false; + } + } + + return true; + } + private void handleTtyConnect() { Log.d(TAG, "handleTtyConnect"); // for each of the available Tty not already supported by a ppp session, @@ -609,6 +732,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private HierarchicalState mUntetherInterfaceErrorState; private HierarchicalState mEnableNatErrorState; private HierarchicalState mDisableNatErrorState; + private HierarchicalState mUsbConfigurationErrorState; private HierarchicalState mUnavailableState; @@ -617,10 +741,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private boolean mTethered; String mIfaceName; + boolean mUsb; - TetherInterfaceSM(String name) { + TetherInterfaceSM(String name, boolean usb) { super(name); mIfaceName = name; + mUsb = usb; mInitialState = new InitialState(); addState(mInitialState); @@ -638,6 +764,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { addState(mEnableNatErrorState); mDisableNatErrorState = new DisableNatErrorState(); addState(mDisableNatErrorState); + mUsbConfigurationErrorState = new UsbConfigurationErrorState(); + addState(mUsbConfigurationErrorState); mUnavailableState = new UnavailableState(); addState(mUnavailableState); @@ -656,6 +784,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { if (current == mUntetherInterfaceErrorState) res += "UntetherInterfaceErrorState"; if (current == mEnableNatErrorState) res += "EnableNatErrorState"; if (current == mDisableNatErrorState) res += "DisableNatErrorState"; + if (current == mUsbConfigurationErrorState) res += "UsbConfigurationErrorState"; if (current == mUnavailableState) res += "UnavailableState"; if (mAvailable) res += " - Available"; if (mTethered) res += " - Tethered"; @@ -686,8 +815,15 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return mErrored; } - private synchronized void setErrored(boolean errored) { - mErrored = errored; + private void setErrored(boolean errored) { + synchronized (this) { + mErrored = errored; + } + if (errored && mUsb) { + // note everything's been unwound by this point so nothing to do on + // further error.. + Tethering.this.configureUsb(false); + } } class InitialState extends HierarchicalState { @@ -726,6 +862,19 @@ public class Tethering extends INetworkManagementEventObserver.Stub { @Override public void enter() { setAvailable(false); + if (mUsb) { + if (!Tethering.this.configureUsb(true)) { + Message m = mTetherMasterSM.obtainMessage( + TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED); + m.obj = TetherInterfaceSM.this; + mTetherMasterSM.sendMessage(m); + + m = obtainMessage(CMD_TRANSITION_TO_ERROR); + m.obj = mUsbConfigurationErrorState; + sendMessageAtFrontOfQueue(m); + return; + } + } sendTetherStateChangedBroadcast(); } @Override @@ -739,6 +888,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED); m.obj = TetherInterfaceSM.this; mTetherMasterSM.sendMessage(m); + if (mUsb) { + if (!Tethering.this.configureUsb(false)) { + transitionTo(mUsbConfigurationErrorState); + break; + } + } transitionTo(mInitialState); break; case CMD_TETHER_MODE_ALIVE: @@ -759,6 +914,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mTetherMasterSM.sendMessage(m); transitionTo(mUnavailableState); break; + case CMD_TRANSITION_TO_ERROR: + HierarchicalState s = (HierarchicalState)(message.obj); + transitionTo(s); + break; default: retValue = false; } @@ -788,12 +947,17 @@ public class Tethering extends INetworkManagementEventObserver.Stub { sendMessageAtFrontOfQueue(m); return; } + if (mUsb) Tethering.this.enableRndisUsb(true); Log.d(TAG, "Tethered " + mIfaceName); setAvailable(false); setTethered(true); sendTetherStateChangedBroadcast(); } @Override + public void exit() { + if(mUsb) Tethering.this.enableRndisUsb(false); + } + @Override public boolean processMessage(Message message) { Log.d(TAG, "TetheredState.processMessage what=" + message.what); boolean retValue = true; @@ -821,7 +985,15 @@ public class Tethering extends INetworkManagementEventObserver.Stub { m.obj = TetherInterfaceSM.this; mTetherMasterSM.sendMessage(m); if (message.what == CMD_TETHER_UNREQUESTED) { - transitionTo(mInitialState); + if (mUsb) { + if (!Tethering.this.configureUsb(false)) { + transitionTo(mUsbConfigurationErrorState); + } else { + transitionTo(mInitialState); + } + } else { + transitionTo(mInitialState); + } } else if (message.what == CMD_INTERFACE_DOWN) { transitionTo(mUnavailableState); } @@ -857,6 +1029,12 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } Log.d(TAG, "Tether lost upstream connection " + mIfaceName); sendTetherStateChangedBroadcast(); + if (mUsb) { + if (!Tethering.this.configureUsb(false)) { + transitionTo(mUsbConfigurationErrorState); + break; + } + } transitionTo(mInitialState); break; case CMD_TRANSITION_TO_ERROR: @@ -974,6 +1152,15 @@ public class Tethering extends INetworkManagementEventObserver.Stub { sendTetherStateChangedBroadcast(); } } + + class UsbConfigurationErrorState extends ErrorState { + @Override + public void enter() { + Log.e(TAG, "Error trying to configure USB " + mIfaceName); + setAvailable(false); + setErrored(true); + } + } } class TetherMasterSM extends HierarchicalStateMachine { @@ -1179,6 +1366,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { class CellDunAliveState extends HierarchicalState { @Override public void enter() { + Log.d(TAG, "renewing Dun in " + CELL_DUN_RENEW_MS + "ms"); sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS); } @@ -1228,6 +1416,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { transitionTo(mInitialState); break; case CMD_CELL_DUN_RENEW: + Log.d(TAG, "renewing dun connection - requeuing for another " + + CELL_DUN_RENEW_MS + "ms"); b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); IConnectivityManager cservice = IConnectivityManager.Stub.asInterface(b); try { @@ -1248,6 +1438,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { class TetherModeAliveState extends HierarchicalState { @Override public void enter() { + Log.d(TAG, "renewing Dun in " + CELL_DUN_RENEW_MS + "ms"); + sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS); for (Object o : mNotifyList) { TetherInterfaceSM sm = (TetherInterfaceSM)o; sm.sendMessage(sm.obtainMessage(TetherInterfaceSM.CMD_TETHER_MODE_ALIVE)); @@ -1298,6 +1490,18 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } transitionTo(mInitialState); break; + case CMD_CELL_DUN_RENEW: + Log.d(TAG, "renewing dun connection - requeuing for another " + + CELL_DUN_RENEW_MS + "ms"); + b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); + IConnectivityManager cservice = IConnectivityManager.Stub.asInterface(b); + try { + cservice.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + Phone.FEATURE_ENABLE_DUN, new Binder()); + } catch (Exception e) { + } + sendMessageDelayed(obtainMessage(CMD_CELL_DUN_RENEW), CELL_DUN_RENEW_MS); + break; default: retValue = false; break; diff --git a/services/java/com/android/server/status/ExpandedView.java b/services/java/com/android/server/status/ExpandedView.java index d0f14cb..33ac8c1 100644 --- a/services/java/com/android/server/status/ExpandedView.java +++ b/services/java/com/android/server/status/ExpandedView.java @@ -11,17 +11,11 @@ import android.util.Log; public class ExpandedView extends LinearLayout { - final Display mDisplay; StatusBarService mService; - boolean mTracking; - int mStartX, mStartY; - int mMaxHeight = 0; int mPrevHeight = -1; public ExpandedView(Context context, AttributeSet attrs) { super(context, attrs); - mDisplay = ((WindowManager)context.getSystemService( - Context.WINDOW_SERVICE)).getDefaultDisplay(); } @Override @@ -36,12 +30,6 @@ public class ExpandedView extends LinearLayout { } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, - MeasureSpec.makeMeasureSpec(mMaxHeight, MeasureSpec.AT_MOST)); - } - - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); int height = bottom - top; @@ -51,11 +39,4 @@ public class ExpandedView extends LinearLayout { mService.updateExpandedViewPos(StatusBarService.EXPANDED_LEAVE_ALONE); } } - - void setMaxHeight(int h) { - if (h != mMaxHeight) { - mMaxHeight = h; - requestLayout(); - } - } } diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java index 493bd93..3f77291 100644 --- a/services/java/com/android/server/status/StatusBarService.java +++ b/services/java/com/android/server/status/StatusBarService.java @@ -187,8 +187,9 @@ public class StatusBarService extends IStatusBar.Stub TextView mSpnLabel; TextView mPlmnLabel; TextView mClearButton; + View mExpandedContents; CloseDragHandle mCloseView; - int[] mCloseLocation = new int[2]; + int[] mPositionTmp = new int[2]; boolean mExpanded; boolean mExpandedVisible; @@ -198,7 +199,7 @@ public class StatusBarService extends IStatusBar.Stub // the tracker view TrackingView mTrackingView; WindowManager.LayoutParams mTrackingParams; - int mTrackingPosition; + int mTrackingPosition; // the position of the top of the tracking view. // ticker private Ticker mTicker; @@ -274,6 +275,7 @@ public class StatusBarService extends IStatusBar.Stub mExpandedDialog = new ExpandedDialog(context); mExpandedView = expanded; + mExpandedContents = expanded.findViewById(R.id.notificationLinearLayout); mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle); mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems); mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle); @@ -1530,20 +1532,11 @@ public class StatusBarService extends IStatusBar.Stub /// ---------- Expanded View -------------- pixelFormat = PixelFormat.TRANSLUCENT; - bg = mExpandedView.getBackground(); - if (bg != null) { - pixelFormat = bg.getOpacity(); - if (pixelFormat != PixelFormat.TRANSLUCENT) { - // we want good-looking gradients, so we force a 8-bits per - // pixel format. - pixelFormat = PixelFormat.RGBX_8888; - } - } final int disph = mDisplay.getHeight(); lp = mExpandedDialog.getWindow().getAttributes(); lp.width = ViewGroup.LayoutParams.MATCH_PARENT; - lp.height = ViewGroup.LayoutParams.WRAP_CONTENT; + lp.height = getExpandedHeight(); lp.x = 0; mTrackingPosition = lp.y = -disph; // sufficiently large negative lp.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; @@ -1562,10 +1555,10 @@ public class StatusBarService extends IStatusBar.Stub mExpandedDialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); mExpandedDialog.setContentView(mExpandedView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); + ViewGroup.LayoutParams.MATCH_PARENT)); + mExpandedDialog.getWindow().setBackgroundDrawable(null); mExpandedDialog.show(); FrameLayout hack = (FrameLayout)mExpandedView.getParent(); - hack.setForeground(null); } void setDateViewVisibility(boolean visible, int anim) { @@ -1630,11 +1623,15 @@ public class StatusBarService extends IStatusBar.Stub mTrackingParams.height = disph-h; WindowManagerImpl.getDefault().updateViewLayout(mTrackingView, mTrackingParams); - mCloseView.getLocationInWindow(mCloseLocation); - if (mExpandedParams != null) { + mCloseView.getLocationInWindow(mPositionTmp); + final int closePos = mPositionTmp[1]; + + mExpandedContents.getLocationInWindow(mPositionTmp); + final int contentsBottom = mPositionTmp[1] + mExpandedContents.getHeight(); + mExpandedParams.y = pos + mTrackingView.getHeight() - - (mTrackingParams.height-mCloseLocation[1]) - mExpandedView.getHeight(); + - (mTrackingParams.height-closePos) - contentsBottom; int max = h; if (mExpandedParams.y > max) { mExpandedParams.y = max; @@ -1644,13 +1641,13 @@ public class StatusBarService extends IStatusBar.Stub mExpandedParams.y = min; } - /* - Log.d(TAG, "mTrackingPosition=" + mTrackingPosition - + " mTrackingView.height=" + mTrackingView.getHeight() - + " diff=" + (mTrackingPosition + mTrackingView.getHeight()) - + " h=" + h); - */ - panelSlightlyVisible((mTrackingPosition + mTrackingView.getHeight()) > h); + boolean visible = (mTrackingPosition + mTrackingView.getHeight()) > h; + if (!visible) { + // if the contents aren't visible, move the expanded view way off screen + // because the window itself extends below the content view. + mExpandedParams.y = -disph; + } + panelSlightlyVisible(visible); mExpandedDialog.getWindow().setAttributes(mExpandedParams); } @@ -1658,16 +1655,19 @@ public class StatusBarService extends IStatusBar.Stub Log.d(TAG, "updateExpandedViewPos after expandedPosition=" + expandedPosition + " mTrackingParams.y=" + mTrackingParams.y + " mTrackingPosition=" + mTrackingPosition - + " mExpandedParams.y=" + mExpandedParams.y); + + " mExpandedParams.y=" + mExpandedParams.y + + " mExpandedParams.height=" + mExpandedParams.height); } } - void updateAvailableHeight() { + int getExpandedHeight() { + return mDisplay.getHeight() - mStatusBarView.getHeight() - mCloseView.getHeight(); + } + + void updateExpandedHeight() { if (mExpandedView != null) { - int disph = mDisplay.getHeight(); - int h = mStatusBarView.getHeight(); - int max = disph - (mCloseView.getHeight() + h); - mExpandedView.setMaxHeight(max); + mExpandedParams.height = getExpandedHeight(); + mExpandedDialog.getWindow().setAttributes(mExpandedParams); } } diff --git a/services/java/com/android/server/status/TrackingView.java b/services/java/com/android/server/status/TrackingView.java index 722d10c..886d66d 100644 --- a/services/java/com/android/server/status/TrackingView.java +++ b/services/java/com/android/server/status/TrackingView.java @@ -23,7 +23,7 @@ public class TrackingView extends LinearLayout { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - mService.updateAvailableHeight(); + mService.updateExpandedHeight(); } @Override diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java index 52c8b1f..cab7b81 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java @@ -440,10 +440,11 @@ public abstract class DataConnectionTracker extends Handler { return Phone.APN_REQUEST_FAILED; } - if(DBG) Log.d(LOG_TAG, "enableApnType("+type+"), isApnTypeActive = " + if (DBG) Log.d(LOG_TAG, "enableApnType("+type+"), isApnTypeActive = " + isApnTypeActive(type) + " and state = " + state); if (!isApnTypeAvailable(type)) { + if (DBG) Log.d(LOG_TAG, "type not available"); return Phone.APN_TYPE_NOT_AVAILABLE; } diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java index e586010..ee6b89c 100644 --- a/test-runner/src/android/test/InstrumentationTestRunner.java +++ b/test-runner/src/android/test/InstrumentationTestRunner.java @@ -33,6 +33,7 @@ import android.test.suitebuilder.TestMethod; import android.test.suitebuilder.TestPredicates; import android.test.suitebuilder.TestSuiteBuilder; import android.test.suitebuilder.annotation.HasAnnotation; +import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import java.io.ByteArrayOutputStream; diff --git a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java index 3bbb447..e85254d 100755 --- a/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/AsecTests.java @@ -132,6 +132,14 @@ public class AsecTests extends AndroidTestCase { return ms.destroySecureContainer(fullId, force); } + private boolean isContainerMounted(String localId) throws RemoteException { + Assert.assertTrue(isMediaMounted()); + String fullId = "com.android.unittests.AsecTests." + localId; + + IMountService ms = getMs(); + return ms.isSecureContainerMounted(fullId); + } + private IMountService getMs() { IBinder service = ServiceManager.getService("mount"); if (service != null) { @@ -300,7 +308,7 @@ public class AsecTests extends AndroidTestCase { } } - public void testRenameMountedContainer() { + public void testRenameSrcMountedContainer() { try { Assert.assertEquals(StorageResultCode.OperationSucceeded, createContainer("testRenameContainer.1", 4, "none")); @@ -312,12 +320,15 @@ public class AsecTests extends AndroidTestCase { } } - public void testRenameToExistingContainer() { + public void testRenameDstMountedContainer() { try { Assert.assertEquals(StorageResultCode.OperationSucceeded, createContainer("testRenameContainer.1", 4, "none")); Assert.assertEquals(StorageResultCode.OperationSucceeded, + unmountContainer("testRenameContainer.1", false)); + + Assert.assertEquals(StorageResultCode.OperationSucceeded, createContainer("testRenameContainer.2", 4, "none")); Assert.assertEquals(StorageResultCode.OperationFailedStorageMounted, @@ -326,4 +337,25 @@ public class AsecTests extends AndroidTestCase { failStr(e); } } + + public void testIsContainerMountedAfterRename() { + try { + Assert.assertEquals(StorageResultCode.OperationSucceeded, + createContainer("testRenameContainer.1", 4, "none")); + + Assert.assertEquals(StorageResultCode.OperationSucceeded, + unmountContainer("testRenameContainer.1", false)); + + Assert.assertEquals(StorageResultCode.OperationSucceeded, + renameContainer("testRenameContainer.1", "testRenameContainer.2")); + + Assert.assertEquals(false, containerExists("testRenameContainer.1")); + Assert.assertEquals(true, containerExists("testRenameContainer.2")); + // Check if isContainerMounted returns valid value + Assert.assertEquals(true, isContainerMounted("testRenameContainer.2")); + } catch (Exception e) { + failStr(e); + } + } + } diff --git a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java index 9c5c44d..d161a88 100755 --- a/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java +++ b/tests/AndroidTests/src/com/android/unit_tests/PackageManagerTests.java @@ -57,17 +57,25 @@ import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.storage.IMountService; +import android.os.storage.IMountServiceListener; +import android.os.storage.StorageEventListener; +import android.os.storage.StorageManager; import android.os.storage.StorageResultCode; import android.os.RemoteException; import android.os.ServiceManager; import android.os.StatFs; import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; public class PackageManagerTests extends AndroidTestCase { private static final boolean localLOGV = true; public static final String TAG="PackageManagerTests"; public final long MAX_WAIT_TIME=120*1000; public final long WAIT_TIME_INCR=20*1000; + private static final String SECURE_CONTAINERS_PREFIX = "/mnt/asec"; + private static final int APP_INSTALL_AUTO = 0; + private static final int APP_INSTALL_DEVICE = 1; + private static final int APP_INSTALL_SDCARD = 2; void failStr(String errMsg) { Log.w(TAG, "errMsg="+errMsg); @@ -244,9 +252,46 @@ public class PackageManagerTests extends AndroidTestCase { packageParser = null; return pkg; } - - private void assertInstall(String pkgName, int flags) { + private boolean getInstallLoc(int flags, int expInstallLocation) { + // Flags explicitly over ride everything else. + if ((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0 ) { + return false; + } else if ((flags & PackageManager.INSTALL_EXTERNAL) != 0 ) { + return true; + } + // Manifest option takes precedence next + if (expInstallLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) { + return true; + } + // TODO Out of memory checks here. + boolean checkSd = false; + int setLoc = 0; + try { + setLoc = Settings.System.getInt(mContext.getContentResolver(), Settings.System.SET_INSTALL_LOCATION); + } catch (SettingNotFoundException e) { + failStr(e); + } + if (setLoc == 1) { + int userPref = APP_INSTALL_AUTO; + try { + userPref = Settings.System.getInt(mContext.getContentResolver(), Settings.System.DEFAULT_INSTALL_LOCATION); + } catch (SettingNotFoundException e) { + failStr(e); + } + if (userPref == APP_INSTALL_DEVICE) { + checkSd = false; + } else if (userPref == APP_INSTALL_SDCARD) { + checkSd = true; + } else if (userPref == APP_INSTALL_AUTO) { + // Might be determined dynamically. TODO fix this + checkSd = false; + } + } + return checkSd; + } + private void assertInstall(PackageParser.Package pkg, int flags, int expInstallLocation) { try { + String pkgName = pkg.packageName; ApplicationInfo info = getPm().getApplicationInfo(pkgName, 0); assertNotNull(info); assertEquals(pkgName, info.packageName); @@ -264,15 +309,14 @@ public class PackageManagerTests extends AndroidTestCase { assertEquals(publicSrcPath, appInstallPath); } else { assertFalse((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0); - if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { - assertTrue((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0); - // Hardcoded for now - assertTrue(srcPath.startsWith("/asec")); - assertTrue(publicSrcPath.startsWith("/asec")); - } else { + if (!getInstallLoc(flags, expInstallLocation)) { assertEquals(srcPath, appInstallPath); assertEquals(publicSrcPath, appInstallPath); assertFalse((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0); + } else { + assertTrue((info.flags & ApplicationInfo.FLAG_ON_SDCARD) != 0); + assertTrue(srcPath.startsWith(SECURE_CONTAINERS_PREFIX)); + assertTrue(publicSrcPath.startsWith(SECURE_CONTAINERS_PREFIX)); } } } catch (NameNotFoundException e) { @@ -300,7 +344,7 @@ public class PackageManagerTests extends AndroidTestCase { private InstallParams sampleInstallFromRawResource(int flags, boolean cleanUp) { return installFromRawResource("install.apk", R.raw.install, flags, cleanUp, - false, -1); + false, -1, PackageInfo.INSTALL_LOCATION_AUTO); } public void clearSecureContainersForPkg(String pkgName) { @@ -327,7 +371,8 @@ public class PackageManagerTests extends AndroidTestCase { * PackageManager api to install it. */ private InstallParams installFromRawResource(String outFileName, - int rawResId, int flags, boolean cleanUp, boolean fail, int result) { + int rawResId, int flags, boolean cleanUp, boolean fail, int result, + int expInstallLocation) { File filesDir = mContext.getFilesDir(); File outFile = new File(filesDir, outFileName); Uri packageURI = getInstallablePackage(rawResId, outFile); @@ -337,9 +382,7 @@ public class PackageManagerTests extends AndroidTestCase { // Make sure the package doesn't exist getPm().deletePackage(pkg.packageName, null, 0); // Clean up the containers as well - if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) { - clearSecureContainersForPkg(pkg.packageName); - } + clearSecureContainersForPkg(pkg.packageName); try { try { if (fail) { @@ -351,7 +394,7 @@ public class PackageManagerTests extends AndroidTestCase { assertTrue(invokeInstallPackage(packageURI, flags, pkg.packageName, receiver)); // Verify installed information - assertInstall(pkg.packageName, flags); + assertInstall(pkg, flags, expInstallLocation); ip = new InstallParams(pkg, outFileName, packageURI); } } catch (Exception e) { @@ -443,6 +486,7 @@ public class PackageManagerTests extends AndroidTestCase { public void replaceFromRawResource(int flags) { InstallParams ip = sampleInstallFromRawResource(flags, false); boolean replace = ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0); + Log.i(TAG, "replace=" + replace); GenericReceiver receiver; if (replace) { receiver = new ReplaceReceiver(ip.pkg.packageName); @@ -455,7 +499,7 @@ public class PackageManagerTests extends AndroidTestCase { assertEquals(invokeInstallPackage(ip.packageURI, flags, ip.pkg.packageName, receiver), replace); if (replace) { - assertInstall(ip.pkg.packageName, flags); + assertInstall(ip.pkg, flags, ip.pkg.installLocation); } } catch (Exception e) { failStr("Failed with exception : " + e); @@ -738,51 +782,73 @@ public class PackageManagerTests extends AndroidTestCase { } } + class StorageListener extends StorageEventListener { + String oldState; + String newState; + String path; + private boolean doneFlag = false; + @Override + public void onStorageStateChanged(String path, String oldState, String newState) { + if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState); + synchronized (this) { + this.oldState = oldState; + this.newState = newState; + this.path = path; + doneFlag = true; + notifyAll(); + } + } + + public boolean isDone() { + return doneFlag; + } + } + private boolean unmountMedia() { if (!getMediaState()) { return true; } + String path = Environment.getExternalStorageDirectory().toString(); + StorageListener observer = new StorageListener(); + StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE); + sm.registerListener(observer); try { - String mPath = Environment.getExternalStorageDirectory().toString(); - int ret = getMs().unmountVolume(mPath, false); - return ret == StorageResultCode.OperationSucceeded; - } catch (RemoteException e) { - return true; + // Wait on observer + synchronized(observer) { + getMs().unmountVolume(path, false); + long waitTime = 0; + while((!observer.isDone()) && (waitTime < MAX_WAIT_TIME) ) { + observer.wait(WAIT_TIME_INCR); + waitTime += WAIT_TIME_INCR; + } + if(!observer.isDone()) { + throw new Exception("Timed out waiting for packageInstalled callback"); + } + return true; + } + } catch (Exception e) { + return false; + } finally { + sm.unregisterListener(observer); } } - /* - * Install package on sdcard. Unmount and then mount the media. - * (Use PackageManagerService private api for now) - * Make sure the installed package is available. - * STOPSHIP will uncomment when MountService api's to mount/unmount - * are made asynchronous. - */ - public void xxxtestMountSdNormalInternal() { - assertTrue(mountFromRawResource()); - } - private boolean mountFromRawResource() { // Install pkg on sdcard - InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL | - PackageManager.INSTALL_REPLACE_EXISTING, false); + InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, false); if (localLOGV) Log.i(TAG, "Installed pkg on sdcard"); boolean origState = getMediaState(); + boolean registeredReceiver = false; SdMountReceiver receiver = new SdMountReceiver(new String[]{ip.pkg.packageName}); try { if (localLOGV) Log.i(TAG, "Unmounting media"); // Unmount media assertTrue(unmountMedia()); if (localLOGV) Log.i(TAG, "Unmounted media"); - try { - if (localLOGV) Log.i(TAG, "Sleeping for 10 second"); - Thread.sleep(10*1000); - } catch (InterruptedException e) { - failStr(e); - } // Register receiver here PackageManager pm = getPm(); mContext.registerReceiver(receiver, receiver.filter); + registeredReceiver = true; // Wait on receiver synchronized (receiver) { @@ -807,7 +873,7 @@ public class PackageManagerTests extends AndroidTestCase { failStr(e); return false; } finally { - mContext.unregisterReceiver(receiver); + if (registeredReceiver) mContext.unregisterReceiver(receiver); // Restore original media state if (origState) { mountMedia(); @@ -819,6 +885,17 @@ public class PackageManagerTests extends AndroidTestCase { } } + /* + * Install package on sdcard. Unmount and then mount the media. + * (Use PackageManagerService private api for now) + * Make sure the installed package is available. + * STOPSHIP will uncomment when MountService api's to mount/unmount + * are made asynchronous. + */ + public void xxxtestMountSdNormalInternal() { + assertTrue(mountFromRawResource()); + } + void cleanUpInstall(InstallParams ip) { if (ip == null) { return; @@ -834,28 +911,29 @@ public class PackageManagerTests extends AndroidTestCase { public void testManifestInstallLocationInternal() { installFromRawResource("install.apk", R.raw.install_loc_internal, - 0, true, false, -1); + 0, true, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } public void testManifestInstallLocationSdcard() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, - 0, true, false, -1); + 0, true, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); } public void testManifestInstallLocationAuto() { installFromRawResource("install.apk", R.raw.install_loc_auto, - 0, true, false, -1); + 0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO); } public void testManifestInstallLocationUnspecified() { installFromRawResource("install.apk", R.raw.install_loc_unspecified, - 0, true, false, -1); + 0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO); } public void testManifestInstallLocationFwdLockedSdcard() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, PackageManager.INSTALL_FORWARD_LOCK, true, true, - PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION); + PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION, + PackageInfo.INSTALL_LOCATION_AUTO); } public void xxxtestClearAllSecureContainers() { diff --git a/tests/CoreTests/android/core/MiscRegressionTest.java b/tests/CoreTests/android/core/MiscRegressionTest.java index 8fe064c..8281db0 100644 --- a/tests/CoreTests/android/core/MiscRegressionTest.java +++ b/tests/CoreTests/android/core/MiscRegressionTest.java @@ -110,6 +110,16 @@ public class MiscRegressionTest extends TestCase { Logger.global.finest("This has logging Level.FINEST, should become VERBOSE"); } + // Regression test for Issue 5697: + // getContextClassLoader returns a non-application classloader + // http://code.google.com/p/android/issues/detail?id=5697 + // + @MediumTest + public void testJavaContextClassLoader() throws Exception { + Assert.assertNotNull("Must hava a Java context ClassLoader", + Thread.currentThread().getContextClassLoader()); + } + // Regression test for #1045939: Different output for Method.toString() @SmallTest public void testMethodToString() { diff --git a/tests/LocationTracker/res/layout/entrylist_item.xml b/tests/LocationTracker/res/layout/entrylist_item.xml index 8187677..d2a8033 100644 --- a/tests/LocationTracker/res/layout/entrylist_item.xml +++ b/tests/LocationTracker/res/layout/entrylist_item.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <!-- /* - * Copyright (C) 2006-2008 Google Inc. + * Copyright (C) 2006-2008 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. diff --git a/tests/LocationTracker/res/menu/menu.xml b/tests/LocationTracker/res/menu/menu.xml index 94c589a..05d13fd 100644 --- a/tests/LocationTracker/res/menu/menu.xml +++ b/tests/LocationTracker/res/menu/menu.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <!-- /** -* Copyright (c) 2008 Google Inc. +* Copyright (c) 2008 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. diff --git a/tests/LocationTracker/res/values/strings.xml b/tests/LocationTracker/res/values/strings.xml index bb3ea86..ea6bf2d 100644 --- a/tests/LocationTracker/res/values/strings.xml +++ b/tests/LocationTracker/res/values/strings.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <!-- /* -* Copyright (C) 2008 Google Inc. +* Copyright (C) 2008 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. diff --git a/tests/LocationTracker/res/xml/preferences.xml b/tests/LocationTracker/res/xml/preferences.xml index b57837f..61d4817 100755 --- a/tests/LocationTracker/res/xml/preferences.xml +++ b/tests/LocationTracker/res/xml/preferences.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 Google Inc. +<!-- Copyright (C) 2008 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. diff --git a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java index c5b2432..cb77118 100755 --- a/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java +++ b/tests/LocationTracker/src/com/android/locationtracker/SettingsActivity.java @@ -1,5 +1,5 @@ /*
- * Copyright (C) 2008 Google Inc.
+ * Copyright (C) 2008 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.
diff --git a/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java b/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java index 92e7803..98d0a50 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java +++ b/tests/LocationTracker/src/com/android/locationtracker/TrackerActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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. diff --git a/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java b/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java index 4206da7..5b75653 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java +++ b/tests/LocationTracker/src/com/android/locationtracker/TrackerService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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. diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java index 22ddf45..672ce28 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/CSVFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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. diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java b/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java index 1691f27..13226bd 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/DateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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. diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java index d413191..af0b5ed 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/IFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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. diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java b/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java index ef4bbbb..a5e1816 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/KMLFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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 diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java index ad25126..a3838df 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerDataHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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 diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java index 8c961d1..b2741f6 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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 diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java index 0247ef0..55d4d1e 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerListHelper.java @@ -1,5 +1,5 @@ /*
- * Copyright (C) 2008 Google Inc.
+ * Copyright (C) 2008 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
diff --git a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java index ea849e0..88f24c3 100644 --- a/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java +++ b/tests/LocationTracker/src/com/android/locationtracker/data/TrackerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * Copyright (C) 2008 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 diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp index 51afc0a..a09cec0 100644 --- a/tools/aapt/StringPool.cpp +++ b/tools/aapt/StringPool.cpp @@ -25,8 +25,12 @@ void printStringPool(const ResStringPool* pool) const size_t NS = pool->size(); for (size_t s=0; s<NS; s++) { size_t len; - printf("String #%ld: %s\n", s, - String8(pool->stringAt(s, &len)).string()); + const char *str = (const char*)pool->string8At(s, &len); + if (str == NULL) { + str = String8(pool->stringAt(s, &len)).string(); + } + + printf("String #%ld: %s\n", s, str); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java index 73a3986..744bfbe 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java @@ -927,6 +927,7 @@ public final class BridgeContext extends Context { return null; } + @Override public File getExternalCacheDir() { // TODO Auto-generated method stub return null; @@ -964,6 +965,7 @@ public final class BridgeContext extends Context { return null; } + @Override public File getExternalFilesDir(String type) { // TODO Auto-generated method stub return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java index efd222e..6a98780 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java @@ -392,7 +392,8 @@ public final class BridgeTypedArray extends TypedArray { if (s == null) { return defValue; - } else if (s.equals(BridgeConstants.MATCH_PARENT)) { + } else if (s.equals(BridgeConstants.MATCH_PARENT) || + s.equals(BridgeConstants.FILL_PARENT)) { return LayoutParams.MATCH_PARENT; } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { return LayoutParams.WRAP_CONTENT; @@ -460,7 +461,8 @@ public final class BridgeTypedArray extends TypedArray { if (s == null) { return defValue; - } else if (s.equals(BridgeConstants.MATCH_PARENT)) { + } else if (s.equals(BridgeConstants.MATCH_PARENT) || + s.equals(BridgeConstants.FILL_PARENT)) { return LayoutParams.MATCH_PARENT; } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { return LayoutParams.WRAP_CONTENT; diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index afaed24..810e4d2 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -943,7 +943,7 @@ public class WifiStateTracker extends NetworkStateTracker { } else { newDetailedState = DetailedState.FAILED; } - handleDisconnectedState(newDetailedState); + handleDisconnectedState(newDetailedState, true); /** * If we were associated with a network (networkId != -1), * assume we reached this state because of a failed attempt @@ -965,7 +965,7 @@ public class WifiStateTracker extends NetworkStateTracker { } else if (newState == SupplicantState.DISCONNECTED) { mHaveIpAddress = false; if (isDriverStopped() || mDisconnectExpected) { - handleDisconnectedState(DetailedState.DISCONNECTED); + handleDisconnectedState(DetailedState.DISCONNECTED, true); } else { scheduleDisconnect(); } @@ -1072,16 +1072,10 @@ public class WifiStateTracker extends NetworkStateTracker { */ if (wasDisconnectPending) { DetailedState saveState = getNetworkInfo().getDetailedState(); - handleDisconnectedState(DetailedState.DISCONNECTED); + handleDisconnectedState(DetailedState.DISCONNECTED, false); setDetailedStateInternal(saveState); - } else { - /** - * stop DHCP to ensure there is a new IP address - * even if the supplicant transitions without disconnect - * COMPLETED -> ASSOCIATED -> COMPLETED - */ - resetConnections(false); } + configureInterface(); mLastBssid = result.BSSID; mLastSsid = mWifiInfo.getSSID(); @@ -1116,7 +1110,7 @@ public class WifiStateTracker extends NetworkStateTracker { case EVENT_DEFERRED_DISCONNECT: if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) { - handleDisconnectedState(DetailedState.DISCONNECTED); + handleDisconnectedState(DetailedState.DISCONNECTED, true); } break; @@ -1284,13 +1278,15 @@ public class WifiStateTracker extends NetworkStateTracker { * Reset our IP state and send out broadcasts following a disconnect. * @param newState the {@code DetailedState} to set. Should be either * {@code DISCONNECTED} or {@code FAILED}. + * @param disableInterface indicates whether the interface should + * be disabled */ - private void handleDisconnectedState(DetailedState newState) { + private void handleDisconnectedState(DetailedState newState, boolean disableInterface) { if (mDisconnectPending) { cancelDisconnect(); } mDisconnectExpected = false; - resetConnections(true); + resetConnections(disableInterface); setDetailedState(newState); sendNetworkStateChangeBroadcast(mLastBssid); mWifiInfo.setBSSID(null); |
