diff options
19 files changed, 963 insertions, 629 deletions
diff --git a/api/current.txt b/api/current.txt index 882503d..9303937 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2601,6 +2601,7 @@ package android.app { method public void finish(); method public void finishActivity(int); method public void finishActivityFromChild(android.app.Activity, int); + method public void finishAffinity(); method public void finishFromChild(android.app.Activity); method public android.app.ActionBar getActionBar(); method public final android.app.Application getApplication(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 35bc7ff..4add7f4 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4093,6 +4093,36 @@ public class Activity extends ContextThemeWrapper } /** + * Finish this activity as well as all activities immediately below it + * in the current task that have the same affinity. This is typically + * used when an application can be launched on to another task (such as + * from an ACTION_VIEW of a content type it understands) and the user + * has used the up navigation to switch out of the current task and in + * to its own task. In this case, if the user has navigated down into + * any other activities of the second application, all of those should + * be removed from the original task as part of the task switch. + * + * <p>Note that this finish does <em>not</em> allow you to deliver results + * to the previous activity, and an exception will be thrown if you are trying + * to do so.</p> + */ + public void finishAffinity() { + if (mParent != null) { + throw new IllegalStateException("Can not be called from an embedded activity"); + } + if (mResultCode != RESULT_CANCELED || mResultData != null) { + throw new IllegalStateException("Can not be called to deliver a result"); + } + try { + if (ActivityManagerNative.getDefault().finishActivityAffinity(mToken)) { + mFinished = true; + } + } catch (RemoteException e) { + // Empty + } + } + + /** * This is called when a child activity of this one calls its * {@link #finish} method. The default implementation simply calls * finish() on this activity (the parent), finishing the entire group. diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 7dce2d3..7746ca9 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1687,6 +1687,8 @@ public class ActivityManager { return DisplayMetrics.DENSITY_MEDIUM; case DisplayMetrics.DENSITY_MEDIUM: return DisplayMetrics.DENSITY_HIGH; + case DisplayMetrics.DENSITY_TV: + return DisplayMetrics.DENSITY_XHIGH; case DisplayMetrics.DENSITY_HIGH: return DisplayMetrics.DENSITY_XHIGH; case DisplayMetrics.DENSITY_XHIGH: @@ -1696,7 +1698,7 @@ public class ActivityManager { default: // The density is some abnormal value. Return some other // abnormal value that is a reasonable scaling of it. - return (int)(density*1.5f); + return (int)((density*1.5f)+.5f); } } @@ -1723,6 +1725,8 @@ public class ActivityManager { return (size * DisplayMetrics.DENSITY_MEDIUM) / DisplayMetrics.DENSITY_LOW; case DisplayMetrics.DENSITY_MEDIUM: return (size * DisplayMetrics.DENSITY_HIGH) / DisplayMetrics.DENSITY_MEDIUM; + case DisplayMetrics.DENSITY_TV: + return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH; case DisplayMetrics.DENSITY_HIGH: return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH; case DisplayMetrics.DENSITY_XHIGH: @@ -1732,7 +1736,7 @@ public class ActivityManager { default: // The density is some abnormal value. Return some other // abnormal value that is a reasonable scaling of it. - return (int)(size*1.5f); + return (int)((size*1.5f) + .5f); } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 7e1589f..2f2918d 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -218,7 +218,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeInt(result ? 1 : 0); return true; } - + case FINISH_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -243,6 +243,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case FINISH_ACTIVITY_AFFINITY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + boolean res = finishActivityAffinity(token); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + case WILL_ACTIVITY_BE_VISIBLE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -1866,6 +1875,18 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } + public boolean finishActivityAffinity(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(FINISH_ACTIVITY_AFFINITY_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } public boolean willActivityBeVisible(IBinder token) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 3fc2280..a2c7fa4 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -72,6 +72,7 @@ public interface IActivityManager extends IInterface { public boolean finishActivity(IBinder token, int code, Intent data) throws RemoteException; public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException; + public boolean finishActivityAffinity(IBinder token) throws RemoteException; public boolean willActivityBeVisible(IBinder token) throws RemoteException; public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, @@ -590,4 +591,5 @@ public interface IActivityManager extends IInterface { int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145; int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146; int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147; + int FINISH_ACTIVITY_AFFINITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+148; } diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 7b6b54c..c169de4 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -72,7 +72,7 @@ class BrowserFrame extends Handler { private final CallbackProxy mCallbackProxy; private final WebSettingsClassic mSettings; private final Context mContext; - private final WebViewDatabase mDatabase; + private final WebViewDatabaseClassic mDatabase; private final WebViewCore mWebViewCore; /* package */ boolean mLoadInitFromJava; private int mLoadType; @@ -241,7 +241,7 @@ class BrowserFrame extends Handler { mSettings = settings; mContext = context; mCallbackProxy = proxy; - mDatabase = WebViewDatabase.getInstance(appContext); + mDatabase = WebViewDatabaseClassic.getInstance(appContext); mWebViewCore = w; mSearchBox = new SearchBoxImpl(mWebViewCore, mCallbackProxy); @@ -496,8 +496,8 @@ class BrowserFrame extends Handler { if (item != null) { WebAddress uri = new WebAddress(item.getUrl()); String schemePlusHost = uri.getScheme() + uri.getHost(); - String[] up = - mDatabase.getUsernamePassword(schemePlusHost); + String[] up = mDatabase.getUsernamePassword( + schemePlusHost); if (up != null && up[0] != null) { setUsernamePassword(up[0], up[1]); } @@ -809,8 +809,7 @@ class BrowserFrame extends Handler { // non-null username implies that user has // chosen to save password, so update the // recorded password - mDatabase.setUsernamePassword( - schemePlusHost, username, password); + mDatabase.setUsernamePassword(schemePlusHost, username, password); } } else { // CallbackProxy will handle creating the resume diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 901372b..fa3cb20 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -848,25 +848,37 @@ public abstract class WebSettings { } /** - * Configures scripting (such as XmlHttpRequest) access from file scheme URLs - * to any origin. Note, calling this method with a true argument value also - * implies calling setAllowFileAccessFromFileURLs with a true. The default - * value is false for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} - * and higher and true otherwise. + * Sets whether JavaScript running in the context of a file scheme URL + * should be allowed to access content from any origin. This includes + * access to content from other file scheme URLs. See + * {@link #setAllowFileAccessFromFileURLs}. To enable the most restrictive, + * and therefore secure policy, this setting should be disabled. + * <p> + * The default value is true for API level + * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, + * and false for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + * and above. * - * @param flag true if the WebView should allow scripting access from file - * scheme URLs to any origin + * @param flag whether JavaScript running in the context of a file scheme + * URL should be allowed to access content from any origin */ public abstract void setAllowUniversalAccessFromFileURLs(boolean flag); /** - * Configures scripting (such as XmlHttpRequest) access from file scheme URLs - * to file origin. The default value is false for API level - * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} and higher and true - * otherwise. + * Sets whether JavaScript running in the context of a file scheme URL + * should be allowed to access content from other file scheme URLs. To + * enable the most restrictive, and therefore secure policy, this setting + * should be disabled. Note that the value of this setting is ignored if + * the value of {@link #getAllowUniversalAccessFromFileURLs} is true. + * <p> + * The default value is true for API level + * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below, + * and false for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} + * and above. * - * @param flag true if the WebView should allow scripting access from file - * scheme URLs to file origin + * @param flag whether JavaScript running in the context of a file scheme + * URL should be allowed to access content from other file + * scheme URLs */ public abstract void setAllowFileAccessFromFileURLs(boolean flag); @@ -1028,21 +1040,22 @@ public abstract class WebSettings { } /** - * Gets whether scripting access {see @setAllowUniversalAccessFromFileURLs} from - * file URLs to any origin is enabled. + * Gets whether JavaScript running in the context of a file scheme URL can + * access content from any origin. This includes access to content from + * other file scheme URLs. * - * @return true if the WebView allows scripting access from file scheme requests - * to any origin + * @return whether JavaScript running in the context of a file scheme URL + * can access content from any origin * @see #setAllowUniversalAccessFromFileURLs */ public abstract boolean getAllowUniversalAccessFromFileURLs(); /** - * Gets whether scripting access {see @setAllowFileAccessFromFileURLs} from file - * URLs to file origin is enabled. + * Gets whether JavaScript running in the context of a file scheme URL can + * access content from other file scheme URLs. * - * @return true if the WebView allows scripting access from file scheme requests - * to file origin + * @return whether JavaScript running in the context of a file scheme URL + * can access content from other file scheme URLs * @see #setAllowFileAccessFromFileURLs */ public abstract boolean getAllowFileAccessFromFileURLs(); diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 9cf0d54..ca17d31 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -701,7 +701,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // A final CallbackProxy shared by WebViewCore and BrowserFrame. private CallbackProxy mCallbackProxy; - private WebViewDatabase mDatabase; + private WebViewDatabaseClassic mDatabase; // SSL certificate for the main top-level page (if secure) private SslCertificate mCertificate; @@ -1230,7 +1230,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mViewManager = new ViewManager(this); L10nUtils.setApplicationContext(context.getApplicationContext()); mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javaScriptInterfaces); - mDatabase = WebViewDatabase.getInstance(context); + mDatabase = WebViewDatabaseClassic.getInstance(context); mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel mZoomManager = new ZoomManager(this, mCallbackProxy); @@ -1294,6 +1294,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc public WebStorage getWebStorage() { return WebStorageClassic.getInstance(); } + + @Override + public WebViewDatabase getWebViewDatabase(Context context) { + return WebViewDatabaseClassic.getInstance(context); + } } private void onHandleUiEvent(MotionEvent event, int eventType, int flags) { @@ -7192,8 +7197,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc break; } case NEVER_REMEMBER_PASSWORD: { - mDatabase.setUsernamePassword( - msg.getData().getString("host"), null, null); + mDatabase.setUsernamePassword(msg.getData().getString("host"), null, null); ((Message) msg.obj).sendToTarget(); break; } diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 6c35f19..9d10d67 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -16,611 +16,79 @@ package android.webkit; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.Map.Entry; - -import android.content.ContentValues; import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteStatement; -import android.util.Log; +/** + * This class allows developers to determine whether any WebView used in the + * application has stored any of the following types of browsing data and + * to clear any such stored data for all WebViews in the application. + * <ul> + * <li>Username/password pairs entered into web forms</li> + * <li>HTTP authentication username/password pairs</li> + * <li>Data entered into text fields (e.g. for autocomplete suggestions)</li> + * </ul> + */ public class WebViewDatabase { - private static final String DATABASE_FILE = "webview.db"; - private static final String CACHE_DATABASE_FILE = "webviewCache.db"; - - // log tag + // TODO: deprecate/hide this. protected static final String LOGTAG = "webviewdatabase"; - private static final int DATABASE_VERSION = 11; - // 2 -> 3 Modified Cache table to allow cache of redirects - // 3 -> 4 Added Oma-Downloads table - // 4 -> 5 Modified Cache table to support persistent contentLength - // 5 -> 4 Removed Oma-Downoads table - // 5 -> 6 Add INDEX for cache table - // 6 -> 7 Change cache localPath from int to String - // 7 -> 8 Move cache to its own db - // 8 -> 9 Store both scheme and host when storing passwords - // 9 -> 10 Update httpauth table UNIQUE - // 10 -> 11 Drop cookies and cache now managed by the chromium stack, - // and update the form data table to use the new format - // implemented for b/5265606. - - private static WebViewDatabase mInstance = null; - - private static SQLiteDatabase mDatabase = null; - - // synchronize locks - private final Object mPasswordLock = new Object(); - private final Object mFormLock = new Object(); - private final Object mHttpAuthLock = new Object(); - - private static final String mTableNames[] = { - "password", "formurl", "formdata", "httpauth" - }; - - // Table ids (they are index to mTableNames) - private static final int TABLE_PASSWORD_ID = 0; - private static final int TABLE_FORMURL_ID = 1; - private static final int TABLE_FORMDATA_ID = 2; - private static final int TABLE_HTTPAUTH_ID = 3; - - // column id strings for "_id" which can be used by any table - private static final String ID_COL = "_id"; - - private static final String[] ID_PROJECTION = new String[] { - "_id" - }; - - // column id strings for "password" table - private static final String PASSWORD_HOST_COL = "host"; - private static final String PASSWORD_USERNAME_COL = "username"; - private static final String PASSWORD_PASSWORD_COL = "password"; - - // column id strings for "formurl" table - private static final String FORMURL_URL_COL = "url"; - - // column id strings for "formdata" table - private static final String FORMDATA_URLID_COL = "urlid"; - private static final String FORMDATA_NAME_COL = "name"; - private static final String FORMDATA_VALUE_COL = "value"; - - // column id strings for "httpauth" table - private static final String HTTPAUTH_HOST_COL = "host"; - private static final String HTTPAUTH_REALM_COL = "realm"; - private static final String HTTPAUTH_USERNAME_COL = "username"; - private static final String HTTPAUTH_PASSWORD_COL = "password"; - - // Initially true until the background thread completes. - private boolean mInitialized = false; - - private WebViewDatabase(final Context context) { - new Thread() { - @Override - public void run() { - init(context); - } - }.start(); - - // Singleton only, use getInstance() - } - - public static synchronized WebViewDatabase getInstance(Context context) { - if (mInstance == null) { - mInstance = new WebViewDatabase(context); - } - return mInstance; - } - - private synchronized void init(Context context) { - if (mInitialized) { - return; - } - - initDatabase(context); - // Before using the Chromium HTTP stack, we stored the WebKit cache in - // our own DB. Clean up the DB file if it's still around. - context.deleteDatabase(CACHE_DATABASE_FILE); - - // Thread done, notify. - mInitialized = true; - notify(); - } - - private void initDatabase(Context context) { - try { - mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null); - } catch (SQLiteException e) { - // try again by deleting the old db and create a new one - if (context.deleteDatabase(DATABASE_FILE)) { - mDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, - null); - } - } - - // mDatabase should not be null, - // the only case is RequestAPI test has problem to create db - if (mDatabase == null) { - mInitialized = true; - notify(); - return; - } - - if (mDatabase.getVersion() != DATABASE_VERSION) { - mDatabase.beginTransactionNonExclusive(); - try { - upgradeDatabase(); - mDatabase.setTransactionSuccessful(); - } finally { - mDatabase.endTransaction(); - } - } - } - - private static void upgradeDatabase() { - upgradeDatabaseToV10(); - upgradeDatabaseFromV10ToV11(); - // Add future database upgrade functions here, one version at a - // time. - mDatabase.setVersion(DATABASE_VERSION); - } - - private static void upgradeDatabaseFromV10ToV11() { - int oldVersion = mDatabase.getVersion(); - - if (oldVersion >= 11) { - // Nothing to do. - return; - } - - // Clear out old java stack cookies - this data is now stored in - // a separate database managed by the Chrome stack. - mDatabase.execSQL("DROP TABLE IF EXISTS cookies"); - - // Likewise for the old cache table. - mDatabase.execSQL("DROP TABLE IF EXISTS cache"); - - // Update form autocomplete URLs to match new ICS formatting. - Cursor c = mDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null, - null, null, null, null); - while (c.moveToNext()) { - String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL))); - String url = c.getString(c.getColumnIndex(FORMURL_URL_COL)); - ContentValues cv = new ContentValues(1); - cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url)); - mDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?", - new String[] { urlId }); - } - c.close(); - } - - private static void upgradeDatabaseToV10() { - int oldVersion = mDatabase.getVersion(); - - if (oldVersion >= 10) { - // Nothing to do. - return; - } - - if (oldVersion != 0) { - Log.i(LOGTAG, "Upgrading database from version " - + oldVersion + " to " - + DATABASE_VERSION + ", which will destroy old data"); - } - - if (9 == oldVersion) { - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_HTTPAUTH_ID]); - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL - + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " - + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" - + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL - + ") ON CONFLICT REPLACE);"); - return; - } - - mDatabase.execSQL("DROP TABLE IF EXISTS cookies"); - mDatabase.execSQL("DROP TABLE IF EXISTS cache"); - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_FORMURL_ID]); - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_FORMDATA_ID]); - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_HTTPAUTH_ID]); - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_PASSWORD_ID]); - - // formurl - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL - + " TEXT" + ");"); - - // formdata - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL - + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" - + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " - + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); - - // httpauth - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL - + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " - + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" - + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL - + ") ON CONFLICT REPLACE);"); - // passwords - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL - + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE (" - + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL - + ") ON CONFLICT REPLACE);"); - } - - // Wait for the background initialization thread to complete and check the - // database creation status. - private boolean checkInitialized() { - synchronized (this) { - while (!mInitialized) { - try { - wait(); - } catch (InterruptedException e) { - Log.e(LOGTAG, "Caught exception while checking " + - "initialization"); - Log.e(LOGTAG, Log.getStackTraceString(e)); - } - } - } - return mDatabase != null; - } - - private boolean hasEntries(int tableId) { - if (!checkInitialized()) { - return false; - } - - Cursor cursor = null; - boolean ret = false; - try { - cursor = mDatabase.query(mTableNames[tableId], ID_PROJECTION, - null, null, null, null, null); - ret = cursor.moveToFirst() == true; - } catch (IllegalStateException e) { - Log.e(LOGTAG, "hasEntries", e); - } finally { - if (cursor != null) cursor.close(); - } - return ret; - } - - // - // password functions - // - /** - * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique. - * - * @param schemePlusHost The scheme and host for the password - * @param username The username for the password. If it is null, it means - * password can't be saved. - * @param password The password + * @hide Only for use by WebViewProvider implementations. */ - void setUsernamePassword(String schemePlusHost, String username, - String password) { - if (schemePlusHost == null || !checkInitialized()) { - return; - } - - synchronized (mPasswordLock) { - final ContentValues c = new ContentValues(); - c.put(PASSWORD_HOST_COL, schemePlusHost); - c.put(PASSWORD_USERNAME_COL, username); - c.put(PASSWORD_PASSWORD_COL, password); - mDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL, - c); - } + protected WebViewDatabase() { } - /** - * Retrieve the username and password for a given host - * - * @param schemePlusHost The scheme and host which passwords applies to - * @return String[] if found, String[0] is username, which can be null and - * String[1] is password. Return null if it can't find anything. - */ - String[] getUsernamePassword(String schemePlusHost) { - if (schemePlusHost == null || !checkInitialized()) { - return null; - } - - final String[] columns = new String[] { - PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL - }; - final String selection = "(" + PASSWORD_HOST_COL + " == ?)"; - synchronized (mPasswordLock) { - String[] ret = null; - Cursor cursor = null; - try { - cursor = mDatabase.query(mTableNames[TABLE_PASSWORD_ID], - columns, selection, new String[] { schemePlusHost }, null, - null, null); - if (cursor.moveToFirst()) { - ret = new String[2]; - ret[0] = cursor.getString( - cursor.getColumnIndex(PASSWORD_USERNAME_COL)); - ret[1] = cursor.getString( - cursor.getColumnIndex(PASSWORD_PASSWORD_COL)); - } - } catch (IllegalStateException e) { - Log.e(LOGTAG, "getUsernamePassword", e); - } finally { - if (cursor != null) cursor.close(); - } - return ret; - } + public static synchronized WebViewDatabase getInstance(Context context) { + return WebViewFactory.getProvider().getWebViewDatabase(context); } /** - * Find out if there are any passwords saved. + * Gets whether there are any username/password combinations + * from web pages saved. * - * @return TRUE if there is passwords saved + * @return true if there are any username/passwords used in web + * forms saved */ public boolean hasUsernamePassword() { - synchronized (mPasswordLock) { - return hasEntries(TABLE_PASSWORD_ID); - } + throw new MustOverrideException(); } /** - * Clear password database + * Clears any username/password combinations saved from web forms. */ public void clearUsernamePassword() { - if (!checkInitialized()) { - return; - } - - synchronized (mPasswordLock) { - mDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null); - } - } - - // - // http authentication password functions - // - - /** - * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, - * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique. - * - * @param host The host for the password - * @param realm The realm for the password - * @param username The username for the password. If it is null, it means - * password can't be saved. - * @param password The password - */ - void setHttpAuthUsernamePassword(String host, String realm, String username, - String password) { - if (host == null || realm == null || !checkInitialized()) { - return; - } - - synchronized (mHttpAuthLock) { - final ContentValues c = new ContentValues(); - c.put(HTTPAUTH_HOST_COL, host); - c.put(HTTPAUTH_REALM_COL, realm); - c.put(HTTPAUTH_USERNAME_COL, username); - c.put(HTTPAUTH_PASSWORD_COL, password); - mDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL, - c); - } + throw new MustOverrideException(); } /** - * Retrieve the HTTP authentication username and password for a given - * host+realm pair + * Gets whether there are any HTTP authentication username/password combinations saved. * - * @param host The host the password applies to - * @param realm The realm the password applies to - * @return String[] if found, String[0] is username, which can be null and - * String[1] is password. Return null if it can't find anything. - */ - String[] getHttpAuthUsernamePassword(String host, String realm) { - if (host == null || realm == null || !checkInitialized()){ - return null; - } - - final String[] columns = new String[] { - HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL - }; - final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND (" - + HTTPAUTH_REALM_COL + " == ?)"; - synchronized (mHttpAuthLock) { - String[] ret = null; - Cursor cursor = null; - try { - cursor = mDatabase.query(mTableNames[TABLE_HTTPAUTH_ID], - columns, selection, new String[] { host, realm }, null, - null, null); - if (cursor.moveToFirst()) { - ret = new String[2]; - ret[0] = cursor.getString( - cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)); - ret[1] = cursor.getString( - cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)); - } - } catch (IllegalStateException e) { - Log.e(LOGTAG, "getHttpAuthUsernamePassword", e); - } finally { - if (cursor != null) cursor.close(); - } - return ret; - } - } - - /** - * Find out if there are any HTTP authentication passwords saved. - * - * @return TRUE if there are passwords saved + * @return true if there are any HTTP authentication username/passwords saved */ public boolean hasHttpAuthUsernamePassword() { - synchronized (mHttpAuthLock) { - return hasEntries(TABLE_HTTPAUTH_ID); - } + throw new MustOverrideException(); } /** - * Clear HTTP authentication password database + * Clears any HTTP authentication username/passwords that are saved. */ public void clearHttpAuthUsernamePassword() { - if (!checkInitialized()) { - return; - } - - synchronized (mHttpAuthLock) { - mDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null); - } - } - - // - // form data functions - // - - /** - * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL, - * FORMDATA_VALUE_COL) is unique - * - * @param url The url of the site - * @param formdata The form data in HashMap - */ - void setFormData(String url, HashMap<String, String> formdata) { - if (url == null || formdata == null || !checkInitialized()) { - return; - } - - final String selection = "(" + FORMURL_URL_COL + " == ?)"; - synchronized (mFormLock) { - long urlid = -1; - Cursor cursor = null; - try { - cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], - ID_PROJECTION, selection, new String[] { url }, null, null, - null); - if (cursor.moveToFirst()) { - urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); - } else { - ContentValues c = new ContentValues(); - c.put(FORMURL_URL_COL, url); - urlid = mDatabase.insert( - mTableNames[TABLE_FORMURL_ID], null, c); - } - } catch (IllegalStateException e) { - Log.e(LOGTAG, "setFormData", e); - } finally { - if (cursor != null) cursor.close(); - } - if (urlid >= 0) { - Set<Entry<String, String>> set = formdata.entrySet(); - Iterator<Entry<String, String>> iter = set.iterator(); - ContentValues map = new ContentValues(); - map.put(FORMDATA_URLID_COL, urlid); - while (iter.hasNext()) { - Entry<String, String> entry = iter.next(); - map.put(FORMDATA_NAME_COL, entry.getKey()); - map.put(FORMDATA_VALUE_COL, entry.getValue()); - mDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map); - } - } - } + throw new MustOverrideException(); } /** - * Get all the values for a form entry with "name" in a given site + * Gets whether there is any previously-entered form data saved. * - * @param url The url of the site - * @param name The name of the form entry - * @return A list of values. Return empty list if nothing is found. - */ - ArrayList<String> getFormData(String url, String name) { - ArrayList<String> values = new ArrayList<String>(); - if (url == null || name == null || !checkInitialized()) { - return values; - } - - final String urlSelection = "(" + FORMURL_URL_COL + " == ?)"; - final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND (" - + FORMDATA_NAME_COL + " == ?)"; - synchronized (mFormLock) { - Cursor cursor = null; - try { - cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], - ID_PROJECTION, urlSelection, new String[] { url }, null, - null, null); - while (cursor.moveToNext()) { - long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); - Cursor dataCursor = null; - try { - dataCursor = mDatabase.query( - mTableNames[TABLE_FORMDATA_ID], - new String[] { ID_COL, FORMDATA_VALUE_COL }, - dataSelection, - new String[] { Long.toString(urlid), name }, - null, null, null); - if (dataCursor.moveToFirst()) { - int valueCol = dataCursor.getColumnIndex( - FORMDATA_VALUE_COL); - do { - values.add(dataCursor.getString(valueCol)); - } while (dataCursor.moveToNext()); - } - } catch (IllegalStateException e) { - Log.e(LOGTAG, "getFormData dataCursor", e); - } finally { - if (dataCursor != null) dataCursor.close(); - } - } - } catch (IllegalStateException e) { - Log.e(LOGTAG, "getFormData cursor", e); - } finally { - if (cursor != null) cursor.close(); - } - return values; - } - } - - /** - * Find out if there is form data saved. - * - * @return TRUE if there is form data in the database + * @return true if there is form data saved */ public boolean hasFormData() { - synchronized (mFormLock) { - return hasEntries(TABLE_FORMURL_ID); - } + throw new MustOverrideException(); } /** - * Clear form database + * Clears any stored previously-entered form data. */ public void clearFormData() { - if (!checkInitialized()) { - return; - } - - synchronized (mFormLock) { - mDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null); - mDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null); - } + throw new MustOverrideException(); } } diff --git a/core/java/android/webkit/WebViewDatabaseClassic.java b/core/java/android/webkit/WebViewDatabaseClassic.java new file mode 100644 index 0000000..9b1d4cb --- /dev/null +++ b/core/java/android/webkit/WebViewDatabaseClassic.java @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2012 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.webkit; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.Map.Entry; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteStatement; +import android.util.Log; + +final class WebViewDatabaseClassic extends WebViewDatabase { + private static final String LOGTAG = "WebViewDatabaseClassic"; + private static final String DATABASE_FILE = "webview.db"; + private static final String CACHE_DATABASE_FILE = "webviewCache.db"; + + private static final int DATABASE_VERSION = 11; + // 2 -> 3 Modified Cache table to allow cache of redirects + // 3 -> 4 Added Oma-Downloads table + // 4 -> 5 Modified Cache table to support persistent contentLength + // 5 -> 4 Removed Oma-Downoads table + // 5 -> 6 Add INDEX for cache table + // 6 -> 7 Change cache localPath from int to String + // 7 -> 8 Move cache to its own db + // 8 -> 9 Store both scheme and host when storing passwords + // 9 -> 10 Update httpauth table UNIQUE + // 10 -> 11 Drop cookies and cache now managed by the chromium stack, + // and update the form data table to use the new format + // implemented for b/5265606. + + private static WebViewDatabaseClassic sInstance = null; + + private static SQLiteDatabase sDatabase = null; + + // synchronize locks + private final Object mPasswordLock = new Object(); + private final Object mFormLock = new Object(); + private final Object mHttpAuthLock = new Object(); + + private static final String mTableNames[] = { + "password", "formurl", "formdata", "httpauth" + }; + + // Table ids (they are index to mTableNames) + private static final int TABLE_PASSWORD_ID = 0; + private static final int TABLE_FORMURL_ID = 1; + private static final int TABLE_FORMDATA_ID = 2; + private static final int TABLE_HTTPAUTH_ID = 3; + + // column id strings for "_id" which can be used by any table + private static final String ID_COL = "_id"; + + private static final String[] ID_PROJECTION = new String[] { + "_id" + }; + + // column id strings for "password" table + private static final String PASSWORD_HOST_COL = "host"; + private static final String PASSWORD_USERNAME_COL = "username"; + private static final String PASSWORD_PASSWORD_COL = "password"; + + // column id strings for "formurl" table + private static final String FORMURL_URL_COL = "url"; + + // column id strings for "formdata" table + private static final String FORMDATA_URLID_COL = "urlid"; + private static final String FORMDATA_NAME_COL = "name"; + private static final String FORMDATA_VALUE_COL = "value"; + + // column id strings for "httpauth" table + private static final String HTTPAUTH_HOST_COL = "host"; + private static final String HTTPAUTH_REALM_COL = "realm"; + private static final String HTTPAUTH_USERNAME_COL = "username"; + private static final String HTTPAUTH_PASSWORD_COL = "password"; + + // Initially true until the background thread completes. + private boolean mInitialized = false; + + WebViewDatabaseClassic(final Context context) { + new Thread() { + @Override + public void run() { + init(context); + } + }.start(); + + // Singleton only, use getInstance() + } + + public static synchronized WebViewDatabaseClassic getInstance(Context context) { + if (sInstance == null) { + sInstance = new WebViewDatabaseClassic(context); + } + return sInstance; + } + + private synchronized void init(Context context) { + if (mInitialized) { + return; + } + + initDatabase(context); + // Before using the Chromium HTTP stack, we stored the WebKit cache in + // our own DB. Clean up the DB file if it's still around. + context.deleteDatabase(CACHE_DATABASE_FILE); + + // Thread done, notify. + mInitialized = true; + notify(); + } + + private void initDatabase(Context context) { + try { + sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, null); + } catch (SQLiteException e) { + // try again by deleting the old db and create a new one + if (context.deleteDatabase(DATABASE_FILE)) { + sDatabase = context.openOrCreateDatabase(DATABASE_FILE, 0, + null); + } + } + + // sDatabase should not be null, + // the only case is RequestAPI test has problem to create db + if (sDatabase == null) { + mInitialized = true; + notify(); + return; + } + + if (sDatabase.getVersion() != DATABASE_VERSION) { + sDatabase.beginTransactionNonExclusive(); + try { + upgradeDatabase(); + sDatabase.setTransactionSuccessful(); + } finally { + sDatabase.endTransaction(); + } + } + } + + private static void upgradeDatabase() { + upgradeDatabaseToV10(); + upgradeDatabaseFromV10ToV11(); + // Add future database upgrade functions here, one version at a + // time. + sDatabase.setVersion(DATABASE_VERSION); + } + + private static void upgradeDatabaseFromV10ToV11() { + int oldVersion = sDatabase.getVersion(); + + if (oldVersion >= 11) { + // Nothing to do. + return; + } + + // Clear out old java stack cookies - this data is now stored in + // a separate database managed by the Chrome stack. + sDatabase.execSQL("DROP TABLE IF EXISTS cookies"); + + // Likewise for the old cache table. + sDatabase.execSQL("DROP TABLE IF EXISTS cache"); + + // Update form autocomplete URLs to match new ICS formatting. + Cursor c = sDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null, + null, null, null, null); + while (c.moveToNext()) { + String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL))); + String url = c.getString(c.getColumnIndex(FORMURL_URL_COL)); + ContentValues cv = new ContentValues(1); + cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url)); + sDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?", + new String[] { urlId }); + } + c.close(); + } + + private static void upgradeDatabaseToV10() { + int oldVersion = sDatabase.getVersion(); + + if (oldVersion >= 10) { + // Nothing to do. + return; + } + + if (oldVersion != 0) { + Log.i(LOGTAG, "Upgrading database from version " + + oldVersion + " to " + + DATABASE_VERSION + ", which will destroy old data"); + } + + if (9 == oldVersion) { + sDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_HTTPAUTH_ID]); + sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL + + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " + + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" + + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL + + ") ON CONFLICT REPLACE);"); + return; + } + + sDatabase.execSQL("DROP TABLE IF EXISTS cookies"); + sDatabase.execSQL("DROP TABLE IF EXISTS cache"); + sDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_FORMURL_ID]); + sDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_FORMDATA_ID]); + sDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_HTTPAUTH_ID]); + sDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_PASSWORD_ID]); + + // formurl + sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL + + " TEXT" + ");"); + + // formdata + sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL + + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" + + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " + + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); + + // httpauth + sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL + + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " + + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" + + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL + + ") ON CONFLICT REPLACE);"); + // passwords + sDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + + PASSWORD_HOST_COL + " TEXT, " + PASSWORD_USERNAME_COL + + " TEXT, " + PASSWORD_PASSWORD_COL + " TEXT," + " UNIQUE (" + + PASSWORD_HOST_COL + ", " + PASSWORD_USERNAME_COL + + ") ON CONFLICT REPLACE);"); + } + + // Wait for the background initialization thread to complete and check the + // database creation status. + private boolean checkInitialized() { + synchronized (this) { + while (!mInitialized) { + try { + wait(); + } catch (InterruptedException e) { + Log.e(LOGTAG, "Caught exception while checking " + + "initialization"); + Log.e(LOGTAG, Log.getStackTraceString(e)); + } + } + } + return sDatabase != null; + } + + private boolean hasEntries(int tableId) { + if (!checkInitialized()) { + return false; + } + + Cursor cursor = null; + boolean ret = false; + try { + cursor = sDatabase.query(mTableNames[tableId], ID_PROJECTION, + null, null, null, null, null); + ret = cursor.moveToFirst() == true; + } catch (IllegalStateException e) { + Log.e(LOGTAG, "hasEntries", e); + } finally { + if (cursor != null) cursor.close(); + } + return ret; + } + + // + // password functions + // + + /** + * Set password. Tuple (PASSWORD_HOST_COL, PASSWORD_USERNAME_COL) is unique. + * + * @param schemePlusHost The scheme and host for the password + * @param username The username for the password. If it is null, it means + * password can't be saved. + * @param password The password + */ + void setUsernamePassword(String schemePlusHost, String username, + String password) { + if (schemePlusHost == null || !checkInitialized()) { + return; + } + + synchronized (mPasswordLock) { + final ContentValues c = new ContentValues(); + c.put(PASSWORD_HOST_COL, schemePlusHost); + c.put(PASSWORD_USERNAME_COL, username); + c.put(PASSWORD_PASSWORD_COL, password); + sDatabase.insert(mTableNames[TABLE_PASSWORD_ID], PASSWORD_HOST_COL, + c); + } + } + + /** + * Retrieve the username and password for a given host + * + * @param schemePlusHost The scheme and host which passwords applies to + * @return String[] if found, String[0] is username, which can be null and + * String[1] is password. Return null if it can't find anything. + */ + String[] getUsernamePassword(String schemePlusHost) { + if (schemePlusHost == null || !checkInitialized()) { + return null; + } + + final String[] columns = new String[] { + PASSWORD_USERNAME_COL, PASSWORD_PASSWORD_COL + }; + final String selection = "(" + PASSWORD_HOST_COL + " == ?)"; + synchronized (mPasswordLock) { + String[] ret = null; + Cursor cursor = null; + try { + cursor = sDatabase.query(mTableNames[TABLE_PASSWORD_ID], + columns, selection, new String[] { schemePlusHost }, null, + null, null); + if (cursor.moveToFirst()) { + ret = new String[2]; + ret[0] = cursor.getString( + cursor.getColumnIndex(PASSWORD_USERNAME_COL)); + ret[1] = cursor.getString( + cursor.getColumnIndex(PASSWORD_PASSWORD_COL)); + } + } catch (IllegalStateException e) { + Log.e(LOGTAG, "getUsernamePassword", e); + } finally { + if (cursor != null) cursor.close(); + } + return ret; + } + } + + /** + * @see WebViewDatabase#hasUsernamePassword + */ + @Override + public boolean hasUsernamePassword() { + synchronized (mPasswordLock) { + return hasEntries(TABLE_PASSWORD_ID); + } + } + + /** + * @see WebViewDatabase#clearUsernamePassword + */ + @Override + public void clearUsernamePassword() { + if (!checkInitialized()) { + return; + } + + synchronized (mPasswordLock) { + sDatabase.delete(mTableNames[TABLE_PASSWORD_ID], null, null); + } + } + + // + // http authentication password functions + // + + /** + * Set HTTP authentication password. Tuple (HTTPAUTH_HOST_COL, + * HTTPAUTH_REALM_COL, HTTPAUTH_USERNAME_COL) is unique. + * + * @param host The host for the password + * @param realm The realm for the password + * @param username The username for the password. If it is null, it means + * password can't be saved. + * @param password The password + */ + void setHttpAuthUsernamePassword(String host, String realm, String username, + String password) { + if (host == null || realm == null || !checkInitialized()) { + return; + } + + synchronized (mHttpAuthLock) { + final ContentValues c = new ContentValues(); + c.put(HTTPAUTH_HOST_COL, host); + c.put(HTTPAUTH_REALM_COL, realm); + c.put(HTTPAUTH_USERNAME_COL, username); + c.put(HTTPAUTH_PASSWORD_COL, password); + sDatabase.insert(mTableNames[TABLE_HTTPAUTH_ID], HTTPAUTH_HOST_COL, + c); + } + } + + /** + * Retrieve the HTTP authentication username and password for a given + * host+realm pair + * + * @param host The host the password applies to + * @param realm The realm the password applies to + * @return String[] if found, String[0] is username, which can be null and + * String[1] is password. Return null if it can't find anything. + */ + String[] getHttpAuthUsernamePassword(String host, String realm) { + if (host == null || realm == null || !checkInitialized()){ + return null; + } + + final String[] columns = new String[] { + HTTPAUTH_USERNAME_COL, HTTPAUTH_PASSWORD_COL + }; + final String selection = "(" + HTTPAUTH_HOST_COL + " == ?) AND (" + + HTTPAUTH_REALM_COL + " == ?)"; + synchronized (mHttpAuthLock) { + String[] ret = null; + Cursor cursor = null; + try { + cursor = sDatabase.query(mTableNames[TABLE_HTTPAUTH_ID], + columns, selection, new String[] { host, realm }, null, + null, null); + if (cursor.moveToFirst()) { + ret = new String[2]; + ret[0] = cursor.getString( + cursor.getColumnIndex(HTTPAUTH_USERNAME_COL)); + ret[1] = cursor.getString( + cursor.getColumnIndex(HTTPAUTH_PASSWORD_COL)); + } + } catch (IllegalStateException e) { + Log.e(LOGTAG, "getHttpAuthUsernamePassword", e); + } finally { + if (cursor != null) cursor.close(); + } + return ret; + } + } + + /** + * @see WebViewDatabase#hasHttpAuthUsernamePassword + */ + @Override + public boolean hasHttpAuthUsernamePassword() { + synchronized (mHttpAuthLock) { + return hasEntries(TABLE_HTTPAUTH_ID); + } + } + + /** + * @see WebViewDatabase#clearHttpAuthUsernamePassword + */ + @Override + public void clearHttpAuthUsernamePassword() { + if (!checkInitialized()) { + return; + } + + synchronized (mHttpAuthLock) { + sDatabase.delete(mTableNames[TABLE_HTTPAUTH_ID], null, null); + } + } + + // + // form data functions + // + + /** + * Set form data for a site. Tuple (FORMDATA_URLID_COL, FORMDATA_NAME_COL, + * FORMDATA_VALUE_COL) is unique + * + * @param url The url of the site + * @param formdata The form data in HashMap + */ + void setFormData(String url, HashMap<String, String> formdata) { + if (url == null || formdata == null || !checkInitialized()) { + return; + } + + final String selection = "(" + FORMURL_URL_COL + " == ?)"; + synchronized (mFormLock) { + long urlid = -1; + Cursor cursor = null; + try { + cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID], + ID_PROJECTION, selection, new String[] { url }, null, null, + null); + if (cursor.moveToFirst()) { + urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); + } else { + ContentValues c = new ContentValues(); + c.put(FORMURL_URL_COL, url); + urlid = sDatabase.insert( + mTableNames[TABLE_FORMURL_ID], null, c); + } + } catch (IllegalStateException e) { + Log.e(LOGTAG, "setFormData", e); + } finally { + if (cursor != null) cursor.close(); + } + if (urlid >= 0) { + Set<Entry<String, String>> set = formdata.entrySet(); + Iterator<Entry<String, String>> iter = set.iterator(); + ContentValues map = new ContentValues(); + map.put(FORMDATA_URLID_COL, urlid); + while (iter.hasNext()) { + Entry<String, String> entry = iter.next(); + map.put(FORMDATA_NAME_COL, entry.getKey()); + map.put(FORMDATA_VALUE_COL, entry.getValue()); + sDatabase.insert(mTableNames[TABLE_FORMDATA_ID], null, map); + } + } + } + } + + /** + * Get all the values for a form entry with "name" in a given site + * + * @param url The url of the site + * @param name The name of the form entry + * @return A list of values. Return empty list if nothing is found. + */ + ArrayList<String> getFormData(String url, String name) { + ArrayList<String> values = new ArrayList<String>(); + if (url == null || name == null || !checkInitialized()) { + return values; + } + + final String urlSelection = "(" + FORMURL_URL_COL + " == ?)"; + final String dataSelection = "(" + FORMDATA_URLID_COL + " == ?) AND (" + + FORMDATA_NAME_COL + " == ?)"; + synchronized (mFormLock) { + Cursor cursor = null; + try { + cursor = sDatabase.query(mTableNames[TABLE_FORMURL_ID], + ID_PROJECTION, urlSelection, new String[] { url }, null, + null, null); + while (cursor.moveToNext()) { + long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); + Cursor dataCursor = null; + try { + dataCursor = sDatabase.query( + mTableNames[TABLE_FORMDATA_ID], + new String[] { ID_COL, FORMDATA_VALUE_COL }, + dataSelection, + new String[] { Long.toString(urlid), name }, + null, null, null); + if (dataCursor.moveToFirst()) { + int valueCol = dataCursor.getColumnIndex( + FORMDATA_VALUE_COL); + do { + values.add(dataCursor.getString(valueCol)); + } while (dataCursor.moveToNext()); + } + } catch (IllegalStateException e) { + Log.e(LOGTAG, "getFormData dataCursor", e); + } finally { + if (dataCursor != null) dataCursor.close(); + } + } + } catch (IllegalStateException e) { + Log.e(LOGTAG, "getFormData cursor", e); + } finally { + if (cursor != null) cursor.close(); + } + return values; + } + } + + /** + * @see WebViewDatabase#hasFormData + */ + @Override + public boolean hasFormData() { + synchronized (mFormLock) { + return hasEntries(TABLE_FORMURL_ID); + } + } + + /** + * @see WebViewDatabase#clearFormData + */ + @Override + public void clearFormData() { + if (!checkInitialized()) { + return; + } + + synchronized (mFormLock) { + sDatabase.delete(mTableNames[TABLE_FORMURL_ID], null, null); + sDatabase.delete(mTableNames[TABLE_FORMDATA_ID], null, null); + } + } +} diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index a832b0a..1d302f1 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -16,6 +16,8 @@ package android.webkit; +import android.content.Context; + /** * This is the main entry-point into the WebView back end implementations, which the WebView * proxy class uses to instantiate all the other objects as needed. The backend must provide an @@ -63,21 +65,32 @@ public interface WebViewFactoryProvider { /** * Gets the singleton CookieManager instance for this WebView implementation. The * implementation must return the same instance on subsequent calls. - * @return the singleton CookieManager instance. + * + * @return the singleton CookieManager instance */ CookieManager getCookieManager(); /** * Gets the singleton WebIconDatabase instance for this WebView implementation. The * implementation must return the same instance on subsequent calls. - * @return the singleton WebIconDatabase instance. + * + * @return the singleton WebIconDatabase instance */ WebIconDatabase getWebIconDatabase(); /** * Gets the singleton WebStorage instance for this WebView implementation. The * implementation must return the same instance on subsequent calls. - * @return the singleton WebStorage instance. + * + * @return the singleton WebStorage instance */ WebStorage getWebStorage(); + + /** + * Gets the singleton WebViewDatabase instance for this WebView implementation. The + * implementation must return the same instance on subsequent calls. + * + * @return the singleton WebViewDatabase instance + */ + WebViewDatabase getWebViewDatabase(Context context); } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 57a3012..e628bc1 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1058,7 +1058,9 @@ public class RemoteViews implements Parcelable, Filter { public ViewGroupAction(int viewId, RemoteViews nestedViews) { this.viewId = viewId; this.nestedViews = nestedViews; - configureRemoteViewsAsChild(nestedViews); + if (nestedViews != null) { + configureRemoteViewsAsChild(nestedViews); + } } public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) { diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java index 436caab..20e4b32 100644 --- a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java @@ -160,9 +160,22 @@ public class SurfaceTextureTarget extends Filter { @Override public void open(FilterContext context) { // Set up SurfaceTexture internals - mSurfaceId = context.getGLEnvironment().registerSurfaceTexture(mSurfaceTexture, mScreenWidth, mScreenHeight); + mSurfaceId = context.getGLEnvironment().registerSurfaceTexture( + mSurfaceTexture, mScreenWidth, mScreenHeight); + if (mSurfaceId <= 0) { + throw new RuntimeException("Could not register SurfaceTexture: " + mSurfaceTexture); + } } + + @Override + public void close(FilterContext context) { + if (mSurfaceId > 0) { + context.getGLEnvironment().unregisterSurfaceId(mSurfaceId); + } + } + + @Override public void process(FilterContext context) { if (mLogVerbose) Log.v(TAG, "Starting frame processing"); @@ -173,9 +186,11 @@ public class SurfaceTextureTarget extends Filter { Frame input = pullInput("frame"); boolean createdFrame = false; - float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); + float currentAspectRatio = + (float)input.getFormat().getWidth() / input.getFormat().getHeight(); if (currentAspectRatio != mAspectRatio) { - if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); + if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio + + ", previously: " + mAspectRatio); mAspectRatio = currentAspectRatio; updateTargetRect(); } diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index 8fbab74..b905db3 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -54,6 +54,7 @@ android:src="@drawable/ic_sysbar_back" systemui:keyCode="4" android:layout_weight="0" + android:scaleType="center" systemui:glowBackground="@drawable/ic_sysbar_highlight" android:contentDescription="@string/accessibility_back" /> @@ -214,6 +215,7 @@ android:layout_height="80dp" android:layout_width="match_parent" android:src="@drawable/ic_sysbar_back_land" + android:scaleType="center" systemui:keyCode="4" android:layout_weight="0" android:contentDescription="@string/accessibility_back" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 0ba8cce..f565e75 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -29,6 +29,17 @@ android:fitsSystemWindows="true" > + <ImageView + android:id="@+id/notification_lights_out" + android:layout_width="@dimen/status_bar_icon_size" + android:layout_height="match_parent" + android:paddingLeft="6dip" + android:paddingBottom="2dip" + android:src="@drawable/ic_sysbar_lights_out_dot_small" + android:scaleType="center" + android:visibility="gone" + /> + <LinearLayout android:id="@+id/icons" android:layout_width="match_parent" android:layout_height="match_parent" @@ -38,6 +49,7 @@ > <LinearLayout + android:id="@+id/notification_icon_area" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 73c5d3a..424317a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -23,6 +23,7 @@ import android.app.StatusBarManager; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Message; import android.os.ServiceManager; @@ -71,6 +72,8 @@ public class NavigationBarView extends LinearLayout { int mDisabledFlags = 0; int mNavigationIconHints = 0; + private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; + private DelegateViewHelper mDelegateHelper; // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) @@ -146,6 +149,11 @@ public class NavigationBarView extends LinearLayout { mVertical = false; mShowMenu = false; mDelegateHelper = new DelegateViewHelper(this); + + mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); + mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); + mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); + mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); } View.OnTouchListener mLightsOutListener = new View.OnTouchListener() { @@ -188,10 +196,10 @@ public class NavigationBarView extends LinearLayout { getRecentsButton().setAlpha( (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); - ((ImageView)getBackButton()).setImageResource( + ((ImageView)getBackButton()).setImageDrawable( (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) - ? R.drawable.ic_sysbar_back_ime - : R.drawable.ic_sysbar_back); + ? (mVertical ? mBackAltLandIcon : mBackAltIcon) + : (mVertical ? mBackLandIcon : mBackIcon)); } public void setDisabledFlags(int disabledFlags) { @@ -250,7 +258,7 @@ public class NavigationBarView extends LinearLayout { } else { navButtons.animate() .alpha(lightsOut ? 0f : 1f) - .setDuration(lightsOut ? 600 : 200) + .setDuration(lightsOut ? 750 : 250) .start(); lowLights.setOnTouchListener(mLightsOutListener); @@ -260,8 +268,7 @@ public class NavigationBarView extends LinearLayout { } lowLights.animate() .alpha(lightsOut ? 1f : 0f) - .setStartDelay(lightsOut ? 500 : 0) - .setDuration(lightsOut ? 1000 : 300) + .setDuration(lightsOut ? 750 : 250) .setInterpolator(new AccelerateInterpolator(2.0f)) .setListener(lightsOut ? null : new AnimatorListenerAdapter() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 80ee64f..8586185 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -16,6 +16,9 @@ package com.android.systemui.statusbar.phone; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -55,6 +58,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerImpl; +import android.view.animation.AccelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageView; @@ -207,6 +211,8 @@ public class PhoneStatusBar extends BaseStatusBar { int[] mAbsPos = new int[2]; Runnable mPostCollapseCleanup = null; + private AnimatorSet mLightsOutAnimation; + private AnimatorSet mLightsOnAnimation; // for disabling the status bar int mDisabled = 0; @@ -935,7 +941,26 @@ public class PhoneStatusBar extends BaseStatusBar { mClearButton.setAlpha(clearable ? 1.0f : 0.0f); } mClearButton.setEnabled(clearable); - + + final View nlo = mStatusBarView.findViewById(R.id.notification_lights_out); + final boolean showDot = (any&&!areLightsOn()); + if (showDot != (nlo.getAlpha() == 1.0f)) { + if (showDot) { + nlo.setAlpha(0f); + nlo.setVisibility(View.VISIBLE); + } + nlo.animate() + .alpha(showDot?1:0) + .setDuration(showDot?750:250) + .setInterpolator(new AccelerateInterpolator(2.0f)) + .setListener(showDot ? null : new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator _a) { + nlo.setVisibility(View.GONE); + } + }) + .start(); + } } public void showClock(boolean show) { @@ -1372,6 +1397,10 @@ public class PhoneStatusBar extends BaseStatusBar { final int hitSize = statusBarSize*2; final int y = (int)event.getRawY(); if (action == MotionEvent.ACTION_DOWN) { + if (!areLightsOn()) { + setLightsOn(true); + } + if (!mExpanded) { mViewDelta = statusBarSize - y; } else { @@ -1470,16 +1499,64 @@ public class PhoneStatusBar extends BaseStatusBar { final boolean lightsOut = (0 != (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)); if (lightsOut) { animateCollapse(); + if (mTicking) { + mTicker.halt(); + } } + if (mNavigationBarView != null) { mNavigationBarView.setLowProfile(lightsOut); } + + setStatusBarLowProfile(lightsOut); } notifyUiVisibilityChanged(); } } + private void setStatusBarLowProfile(boolean lightsOut) { + if (mLightsOutAnimation == null) { + final View notifications = mStatusBarView.findViewById(R.id.notification_icon_area); + final View systemIcons = mStatusBarView.findViewById(R.id.statusIcons); + final View signal = mStatusBarView.findViewById(R.id.signal_cluster); + final View battery = mStatusBarView.findViewById(R.id.battery); + final View clock = mStatusBarView.findViewById(R.id.clock); + + mLightsOutAnimation = new AnimatorSet(); + mLightsOutAnimation.playTogether( + ObjectAnimator.ofFloat(notifications, View.ALPHA, 0), + ObjectAnimator.ofFloat(systemIcons, View.ALPHA, 0), + ObjectAnimator.ofFloat(signal, View.ALPHA, 0), + ObjectAnimator.ofFloat(battery, View.ALPHA, 0.5f), + ObjectAnimator.ofFloat(clock, View.ALPHA, 0.5f) + ); + mLightsOutAnimation.setDuration(750); + + mLightsOnAnimation = new AnimatorSet(); + mLightsOnAnimation.playTogether( + ObjectAnimator.ofFloat(notifications, View.ALPHA, 1), + ObjectAnimator.ofFloat(systemIcons, View.ALPHA, 1), + ObjectAnimator.ofFloat(signal, View.ALPHA, 1), + ObjectAnimator.ofFloat(battery, View.ALPHA, 1), + ObjectAnimator.ofFloat(clock, View.ALPHA, 1) + ); + mLightsOnAnimation.setDuration(250); + } + + mLightsOutAnimation.cancel(); + mLightsOnAnimation.cancel(); + + final Animator a = lightsOut ? mLightsOutAnimation : mLightsOnAnimation; + a.start(); + + setAreThereNotifications(); + } + + private boolean areLightsOn() { + return 0 == (mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + public void setLightsOn(boolean on) { Log.v(TAG, "setLightsOn(" + on + ")"); if (on) { @@ -1580,6 +1657,9 @@ public class PhoneStatusBar extends BaseStatusBar { } private void tick(StatusBarNotification n) { + // no ticking in lights-out mode + if (!areLightsOn()) return; + // Show the ticker if one is requested. Also don't do this // until status bar window is attached to the window manager, // because... well, what's the point otherwise? And trying to diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 6c99cdb..429c3c4 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -2697,26 +2697,18 @@ public final class ActivityManagerService extends ActivityManagerNative public final void finishSubActivity(IBinder token, String resultWho, int requestCode) { synchronized(this) { - ActivityRecord self = mMainStack.isInStackLocked(token); - if (self == null) { - return; - } - final long origId = Binder.clearCallingIdentity(); + mMainStack.finishSubActivityLocked(token, resultWho, requestCode); + Binder.restoreCallingIdentity(origId); + } + } - int i; - for (i=mMainStack.mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - if (r.resultTo == self && r.requestCode == requestCode) { - if ((r.resultWho == null && resultWho == null) || - (r.resultWho != null && r.resultWho.equals(resultWho))) { - mMainStack.finishActivityLocked(r, i, - Activity.RESULT_CANCELED, null, "request-sub"); - } - } - } - + public boolean finishActivityAffinity(IBinder token) { + synchronized(this) { + final long origId = Binder.clearCallingIdentity(); + boolean res = mMainStack.finishActivityAffinityLocked(token); Binder.restoreCallingIdentity(origId); + return res; } } diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index c8e015b..25fae83 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -3497,6 +3497,51 @@ final class ActivityStack { return true; } + final void finishSubActivityLocked(IBinder token, String resultWho, int requestCode) { + ActivityRecord self = isInStackLocked(token); + if (self == null) { + return; + } + + int i; + for (i=mHistory.size()-1; i>=0; i--) { + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.resultTo == self && r.requestCode == requestCode) { + if ((r.resultWho == null && resultWho == null) || + (r.resultWho != null && r.resultWho.equals(resultWho))) { + finishActivityLocked(r, i, + Activity.RESULT_CANCELED, null, "request-sub"); + } + } + } + } + + final boolean finishActivityAffinityLocked(IBinder token) { + int index = indexOfTokenLocked(token); + if (DEBUG_RESULTS) Slog.v( + TAG, "Finishing activity affinity @" + index + ": token=" + token); + if (index < 0) { + return false; + } + ActivityRecord r = mHistory.get(index); + + while (index > 0) { + ActivityRecord cur = mHistory.get(index); + if (cur.task != r.task) { + break; + } + if (cur.taskAffinity == null && r.taskAffinity != null) { + break; + } + if (cur.taskAffinity != null && !cur.taskAffinity.equals(r.taskAffinity)) { + break; + } + finishActivityLocked(cur, index, Activity.RESULT_CANCELED, null, "request-affinity"); + index--; + } + return true; + } + final void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) { // send the result ActivityRecord resultTo = r.resultTo; |
