diff options
Diffstat (limited to 'core/java/android')
58 files changed, 2434 insertions, 274 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 53e6f34..541f413 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -990,6 +990,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case SHUTDOWN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + boolean res = shutdown(data.readInt()); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + case PEEK_SERVICE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); Intent service = Intent.CREATOR.createFromParcel(data); @@ -2160,5 +2168,19 @@ class ActivityManagerProxy implements IActivityManager return res; } + public boolean shutdown(int timeout) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(timeout); + mRemote.transact(SHUTDOWN_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + reply.recycle(); + data.recycle(); + return res; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2fc476e..1e15d14 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -191,16 +191,11 @@ public final class ActivityThread { usePreloaded = false; DisplayMetrics newMetrics = new DisplayMetrics(); newMetrics.setTo(metrics); - float invertedScale = 1.0f / applicationScale; - newMetrics.density *= invertedScale; - newMetrics.xdpi *= invertedScale; - newMetrics.ydpi *= invertedScale; - newMetrics.widthPixels *= invertedScale; - newMetrics.heightPixels *= invertedScale; + float newDensity = metrics.density / applicationScale; + newMetrics.updateDensity(newDensity); metrics = newMetrics; } - //Log.i(TAG, "Resource:" + appDir + ", density " + newMetrics.density + ", orig density:" + - // metrics.density); + //Log.i(TAG, "Resource:" + appDir + ", display metrics=" + metrics); r = new Resources(assets, metrics, getConfiguration(), usePreloaded); //Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration()); // XXX need to remove entries when weak references go away @@ -3502,8 +3497,10 @@ public final class ActivityThread { Resources r = v.get(); if (r != null) { // keep the original density based on application cale. - appDm.density = r.getDisplayMetrics().density; + appDm.updateDensity(r.getDisplayMetrics().density); r.updateConfiguration(config, appDm); + // reset + appDm.setTo(dm); //Log.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 72d9e3d..d235832 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -2326,15 +2326,26 @@ class ApplicationContext extends Context { } @Override - public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags) { + public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags, + String installerPackageName) { try { - mPM.installPackage(packageURI, observer, flags); + mPM.installPackage(packageURI, observer, flags, installerPackageName); } catch (RemoteException e) { // Should never happen! } } @Override + public String getInstallerPackageName(String packageName) { + try { + return mPM.getInstallerPackageName(packageName); + } catch (RemoteException e) { + // Should never happen! + } + return null; + } + + @Override public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) { try { mPM.deletePackage(packageName, observer, flags); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 2ac6160..56b29c1 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -16,7 +16,6 @@ package android.app; -import android.app.ActivityManager.MemoryInfo; import android.content.ComponentName; import android.content.ContentProviderNative; import android.content.IContentProvider; @@ -34,7 +33,6 @@ import android.os.IInterface; import android.os.Parcel; import android.os.Parcelable; import android.os.ParcelFileDescriptor; -import android.text.TextUtils; import android.os.Bundle; import java.util.List; @@ -225,6 +223,8 @@ public interface IActivityManager extends IInterface { public boolean profileControl(String process, boolean start, String path) throws RemoteException; + public boolean shutdown(int timeout) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -370,4 +370,5 @@ public interface IActivityManager extends IInterface { int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83; int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84; int PROFILE_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+85; + int SHUTDOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+86; } diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 8fc2447..343380c 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -171,7 +171,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // having windows anchored by their parent but not clipped by them. ViewGroup.LayoutParams.FILL_PARENT); WindowManager.LayoutParams lp = theWindow.getAttributes(); - lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE; + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; theWindow.setAttributes(lp); // get the view elements for local access @@ -309,11 +309,23 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS + appSearchData + ", " + globalSearch + ")"); } + // Try to get the searchable info for the provided component (or for global search, + // if globalSearch == true). mSearchable = SearchManager.getSearchableInfo(componentName, globalSearch); - if (mSearchable == null) { - // unfortunately, we can't log here. it would be logspam every time the user - // clicks the "search" key on a non-search app - return false; + + // If we got back nothing, and it wasn't a request for global search, then try again + // for global search, as we'll try to launch that in lieu of any component-specific search. + if (!globalSearch && mSearchable == null) { + globalSearch = true; + mSearchable = SearchManager.getSearchableInfo(componentName, globalSearch); + + // If we still get back null (i.e., there's not even a searchable info available + // for global search), then really give up. + if (mSearchable == null) { + // Unfortunately, we can't log here. it would be logspam every time the user + // clicks the "search" key on a non-search app. + return false; + } } mLaunchComponent = componentName; @@ -325,6 +337,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // show the dialog. this will call onStart(). if (!isShowing()) { + // First make sure the keyboard is showing (if needed), so that we get the right height + // for the dropdown to respect the IME. + if (getContext().getResources().getConfiguration().hardKeyboardHidden == + Configuration.HARDKEYBOARDHIDDEN_YES) { + InputMethodManager inputManager = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInputUnchecked(0, null); + } show(); } @@ -531,9 +551,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (mGlobalSearchMode) { mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in mSearchAutoComplete.setDropDownDismissedOnCompletion(false); + mSearchAutoComplete.setDropDownBackgroundResource( + com.android.internal.R.drawable.search_dropdown_background); } else { mSearchAutoComplete.setDropDownAlwaysVisible(false); mSearchAutoComplete.setDropDownDismissedOnCompletion(true); + mSearchAutoComplete.setDropDownBackgroundResource( + com.android.internal.R.drawable.search_dropdown_background_apps); } // attach the suggestions adapter, if suggestions are available @@ -1329,7 +1353,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS private SearchDialog mSearchDialog; public SearchAutoComplete(Context context) { - super(null); + super(context); mThreshold = getThreshold(); } diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 90de40d..3bf37c3 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -1184,23 +1184,32 @@ public class SearchManager public final static String SUGGEST_URI_PATH_QUERY = "search_suggest_query"; /** + * MIME type for suggestions data. You'll use this in your suggestions content provider + * in the getType() function. + */ + public final static String SUGGEST_MIME_TYPE = + "vnd.android.cursor.dir/vnd.android.search.suggest"; + + /** * Uri path for shortcut validation. This is the path that the search manager will use when * querying your content provider to refresh a shortcutted suggestion result and to check if it * is still valid. When asked, a source may return an up to date result, or no result. No * result indicates the shortcut refers to a no longer valid sugggestion. * * @see #SUGGEST_COLUMN_SHORTCUT_ID - * @hide + * + * @hide pending API council approval */ public final static String SUGGEST_URI_PATH_SHORTCUT = "search_suggest_shortcut"; /** - * MIME type for suggestions data. You'll use this in your suggestions content provider + * MIME type for shortcut validation. You'll use this in your suggestions content provider * in the getType() function. + * + * @hide pending API council approval */ - public final static String SUGGEST_MIME_TYPE = - "vnd.android.cursor.dir/vnd.android.search.suggest"; - + public final static String SHORTCUT_MIME_TYPE = + "vnd.android.cursor.item/vnd.android.search.suggest"; /** * Column name for suggestions cursor. <i>Unused - can be null or column can be omitted.</i> */ diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java index 7871fb6..26712a1 100755 --- a/core/java/android/appwidget/AppWidgetProvider.java +++ b/core/java/android/appwidget/AppWidgetProvider.java @@ -22,7 +22,7 @@ import android.content.Intent; import android.os.Bundle; /** - * A conveience class to aid in implementing an AppWidget provider. + * A convenience class to aid in implementing an AppWidget provider. * Everything you can do with AppWidgetProvider, you can do with a regular {@link BroadcastReceiver}. * AppWidgetProvider merely parses the relevant fields out of the Intent that is received in * {@link #onReceive(Context,Intent) onReceive(Context,Intent)}, and calls hook methods diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java index 6c47f7e..555494e 100644 --- a/core/java/android/backup/BackupDataOutput.java +++ b/core/java/android/backup/BackupDataOutput.java @@ -20,6 +20,7 @@ import android.content.Context; import java.io.FileDescriptor; +/** @hide */ public class BackupDataOutput { /* package */ FileDescriptor fd; diff --git a/core/java/android/backup/BackupService.java b/core/java/android/backup/BackupService.java index 6ac703a..50a5921 100644 --- a/core/java/android/backup/BackupService.java +++ b/core/java/android/backup/BackupService.java @@ -75,9 +75,8 @@ public abstract class BackupService extends Service { * file. The application should record the final backup state * here after writing the requested data to dataFd. */ - public abstract void onBackup(ParcelFileDescriptor oldState, - ParcelFileDescriptor data, - ParcelFileDescriptor newState); + public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState); /** * The application is being restored from backup, and should replace any @@ -92,7 +91,7 @@ public abstract class BackupService extends Service { * file. The application should record the final backup state * here after restoring its data from dataFd. */ - public abstract void onRestore(ParcelFileDescriptor data, ParcelFileDescriptor newState); + public abstract void onRestore(ParcelFileDescriptor /* TODO: BackupDataInput */ data, ParcelFileDescriptor newState); // ----- Core implementation ----- @@ -117,7 +116,15 @@ public abstract class BackupService extends Service { ParcelFileDescriptor newState) throws RemoteException { // !!! TODO - real implementation; for now just invoke the callbacks directly Log.v("BackupServiceBinder", "doBackup() invoked"); - BackupService.this.onBackup(oldState, data, newState); + BackupDataOutput output = new BackupDataOutput(BackupService.this, + data.getFileDescriptor()); + try { + BackupService.this.onBackup(oldState, output, newState); + } catch (RuntimeException ex) { + Log.d("BackupService", "onBackup (" + + BackupService.this.getClass().getName() + ") threw", ex); + throw ex; + } } public void doRestore(ParcelFileDescriptor data, diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java index 3b2122c..05159dc 100644 --- a/core/java/android/backup/FileBackupHelper.java +++ b/core/java/android/backup/FileBackupHelper.java @@ -18,20 +18,24 @@ package android.backup; import android.content.Context; import android.os.ParcelFileDescriptor; +import android.util.Log; import java.io.FileDescriptor; +/** @hide */ public class FileBackupHelper { + private static final String TAG = "FileBackupHelper"; + /** - * Based on oldSnapshot, determine which of the files from the application's data directory - * need to be backed up, write them to the data stream, and fill in newSnapshot with the + * Based on oldState, determine which of the files from the application's data directory + * need to be backed up, write them to the data stream, and fill in newState with the * state as it exists now. */ public static void performBackup(Context context, - ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot, - BackupDataOutput data, String[] files) { + ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState, String[] files) { String basePath = context.getFilesDir().getAbsolutePath(); - performBackup_checked(basePath, oldSnapshot, newSnapshot, data, files); + performBackup_checked(basePath, oldState, data, newState, files); } /** @@ -39,30 +43,31 @@ public class FileBackupHelper { * since it's easier to do that from java. */ static void performBackup_checked(String basePath, - ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot, - BackupDataOutput data, String[] files) { - if (newSnapshot == null) { - throw new NullPointerException("newSnapshot==null"); + ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState, String[] files) { + if (files.length == 0) { + return; } - if (data == null) { - throw new NullPointerException("data==null"); + if (basePath == null) { + throw new NullPointerException(); } + // oldStateFd can be null + FileDescriptor oldStateFd = oldState != null ? oldState.getFileDescriptor() : null; if (data.fd == null) { - throw new NullPointerException("data.fd==null"); + throw new NullPointerException(); } - if (files == null) { - throw new NullPointerException("files==null"); + FileDescriptor newStateFd = newState.getFileDescriptor(); + if (newStateFd == null) { + throw new NullPointerException(); } - int err = performBackup_native(basePath, oldSnapshot.getFileDescriptor(), - newSnapshot.getFileDescriptor(), data.fd, files); + int err = performBackup_native(basePath, oldStateFd, data.fd, newStateFd, files); if (err != 0) { throw new RuntimeException("Backup failed"); // TODO: more here } } - native private static int performBackup_native(String basePath, - FileDescriptor oldSnapshot, FileDescriptor newSnapshot, - FileDescriptor data, String[] files); + native private static int performBackup_native(String basePath, FileDescriptor oldState, + FileDescriptor data, FileDescriptor newState, String[] files); } diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java index e839bb4..8627f08 100644 --- a/core/java/android/backup/SharedPreferencesBackupHelper.java +++ b/core/java/android/backup/SharedPreferencesBackupHelper.java @@ -21,6 +21,7 @@ import android.os.ParcelFileDescriptor; import java.io.FileDescriptor; +/** @hide */ public class SharedPreferencesBackupHelper { public static void performBackup(Context context, ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot, @@ -34,7 +35,7 @@ public class SharedPreferencesBackupHelper { files[i] = prefGroups[i] + ".xml"; } - FileBackupHelper.performBackup_checked(basePath, oldSnapshot, newSnapshot, data, files); + FileBackupHelper.performBackup_checked(basePath, oldSnapshot, data, newSnapshot, files); } } diff --git a/core/java/android/content/AbstractCursorEntityIterator.java b/core/java/android/content/AbstractCursorEntityIterator.java new file mode 100644 index 0000000..bf3c4de --- /dev/null +++ b/core/java/android/content/AbstractCursorEntityIterator.java @@ -0,0 +1,112 @@ +package android.content; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.RemoteException; + +/** + * An abstract class that makes it easy to implement an EntityIterator over a cursor. + * The user must implement {@link #newEntityFromCursorLocked}, which runs inside of a + * database transaction. + */ +public abstract class AbstractCursorEntityIterator implements EntityIterator { + private final Cursor mEntityCursor; + private final SQLiteDatabase mDb; + private volatile Entity mNextEntity; + private volatile boolean mIsClosed; + + public AbstractCursorEntityIterator(SQLiteDatabase db, Cursor entityCursor) { + mEntityCursor = entityCursor; + mDb = db; + mNextEntity = null; + mIsClosed = false; + } + + /** + * If there are entries left in the cursor then advance the cursor and use the new row to + * populate mNextEntity. If the cursor is at the end or if advancing it causes the cursor + * to become at the end then set mEntityCursor to null. If newEntityFromCursor returns null + * then continue advancing until it either returns a non-null Entity or the cursor reaches + * the end. + */ + private void fillEntityIfAvailable() { + while (mNextEntity == null) { + if (!mEntityCursor.moveToNext()) { + // the cursor is at then end, bail out + return; + } + // This may return null if newEntityFromCursor is not able to create an entity + // from the current cursor position. In that case this method will loop and try + // the next cursor position + mNextEntity = newEntityFromCursorLocked(mEntityCursor); + } + mDb.beginTransaction(); + try { + int position = mEntityCursor.getPosition(); + mNextEntity = newEntityFromCursorLocked(mEntityCursor); + int newPosition = mEntityCursor.getPosition(); + if (newPosition != position) { + throw new IllegalStateException("the cursor position changed during the call to" + + "newEntityFromCursorLocked, from " + position + " to " + newPosition); + } + } finally { + mDb.endTransaction(); + } + } + + /** + * Checks if there are more Entities accessible via this iterator. This may not be called + * if the iterator is already closed. + * @return true if the call to next() will return an Entity. + */ + public boolean hasNext() { + if (mIsClosed) { + throw new IllegalStateException("calling hasNext() when the iterator is closed"); + } + fillEntityIfAvailable(); + return mNextEntity != null; + } + + /** + * Returns the next Entity that is accessible via this iterator. This may not be called + * if the iterator is already closed. + * @return the next Entity that is accessible via this iterator + */ + public Entity next() { + if (mIsClosed) { + throw new IllegalStateException("calling next() when the iterator is closed"); + } + if (!hasNext()) { + throw new IllegalStateException("you may only call next() if hasNext() is true"); + } + + try { + return mNextEntity; + } finally { + mNextEntity = null; + } + } + + /** + * Closes this iterator making it invalid. If is invalid for the user to call any public + * method on the iterator once it has been closed. + */ + public void close() { + if (mIsClosed) { + throw new IllegalStateException("closing when already closed"); + } + mIsClosed = true; + mEntityCursor.close(); + } + + /** + * Returns a new Entity from the current cursor position. This is called from within a + * database transaction. If a new entity cannot be created from this cursor position (e.g. + * if the row that is referred to no longer exists) then this may return null. The cursor + * is guaranteed to be pointing to a valid row when this call is made. The implementation + * of newEntityFromCursorLocked is not allowed to change the position of the cursor. + * @param cursor from where to read the data for the Entity + * @return an Entity that corresponds to the current cursor position or null + */ + public abstract Entity newEntityFromCursorLocked(Cursor cursor); +} diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java index 32c6864..0455202 100644 --- a/core/java/android/content/ComponentName.java +++ b/core/java/android/content/ComponentName.java @@ -18,6 +18,7 @@ package android.content; import android.os.Parcel; import android.os.Parcelable; +import java.lang.Comparable; /** * Identifier for a specific application component @@ -29,7 +30,7 @@ import android.os.Parcelable; * name inside of that package. * */ -public final class ComponentName implements Parcelable { +public final class ComponentName implements Parcelable, Comparable<ComponentName> { private final String mPackage; private final String mClass; @@ -196,6 +197,15 @@ public final class ComponentName implements Parcelable { public int hashCode() { return mPackage.hashCode() + mClass.hashCode(); } + + public int compareTo(ComponentName that) { + int v; + v = this.mPackage.compareTo(that.mPackage); + if (v != 0) { + return v; + } + return this.mClass.compareTo(that.mClass); + } public int describeContents() { return 0; diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 3a080a0..3a8de6e 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -130,6 +130,12 @@ public abstract class ContentProvider implements ComponentCallbacks { selectionArgs, sortOrder); } + public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs, + String sortOrder) { + checkReadPermission(uri); + return ContentProvider.this.queryEntities(uri, selection, selectionArgs, sortOrder); + } + public String getType(Uri uri) { return ContentProvider.this.getType(uri); } @@ -145,6 +151,11 @@ public abstract class ContentProvider implements ComponentCallbacks { return ContentProvider.this.bulkInsert(uri, initialValues); } + public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) { + checkWritePermission(uri); + return ContentProvider.this.bulkInsertEntities(uri, entities); + } + public int delete(Uri uri, String selection, String[] selectionArgs) { checkWritePermission(uri); return ContentProvider.this.delete(uri, selection, selectionArgs); @@ -156,6 +167,11 @@ public abstract class ContentProvider implements ComponentCallbacks { return ContentProvider.this.update(uri, values, selection, selectionArgs); } + public int[] bulkUpdateEntities(Uri uri, Entity[] entities) { + checkWritePermission(uri); + return ContentProvider.this.bulkUpdateEntities(uri, entities); + } + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { if (mode != null && mode.startsWith("rw")) checkWritePermission(uri); @@ -328,6 +344,11 @@ public abstract class ContentProvider implements ComponentCallbacks { public abstract Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder); + public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs, + String sortOrder) { + throw new UnsupportedOperationException(); + } + /** * Return the MIME type of the data at the given URI. This should start with * <code>vnd.android.cursor.item</code> for a single record, @@ -378,6 +399,18 @@ public abstract class ContentProvider implements ComponentCallbacks { return numValues; } + public Uri insertEntity(Uri uri, Entity entity) { + throw new UnsupportedOperationException(); + } + + public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) { + Uri[] result = new Uri[entities.length]; + for (int i = 0; i < entities.length; i++) { + result[i] = insertEntity(uri, entities[i]); + } + return result; + } + /** * A request to delete one or more rows. The selection clause is applied when performing * the deletion, allowing the operation to affect multiple rows in a @@ -422,6 +455,18 @@ public abstract class ContentProvider implements ComponentCallbacks { public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs); + public int updateEntity(Uri uri, Entity entity) { + throw new UnsupportedOperationException(); + } + + public int[] bulkUpdateEntities(Uri uri, Entity[] entities) { + int[] result = new int[entities.length]; + for (int i = 0; i < entities.length; i++) { + result[i] = updateEntity(uri, entities[i]); + } + return result; + } + /** * Open a file blob associated with a content URI. * This method can be called from multiple diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 9902807..ed3ed42 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.content; import android.database.Cursor; @@ -26,52 +42,78 @@ public class ContentProviderClient { mContentResolver = contentResolver; } - /** {@see ContentProvider#query} */ + /** see {@link ContentProvider#query} */ public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException { return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder); } - /** {@see ContentProvider#getType} */ + /** see {@link ContentProvider#getType} */ public String getType(Uri url) throws RemoteException { return mContentProvider.getType(url); } - /** {@see ContentProvider#insert} */ + /** see {@link ContentProvider#insert} */ public Uri insert(Uri url, ContentValues initialValues) throws RemoteException { return mContentProvider.insert(url, initialValues); } - /** {@see ContentProvider#bulkInsert} */ + /** see {@link ContentProvider#bulkInsert} */ public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException { return mContentProvider.bulkInsert(url, initialValues); } - /** {@see ContentProvider#delete} */ + /** see {@link ContentProvider#delete} */ public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException { return mContentProvider.delete(url, selection, selectionArgs); } - /** {@see ContentProvider#update} */ + /** see {@link ContentProvider#update} */ public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { return mContentProvider.update(url, values, selection, selectionArgs); } - /** {@see ContentProvider#openFile} */ + /** see {@link ContentProvider#openFile} */ public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException { return mContentProvider.openFile(url, mode); } - /** {@see ContentProvider#openAssetFile} */ + /** see {@link ContentProvider#openAssetFile} */ public AssetFileDescriptor openAssetFile(Uri url, String mode) throws RemoteException, FileNotFoundException { return mContentProvider.openAssetFile(url, mode); } + /** see {@link ContentProvider#queryEntities} */ + public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs, + String sortOrder) throws RemoteException { + return mContentProvider.queryEntities(uri, selection, selectionArgs, sortOrder); + } + + /** see {@link ContentProvider#insertEntity} */ + public Uri insertEntity(Uri uri, Entity entity) throws RemoteException { + return mContentProvider.bulkInsertEntities(uri, new Entity[]{entity})[0]; + } + + /** see {@link ContentProvider#bulkInsertEntities} */ + public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) throws RemoteException { + return mContentProvider.bulkInsertEntities(uri, entities); + } + + /** see {@link ContentProvider#updateEntity} */ + public int updateEntity(Uri uri, Entity entity) throws RemoteException { + return mContentProvider.bulkUpdateEntities(uri, new Entity[]{entity})[0]; + } + + /** see {@link ContentProvider#bulkUpdateEntities} */ + public int[] bulkUpdateEntities(Uri uri, Entity[] entities) throws RemoteException { + return mContentProvider.bulkUpdateEntities(uri, entities); + } + /** * Call this to indicate to the system that the associated {@link ContentProvider} is no * longer needed by this {@link ContentProviderClient}. diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index f9282e2..ac67076 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -105,6 +105,20 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return true; } + case QUERY_ENTITIES_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + Uri url = Uri.CREATOR.createFromParcel(data); + String selection = data.readString(); + String[] selectionArgs = data.readStringArray(); + String sortOrder = data.readString(); + EntityIterator entityIterator = queryEntities(url, selection, selectionArgs, + sortOrder); + reply.writeNoException(); + reply.writeStrongBinder(new IEntityIteratorImpl(entityIterator).asBinder()); + return true; + } + case GET_TYPE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); @@ -140,6 +154,40 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return true; } + case BULK_INSERT_ENTITIES_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + Uri uri = Uri.CREATOR.createFromParcel(data); + String className = data.readString(); + Class entityClass = Class.forName(className); + int numEntities = data.readInt(); + Entity[] entities = new Entity[numEntities]; + for (int i = 0; i < numEntities; i++) { + entities[i] = (Entity) data.readParcelable(entityClass.getClassLoader()); + } + Uri[] uris = bulkInsertEntities(uri, entities); + reply.writeNoException(); + reply.writeTypedArray(uris, 0); + return true; + } + + case BULK_UPDATE_ENTITIES_TRANSACTION: + { + data.enforceInterface(IContentProvider.descriptor); + Uri uri = Uri.CREATOR.createFromParcel(data); + String className = data.readString(); + Class entityClass = Class.forName(className); + int numEntities = data.readInt(); + Entity[] entities = new Entity[numEntities]; + for (int i = 0; i < numEntities; i++) { + entities[i] = (Entity) data.readParcelable(entityClass.getClassLoader()); + } + int[] counts = bulkUpdateEntities(uri, entities); + reply.writeNoException(); + reply.writeIntArray(counts); + return true; + } + case DELETE_TRANSACTION: { data.enforceInterface(IContentProvider.descriptor); @@ -215,6 +263,25 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return super.onTransact(code, data, reply, flags); } + private class IEntityIteratorImpl extends IEntityIterator.Stub { + private final EntityIterator mEntityIterator; + + IEntityIteratorImpl(EntityIterator iterator) { + mEntityIterator = iterator; + } + public boolean hasNext() throws RemoteException { + return mEntityIterator.hasNext(); + } + + public Entity next() throws RemoteException { + return mEntityIterator.next(); + } + + public void close() throws RemoteException { + mEntityIterator.close(); + } + } + public IBinder asBinder() { return this; @@ -288,7 +355,7 @@ final class ContentProviderProxy implements IContentProvider BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); IBulkCursor bulkCursor = bulkQuery(url, projection, selection, selectionArgs, sortOrder, adaptor.getObserver(), window); - + if (bulkCursor == null) { return null; } @@ -296,6 +363,54 @@ final class ContentProviderProxy implements IContentProvider return adaptor; } + public EntityIterator queryEntities(Uri url, String selection, String[] selectionArgs, + String sortOrder) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + + data.writeInterfaceToken(IContentProvider.descriptor); + + url.writeToParcel(data, 0); + data.writeString(selection); + data.writeStringArray(selectionArgs); + data.writeString(sortOrder); + + mRemote.transact(IContentProvider.QUERY_ENTITIES_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + + IBinder entityIteratorBinder = reply.readStrongBinder(); + + data.recycle(); + reply.recycle(); + + return new RemoteEntityIterator(IEntityIterator.Stub.asInterface(entityIteratorBinder)); + } + + static class RemoteEntityIterator implements EntityIterator { + private final IEntityIterator mEntityIterator; + RemoteEntityIterator(IEntityIterator entityIterator) { + mEntityIterator = entityIterator; + } + + public boolean hasNext() throws RemoteException { + return mEntityIterator.hasNext(); + } + + public Entity next() throws RemoteException { + return mEntityIterator.next(); + } + + public void close() { + try { + mEntityIterator.close(); + } catch (RemoteException e) { + // doesn't matter + } + } + } + public String getType(Uri url) throws RemoteException { Parcel data = Parcel.obtain(); @@ -357,6 +472,54 @@ final class ContentProviderProxy implements IContentProvider return count; } + public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + + data.writeInterfaceToken(IContentProvider.descriptor); + uri.writeToParcel(data, 0); + data.writeString(entities[0].getClass().getName()); + data.writeInt(entities.length); + for (Entity entity : entities) { + data.writeParcelable(entity, 0); + } + + mRemote.transact(IContentProvider.BULK_INSERT_ENTITIES_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + Uri[] results = new Uri[entities.length]; + reply.readTypedArray(results, Uri.CREATOR); + + data.recycle(); + reply.recycle(); + + return results; + } + + public int[] bulkUpdateEntities(Uri uri, Entity[] entities) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + + data.writeInterfaceToken(IContentProvider.descriptor); + uri.writeToParcel(data, 0); + data.writeString(entities[0].getClass().getName()); + data.writeInt(entities.length); + for (Entity entity : entities) { + data.writeParcelable(entity, 0); + } + + mRemote.transact(IContentProvider.BULK_UPDATE_ENTITIES_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + int[] results = new int[entities.length]; + reply.readIntArray(results); + + data.recycle(); + reply.recycle(); + + return results; + } + public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 6f3b1b6..f9bed59 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -166,6 +166,53 @@ public abstract class ContentResolver { } } + class EntityIteratorWrapper implements EntityIterator { + private final EntityIterator mInner; + private final ContentProviderClient mClient; + + EntityIteratorWrapper(EntityIterator inner, ContentProviderClient client) { + mInner = inner; + mClient = client; + } + + public boolean hasNext() throws RemoteException { + return mInner.hasNext(); + } + + public Entity next() throws RemoteException { + return mInner.next(); + } + + public void close() { + mClient.release(); + mInner.close(); + } + + protected void finalize() throws Throwable { + close(); + super.finalize(); + } + } + + public final EntityIterator queryEntity(Uri uri, + String selection, String[] selectionArgs, String sortOrder) throws RemoteException { + ContentProviderClient provider = acquireContentProviderClient(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URL " + uri); + } + try { + EntityIterator entityIterator = + provider.queryEntities(uri, selection, selectionArgs, sortOrder); + return new EntityIteratorWrapper(entityIterator, provider); + } catch(RuntimeException e) { + provider.release(); + throw e; + } catch(RemoteException e) { + provider.release(); + throw e; + } + } + /** * Open a stream on to the content associated with a content URI. If there * is no data associated with the URI, FileNotFoundException is thrown. @@ -485,6 +532,56 @@ public abstract class ContentResolver { } } + public final Uri insertEntity(Uri uri, Entity entity) throws RemoteException { + ContentProviderClient provider = acquireContentProviderClient(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URL " + uri); + } + try { + return provider.insertEntity(uri, entity); + } finally { + provider.release(); + } + } + + public final int updateEntity(Uri uri, Entity entity) throws RemoteException { + ContentProviderClient provider = acquireContentProviderClient(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URL " + uri); + } + try { + return provider.updateEntity(uri, entity); + } finally { + provider.release(); + } + } + + public final Uri[] bulkInsertEntities(Uri uri, Entity[] entities) + throws RemoteException { + ContentProviderClient provider = acquireContentProviderClient(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URL " + uri); + } + try { + return provider.bulkInsertEntities(uri, entities); + } finally { + provider.release(); + } + } + + public final int[] bulkUpdateEntities(Uri uri, Entity[] entities) + throws RemoteException { + ContentProviderClient provider = acquireContentProviderClient(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URL " + uri); + } + try { + return provider.bulkUpdateEntities(uri, entities); + } finally { + provider.release(); + } + } + /** * Inserts multiple rows into a table at the given URL. * diff --git a/core/java/android/content/Entity.aidl b/core/java/android/content/Entity.aidl new file mode 100644 index 0000000..fb201f3 --- /dev/null +++ b/core/java/android/content/Entity.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/content/Entity.aidl +** +** 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. +*/ + +package android.content; + +parcelable Entity; diff --git a/core/java/android/content/Entity.java b/core/java/android/content/Entity.java new file mode 100644 index 0000000..31ea2cd --- /dev/null +++ b/core/java/android/content/Entity.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.os.Parcelable; + +/** + * Objects that pass through the ContentProvider and ContentResolver's methods that deal with + * Entities must implement this abstract base class and thus themselves be Parcelable. + */ +public abstract class Entity implements Parcelable { +} diff --git a/core/java/android/content/EntityIterator.java b/core/java/android/content/EntityIterator.java new file mode 100644 index 0000000..61914cf --- /dev/null +++ b/core/java/android/content/EntityIterator.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.os.RemoteException; + +public interface EntityIterator { + /** + * Returns whether there are more elements to iterate, i.e. whether the + * iterator is positioned in front of an element. + * + * @return {@code true} if there are more elements, {@code false} otherwise. + * @see #next + * @since Android 1.0 + */ + public boolean hasNext() throws RemoteException; + + /** + * Returns the next object in the iteration, i.e. returns the element in + * front of the iterator and advances the iterator by one position. + * + * @return the next object. + * @throws java.util.NoSuchElementException + * if there are no more elements. + * @see #hasNext + * @since Android 1.0 + */ + public Entity next() throws RemoteException; + + /** + * Indicates that this iterator is no longer needed and that any associated resources + * may be released (such as a SQLite cursor). + */ + public void close(); +} diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 0b81245..b4d1865 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -43,14 +43,19 @@ public interface IContentProvider extends IInterface { CursorWindow window) throws RemoteException; public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException; + public EntityIterator queryEntities(Uri url, String selection, + String[] selectionArgs, String sortOrder) + throws RemoteException; public String getType(Uri url) throws RemoteException; public Uri insert(Uri url, ContentValues initialValues) throws RemoteException; public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException; + public Uri[] bulkInsertEntities(Uri uri, Entity[] entities) throws RemoteException; public int delete(Uri url, String selection, String[] selectionArgs) throws RemoteException; public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException; + public int[] bulkUpdateEntities(Uri uri, Entity[] entities) throws RemoteException; public ParcelFileDescriptor openFile(Uri url, String mode) throws RemoteException, FileNotFoundException; public AssetFileDescriptor openAssetFile(Uri url, String mode) @@ -67,4 +72,7 @@ public interface IContentProvider extends IInterface { static final int BULK_INSERT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 12; static final int OPEN_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 13; static final int OPEN_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 14; + static final int BULK_INSERT_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 16; + static final int BULK_UPDATE_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 17; + static final int QUERY_ENTITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 18; } diff --git a/core/java/android/content/IEntityIterator.java b/core/java/android/content/IEntityIterator.java new file mode 100644 index 0000000..eb800f7 --- /dev/null +++ b/core/java/android/content/IEntityIterator.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.Log; + +/** + * ICPC interface methods for an iterator over Entity objects. + */ +public interface IEntityIterator extends IInterface { + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends Binder implements IEntityIterator { + private static final String TAG = "IEntityIterator"; + private static final java.lang.String DESCRIPTOR = "android.content.IEntityIterator"; + + /** Construct the stub at attach it to the interface. */ + public Stub() { + this.attachInterface(this, DESCRIPTOR); + } + /** + * Cast an IBinder object into an IEntityIterator interface, + * generating a proxy if needed. + */ + public static IEntityIterator asInterface(IBinder obj) { + if ((obj==null)) { + return null; + } + IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin!=null)&&(iin instanceof IEntityIterator))) { + return ((IEntityIterator)iin); + } + return new IEntityIterator.Stub.Proxy(obj); + } + + public IBinder asBinder() { + return this; + } + + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: + { + reply.writeString(DESCRIPTOR); + return true; + } + + case TRANSACTION_hasNext: + { + data.enforceInterface(DESCRIPTOR); + boolean _result; + try { + _result = this.hasNext(); + } catch (Exception e) { + Log.e(TAG, "caught exception in hasNext()", e); + reply.writeException(e); + return true; + } + reply.writeNoException(); + reply.writeInt(((_result)?(1):(0))); + return true; + } + + case TRANSACTION_next: + { + data.enforceInterface(DESCRIPTOR); + Entity entity; + try { + entity = this.next(); + } catch (RemoteException e) { + Log.e(TAG, "caught exception in next()", e); + reply.writeException(e); + return true; + } + reply.writeNoException(); + reply.writeString(entity.getClass().getName()); + reply.writeParcelable(entity, 0); + return true; + } + + case TRANSACTION_close: + { + data.enforceInterface(DESCRIPTOR); + try { + this.close(); + } catch (RemoteException e) { + Log.e(TAG, "caught exception in close()", e); + reply.writeException(e); + return true; + } + reply.writeNoException(); + return true; + } + } + return super.onTransact(code, data, reply, flags); + } + + private static class Proxy implements IEntityIterator { + private IBinder mRemote; + Proxy(IBinder remote) { + mRemote = remote; + } + public IBinder asBinder() { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() { + return DESCRIPTOR; + } + public boolean hasNext() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + boolean _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_hasNext, _data, _reply, 0); + _reply.readException(); + _result = (0!=_reply.readInt()); + } + finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + public Entity next() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + Entity _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_next, _data, _reply, 0); + _reply.readException(); + final String className = _reply.readString(); + final Class entityClass = Class.forName(className); + ClassLoader classLoader = entityClass.getClassLoader(); + _result = (Entity) _reply.readParcelable(classLoader); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + public void close() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_close, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + } + static final int TRANSACTION_hasNext = (IBinder.FIRST_CALL_TRANSACTION + 0); + static final int TRANSACTION_next = (IBinder.FIRST_CALL_TRANSACTION + 1); + static final int TRANSACTION_close = (IBinder.FIRST_CALL_TRANSACTION + 2); + } + public boolean hasNext() throws RemoteException; + public Entity next() throws RemoteException; + public void close() throws RemoteException; +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index ebdd588..0e6be0a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -510,6 +510,7 @@ import java.util.Set; * <li> {@link #ACTION_BATTERY_CHANGED} * <li> {@link #ACTION_POWER_CONNECTED} * <li> {@link #ACTION_POWER_DISCONNECTED} + * <li> {@link #ACTION_SHUTDOWN} * </ul> * * <h3>Standard Categories</h3> @@ -1047,6 +1048,17 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS"; + /** + * Activity Action: The user pressed the "Report" button in the crash/ANR dialog. + * This intent is delivered to the package which installed the application, usually + * the Market. + * <p>Input: No data is specified. The bug report is passed in using + * an {@link #EXTRA_BUG_REPORT} field. + * <p>Output: Nothing. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). @@ -1271,6 +1283,15 @@ public class Intent implements Parcelable { public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.POWER_DISCONNECTED"; /** + * Broadcast Action: Device is shutting down. + * This is broadcast when the device is being shut down (completely turned + * off, not sleeping). Once the broadcast is complete, the final shutdown + * will proceed and all unsaved data lost. Apps will not normally need + * to handle this, since the forground activity will be paused as well. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; + /** * Broadcast Action: Indicates low memory condition on the device */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -1532,6 +1553,21 @@ public class Intent implements Parcelable { public static final String ACTION_REBOOT = "android.intent.action.REBOOT"; + /** + * Broadcast Action: a remote intent is to be broadcasted. + * + * A remote intent is used for remote RPC between devices. The remote intent + * is serialized and sent from one device to another device. The receiving + * device parses the remote intent and broadcasts it. Note that anyone can + * broadcast a remote intent. However, if the intent receiver of the remote intent + * does not trust intent broadcasts from arbitrary intent senders, it should require + * the sender to hold certain permissions so only trusted sender's broadcast will be + * let through. + */ + public static final String ACTION_REMOTE_INTENT = + "android.intent.action.REMOTE_INTENT"; + + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -1771,6 +1807,31 @@ public class Intent implements Parcelable { * delivered. */ public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT"; + + /** + * Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing + * the bug report. + * + * @hide + */ + public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT"; + + /** + * Used as a string extra field when sending an intent to PackageInstaller to install a + * package. Specifies the installer package name; this package will receive the + * {@link #ACTION_APP_ERROR} intent. + * + * @hide + */ + public static final String EXTRA_INSTALLER_PACKAGE_NAME + = "android.intent.extra.INSTALLER_PACKAGE_NAME"; + + /** + * Used in the extra field in the remote intent. It's astring token passed with the + * remote intent. + */ + public static final String EXTRA_REMOTE_INTENT_TOKEN = + "android.intent.extra.remote_intent_token"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- @@ -1931,7 +1992,7 @@ public class Intent implements Parcelable { /** * If set, this marks a point in the task's activity stack that should * be cleared when the task is reset. That is, the next time the task - * is broad to the foreground with + * is brought to the foreground with * {@link #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED} (typically as a result of * the user re-launching it from home), this activity and all on top of * it will be finished so that the user does not return to them, but diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 019abb8..03cfbea 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -255,6 +255,14 @@ class SyncManager implements OnAccountsUpdatedListener { } }; + private BroadcastReceiver mShutdownIntentReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + Log.w(TAG, "Writing sync state before shutdown..."); + getSyncStorageEngine().writeAllState(); + } + }; + private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; private static final String SYNC_POLL_ALARM = "android.content.syncmanager.SYNC_POLL_ALARM"; private final SyncHandler mSyncHandler; @@ -301,6 +309,10 @@ class SyncManager implements OnAccountsUpdatedListener { intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); context.registerReceiver(mStorageIntentReceiver, intentFilter); + intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); + intentFilter.setPriority(100); + context.registerReceiver(mShutdownIntentReceiver, intentFilter); + if (!factoryTest) { mNotificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index e20e70f..aaa763d 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -391,8 +391,7 @@ public class SyncStorageEngine extends Handler { while (i > 0) { i--; AuthorityInfo authority = mAuthorities.get(i); - if (authority.account.equals(account) - && authority.authority.equals(providerName)) { + if (authority.authority.equals(providerName)) { authority.enabled = sync; } } @@ -740,7 +739,7 @@ public class SyncStorageEngine extends Handler { } boolean writeStatisticsNow = false; - int day = getCurrentDay(); + int day = getCurrentDayLocked(); if (mDayStats[0] == null) { mDayStats[0] = new DayStats(day); } else if (day != mDayStats[0].day) { @@ -929,7 +928,7 @@ public class SyncStorageEngine extends Handler { } } - private int getCurrentDay() { + private int getCurrentDayLocked() { mCal.setTimeInMillis(System.currentTimeMillis()); final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); if (mYear != mCal.get(Calendar.YEAR)) { @@ -1009,6 +1008,21 @@ public class SyncStorageEngine extends Handler { return status; } + public void writeAllState() { + synchronized (mAuthorities) { + // Account info is always written so no need to do it here. + + if (mNumPendingFinished > 0) { + // Only write these if they are out of date. + writePendingOperationsLocked(); + } + + // Just always write these... they are likely out of date. + writeStatusLocked(); + writeStatisticsLocked(); + } + } + /** * Read all account information back in to the initial engine state. */ diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 173057c..88ac04c 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -113,19 +113,31 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int FLAG_ALLOW_CLEAR_USER_DATA = 1<<6; - /** - * Value for {@link #flags}: default value for the corresponding ActivityInfo flag. - * {@hide} + * Value for {@link #flags}: this is set if this application has been + * install as an update to a built-in system application. */ public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7; + + /** + * Value for {@link #flags}: this is set of the application has set + * its android:targetSdkVersion to something >= the current SDK version. + */ + public static final int FLAG_TARGETS_SDK = 1<<8; + + /** + * Value for {@link #flags}: this is set of the application has set + * its android:targetSdkVersion to something >= the current SDK version. + */ + public static final int FLAG_TEST_ONLY = 1<<9; /** * Flags associated with the application. Any combination of * {@link #FLAG_SYSTEM}, {@link #FLAG_DEBUGGABLE}, {@link #FLAG_HAS_CODE}, * {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and * {@link #FLAG_ALLOW_TASK_REPARENTING} - * {@link #FLAG_ALLOW_CLEAR_USER_DATA}. + * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, + * {@link #FLAG_TARGETS_SDK}. */ public int flags = 0; diff --git a/core/java/android/content/pm/IPackageInstallObserver.aidl b/core/java/android/content/pm/IPackageInstallObserver.aidl index e83bbc6..6133365 100644 --- a/core/java/android/content/pm/IPackageInstallObserver.aidl +++ b/core/java/android/content/pm/IPackageInstallObserver.aidl @@ -19,7 +19,7 @@ package android.content.pm; /** * API for installation callbacks from the Package Manager. - * + * @hide */ oneway interface IPackageInstallObserver { void packageInstalled(in String packageName, int returnCode); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index bf963ff..57b84b2 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -140,8 +140,11 @@ interface IPackageManager { * @param observer a callback to use to notify when the package installation in finished. * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE}, * {@link #REPLACE_EXISITING_PACKAGE} + * @param installerPackageName Optional package name of the application that is performing the + * installation. This identifies which market the package came from. */ - void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags); + void installPackage(in Uri packageURI, IPackageInstallObserver observer, int flags, + in String installerPackageName); /** * Delete a package. @@ -152,6 +155,8 @@ interface IPackageManager { */ void deletePackage(in String packageName, IPackageDeleteObserver observer, int flags); + String getInstallerPackageName(in String packageName); + void addPackageToPreferred(String packageName); void removePackageFromPreferred(String packageName); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9e06666..8095990 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -172,6 +172,14 @@ public abstract class PackageManager { public static final int GET_SUPPORTS_DENSITIES = 0x00008000; /** + * Resolution and querying flag: if set, only filters that support the + * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for + * matching. This is a synonym for including the CATEGORY_DEFAULT in your + * supplied Intent. + */ + public static final int MATCH_DEFAULT_ONLY = 0x00010000; + + /** * Permission check result: this is returned by {@link #checkPermission} * if the permission has been granted to the given package. */ @@ -219,14 +227,6 @@ public abstract class PackageManager { */ public static final int SIGNATURE_UNKNOWN_PACKAGE = -4; - /** - * Resolution and querying flag: if set, only filters that support the - * {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for - * matching. This is a synonym for including the CATEGORY_DEFAULT in your - * supplied Intent. - */ - public static final int MATCH_DEFAULT_ONLY = 0x00010000; - public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; @@ -235,14 +235,24 @@ public abstract class PackageManager { * Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to * indicate that this package should be installed as forward locked, i.e. only the app itself * should have access to it's code and non-resource assets. + * @hide */ - public static final int FORWARD_LOCK_PACKAGE = 0x00000001; + public static final int INSTALL_FORWARD_LOCK = 0x00000001; /** * Flag parameter for {@link #installPackage} to indicate that you want to replace an already - * installed package, if one exists + * installed package, if one exists. + * @hide */ - public static final int REPLACE_EXISTING_PACKAGE = 0x00000002; + public static final int INSTALL_REPLACE_EXISTING = 0x00000002; + + /** + * Flag parameter for {@link #installPackage} to indicate that you want to + * allow test packages (those that have set android:testOnly in their + * manifest) to be installed. + * @hide + */ + public static final int INSTALL_ALLOW_TEST = 0x00000004; /** * Flag parameter for @@ -255,6 +265,7 @@ public abstract class PackageManager { /** * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} on success. + * @hide */ public static final int INSTALL_SUCCEEDED = 1; @@ -262,6 +273,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package is * already installed. + * @hide */ public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; @@ -269,6 +281,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package archive * file is invalid. + * @hide */ public static final int INSTALL_FAILED_INVALID_APK = -2; @@ -276,13 +289,15 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the URI passed in * is invalid. + * @hide */ public static final int INSTALL_FAILED_INVALID_URI = -3; /** * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if the package manager - * service found that the device didn't have enough storage space to install the app + * service found that the device didn't have enough storage space to install the app. + * @hide */ public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE = -4; @@ -290,6 +305,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if a * package is already installed with the same name. + * @hide */ public static final int INSTALL_FAILED_DUPLICATE_PACKAGE = -5; @@ -297,6 +313,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the requested shared user does not exist. + * @hide */ public static final int INSTALL_FAILED_NO_SHARED_USER = -6; @@ -305,6 +322,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * a previously installed package of the same name has a different signature * than the new package (and the old package's data was not removed). + * @hide */ public static final int INSTALL_FAILED_UPDATE_INCOMPATIBLE = -7; @@ -313,6 +331,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package is requested a shared user which is already installed on the * device and does not have matching signature. + * @hide */ public static final int INSTALL_FAILED_SHARED_USER_INCOMPATIBLE = -8; @@ -320,6 +339,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package uses a shared library that is not available. + * @hide */ public static final int INSTALL_FAILED_MISSING_SHARED_LIBRARY = -9; @@ -327,6 +347,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package uses a shared library that is not available. + * @hide */ public static final int INSTALL_FAILED_REPLACE_COULDNT_DELETE = -10; @@ -335,6 +356,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package failed while optimizing and validating its dex files, * either because there was not enough storage or the validation failed. + * @hide */ public static final int INSTALL_FAILED_DEXOPT = -11; @@ -343,6 +365,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package failed because the current SDK version is older than * that required by the package. + * @hide */ public static final int INSTALL_FAILED_OLDER_SDK = -12; @@ -351,14 +374,35 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the new package failed because it contains a content provider with the * same authority as a provider already installed in the system. + * @hide */ public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; /** + * Installation return code: this is passed to the {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because the current SDK version is newer than + * that required by the package. + * @hide + */ + public static final int INSTALL_FAILED_NEWER_SDK = -14; + + /** + * Installation return code: this is passed to the {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because it has specified that it is a test-only + * package and the caller has not supplied the {@link #INSTALL_ALLOW_TEST} + * flag. + * @hide + */ + public static final int INSTALL_FAILED_TEST_ONLY = -15; + + /** * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser was given a path that is not a file, or does not end with the expected * '.apk' extension. + * @hide */ public static final int INSTALL_PARSE_FAILED_NOT_APK = -100; @@ -366,6 +410,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser was unable to retrieve the AndroidManifest.xml file. + * @hide */ public static final int INSTALL_PARSE_FAILED_BAD_MANIFEST = -101; @@ -373,6 +418,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered an unexpected exception. + * @hide */ public static final int INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION = -102; @@ -380,6 +426,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser did not find any certificates in the .apk. + * @hide */ public static final int INSTALL_PARSE_FAILED_NO_CERTIFICATES = -103; @@ -387,6 +434,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser found inconsistent certificates on the files in the .apk. + * @hide */ public static final int INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES = -104; @@ -395,6 +443,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered a CertificateEncodingException in one of the * files in the .apk. + * @hide */ public static final int INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING = -105; @@ -402,6 +451,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered a bad or missing package name in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME = -106; @@ -409,6 +459,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered a bad shared user id name in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID = -107; @@ -416,6 +467,7 @@ public abstract class PackageManager { * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser encountered some structural problem in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_MANIFEST_MALFORMED = -108; @@ -424,6 +476,7 @@ public abstract class PackageManager { * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser did not find any actionable tags (instrumentation or application) * in the manifest. + * @hide */ public static final int INSTALL_PARSE_FAILED_MANIFEST_EMPTY = -109; @@ -1330,6 +1383,8 @@ public abstract class PackageManager { } /** + * @hide + * * Install a package. Since this may take a little while, the result will * be posted back to the given observer. An installation will fail if the calling context * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the @@ -1341,13 +1396,14 @@ public abstract class PackageManager { * @param observer An observer callback to get notified when the package installation is * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be * called when that happens. observer may be null to indicate that no callback is desired. - * @param flags - possible values: {@link #FORWARD_LOCK_PACKAGE}, - * {@link #REPLACE_EXISTING_PACKAGE} - * - * @see #installPackage(android.net.Uri) + * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK}, + * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}. + * @param installerPackageName Optional package name of the application that is performing the + * installation. This identifies which market the package came from. */ public abstract void installPackage( - Uri packageURI, IPackageInstallObserver observer, int flags); + Uri packageURI, IPackageInstallObserver observer, int flags, + String installerPackageName); /** * Attempts to delete a package. Since this may take a little while, the result will @@ -1366,6 +1422,15 @@ public abstract class PackageManager { */ public abstract void deletePackage( String packageName, IPackageDeleteObserver observer, int flags); + + /** + * Retrieve the package name of the application that installed a package. This identifies + * which market the package came from. + * + * @param packageName The name of the package to query + */ + public abstract String getInstallerPackageName(String packageName); + /** * Attempts to clear the user data directory of an application. * Since this may take a little while, the result will @@ -1471,17 +1536,6 @@ public abstract class PackageManager { IPackageStatsObserver observer); /** - * Install a package. - * - * @param packageURI The location of the package file to install - * - * @see #installPackage(android.net.Uri, IPackageInstallObserver, int) - */ - public void installPackage(Uri packageURI) { - installPackage(packageURI, null, 0); - } - - /** * Add a new package to the list of preferred packages. This new package * will be added to the front of the list (removed from its current location * if already listed), meaning it will now be preferred over all other diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index f9c4984..88907c1 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -59,6 +59,7 @@ public class PackageParser { private String mArchiveSourcePath; private String[] mSeparateProcesses; private int mSdkVersion; + private String mSdkCodename; private int mParseError = PackageManager.INSTALL_SUCCEEDED; @@ -123,8 +124,9 @@ public class PackageParser { mSeparateProcesses = procs; } - public void setSdkVersion(int sdkVersion) { + public void setSdkVersion(int sdkVersion, String codename) { mSdkVersion = sdkVersion; + mSdkCodename = codename; } private static final boolean isPackageFilename(String name) { @@ -613,9 +615,9 @@ public class PackageParser { int type; final Package pkg = new Package(pkgName); - pkg.mSystem = (flags&PARSE_IS_SYSTEM) != 0; boolean foundApp = false; - + boolean targetsSdk = false; + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifest); pkg.mVersionCode = sa.getInteger( @@ -721,19 +723,74 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("uses-sdk")) { + } else if (tagName.equals("uses-sdk")) { if (mSdkVersion > 0) { sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesSdk); - int vers = sa.getInt( - com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion, 0); + int minVers = 0; + String minCode = null; + int targetVers = 0; + String targetCode = null; + + TypedValue val = sa.peekValue( + com.android.internal.R.styleable.AndroidManifestUsesSdk_minSdkVersion); + if (val != null) { + if (val.type == TypedValue.TYPE_STRING && val.string != null) { + targetCode = minCode = val.string.toString(); + } else { + // If it's not a string, it's an integer. + minVers = val.data; + } + } + + val = sa.peekValue( + com.android.internal.R.styleable.AndroidManifestUsesSdk_targetSdkVersion); + if (val != null) { + if (val.type == TypedValue.TYPE_STRING && val.string != null) { + targetCode = minCode = val.string.toString(); + } else { + // If it's not a string, it's an integer. + targetVers = val.data; + } + } + + int maxVers = sa.getInt( + com.android.internal.R.styleable.AndroidManifestUsesSdk_maxSdkVersion, + mSdkVersion); sa.recycle(); - if (vers > mSdkVersion) { - outError[0] = "Requires newer sdk version #" + vers - + " (current version is #" + mSdkVersion + ")"; + if (targetCode != null) { + if (!targetCode.equals(mSdkCodename)) { + if (mSdkCodename != null) { + outError[0] = "Requires development platform " + targetCode + + " (current platform is " + mSdkCodename + ")"; + } else { + outError[0] = "Requires development platform " + targetCode + + " but this is a release platform."; + } + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; + } + // If the code matches, it definitely targets this SDK. + targetsSdk = true; + } else if (targetVers >= mSdkVersion) { + // If they have explicitly targeted our current version + // or something after it, then note this. + targetsSdk = true; + } + + if (minVers > mSdkVersion) { + outError[0] = "Requires newer sdk version #" + minVers + + " (current version is #" + mSdkVersion + ")"; + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; + } + + if (maxVers < mSdkVersion) { + outError[0] = "Requires older sdk version #" + maxVers + + " (current version is #" + mSdkVersion + ")"; mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; return null; } @@ -767,6 +824,10 @@ public class PackageParser { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY; } + if (targetsSdk) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_TARGETS_SDK; + } + if (pkg.usesLibraries.size() > 0) { pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()]; pkg.usesLibraries.toArray(pkg.usesLibraryFiles); @@ -1126,6 +1187,12 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_testOnly, + false)) { + ai.flags |= ApplicationInfo.FLAG_TEST_ONLY; + } + String str; str = sa.getNonResourceString( com.android.internal.R.styleable.AndroidManifestApplication_permission); @@ -2133,9 +2200,6 @@ public class PackageParser { // If this is a 3rd party app, this is the path of the zip file. public String mPath; - // True if this package is part of the system image. - public boolean mSystem; - // The version code declared for this package. public int mVersionCode; diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java index 6a560ce..fea63be 100755 --- a/core/java/android/inputmethodservice/Keyboard.java +++ b/core/java/android/inputmethodservice/Keyboard.java @@ -27,8 +27,7 @@ import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; import android.util.Xml; -import android.view.Display; -import android.view.WindowManager; +import android.util.DisplayMetrics; import java.io.IOException; import java.util.ArrayList; @@ -510,10 +509,11 @@ public class Keyboard { * @param modeId keyboard mode identifier */ public Keyboard(Context context, int xmlLayoutResId, int modeId) { - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - final Display display = wm.getDefaultDisplay(); - mDisplayWidth = display.getWidth(); - mDisplayHeight = display.getHeight(); + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + mDisplayWidth = dm.widthPixels; + mDisplayHeight = dm.heightPixels; + //Log.v(TAG, "keyboard's display metrics:" + dm); + mDefaultHorizontalGap = 0; mDefaultWidth = mDisplayWidth / 10; mDefaultVerticalGap = 0; diff --git a/core/java/android/net/http/CertificateValidatorCache.java b/core/java/android/net/http/CertificateValidatorCache.java index 54a1dbe..47661d5 100644 --- a/core/java/android/net/http/CertificateValidatorCache.java +++ b/core/java/android/net/http/CertificateValidatorCache.java @@ -236,15 +236,17 @@ class CertificateValidatorCache { } } - int hashLength = secureHash.length; - if (secureHash != null && 0 < hashLength) { - if (hashLength == mHash.length) { - for (int i = 0; i < hashLength; ++i) { - if (secureHash[i] != mHash[i]) { - return false; + if (secureHash != null) { + int hashLength = secureHash.length; + if (0 < hashLength) { + if (hashLength == mHash.length) { + for (int i = 0; i < hashLength; ++i) { + if (secureHash[i] != mHash[i]) { + return false; + } } + return true; } - return true; } } diff --git a/core/java/android/net/http/Request.java b/core/java/android/net/http/Request.java index df4fff0..aeb85a2 100644 --- a/core/java/android/net/http/Request.java +++ b/core/java/android/net/http/Request.java @@ -116,12 +116,17 @@ class Request { mBodyProvider = bodyProvider; mBodyLength = bodyLength; - if (bodyProvider == null) { + if (bodyProvider == null && !"POST".equalsIgnoreCase(method)) { mHttpRequest = new BasicHttpRequest(method, getUri()); } else { mHttpRequest = new BasicHttpEntityEnclosingRequest( method, getUri()); - setBodyProvider(bodyProvider, bodyLength); + // it is ok to have null entity for BasicHttpEntityEnclosingRequest. + // By using BasicHttpEntityEnclosingRequest, it will set up the + // correct content-length, content-type and content-encoding. + if (bodyProvider != null) { + setBodyProvider(bodyProvider, bodyLength); + } } addHeader(HOST_HEADER, getHostPort()); diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 333ba73..8a0fd58 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -61,6 +61,13 @@ public abstract class BatteryStats implements Parcelable { */ public static final int SCAN_WIFI_LOCK = 6; + /** + * A constant indicating a wifi multicast timer + * + * {@hide} + */ + public static final int WIFI_MULTICAST_ENABLED = 7; + /** * Include all of the data in the stats, including previously saved data. */ @@ -225,9 +232,13 @@ public abstract class BatteryStats implements Parcelable { public abstract void noteFullWifiLockReleasedLocked(); public abstract void noteScanWifiLockAcquiredLocked(); public abstract void noteScanWifiLockReleasedLocked(); + public abstract void noteWifiMulticastEnabledLocked(); + public abstract void noteWifiMulticastDisabledLocked(); public abstract long getWifiTurnedOnTime(long batteryRealtime, int which); public abstract long getFullWifiLockTime(long batteryRealtime, int which); public abstract long getScanWifiLockTime(long batteryRealtime, int which); + public abstract long getWifiMulticastTime(long batteryRealtime, + int which); /** * Note that these must match the constants in android.os.LocalPowerManager. diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 467c17f..5487c54 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -59,11 +59,47 @@ public class Build { public static final String RELEASE = getString("ro.build.version.release"); /** - * The user-visible SDK version of the framework. It is an integer starting at 1. + * The user-visible SDK version of the framework in its raw String + * representation; use {@link #SDK_INT} instead. + * + * @deprecated Use {@link #SDK_INT} to easily get this as an integer. */ public static final String SDK = getString("ro.build.version.sdk"); + + /** + * The user-visible SDK version of the framework; its possible + * values are defined in {@link Build.VERSION_CODES}. + */ + public static final int SDK_INT = SystemProperties.getInt( + "ro.build.version.sdk", 0); + + /** + * The current development codename, or the string "REL" if this is + * a release build. + */ + public static final String CODENAME = getString("ro.build.version.codename"); } + /** + * Enumeration of the currently known SDK version codes. These are the + * values that can be found in {@link VERSION#SDK}. Version numbers + * increment monotonically with each official platform release. + */ + public static class VERSION_CODES { + /** + * October 2008: The original, first, version of Android. Yay! + */ + public static final int BASE = 1; + /** + * February 2009: First Android update, officially called 1.1. + */ + public static final int BASE_1_1 = 2; + /** + * May 2009: Android 1.5. + */ + public static final int CUPCAKE = 3; + } + /** The type of build, like "user" or "eng". */ public static final String TYPE = getString("ro.build.type"); diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index a7de895..d9612af 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -30,6 +30,10 @@ import java.io.PrintWriter; import java.io.Reader; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import org.apache.harmony.dalvik.ddmc.Chunk; import org.apache.harmony.dalvik.ddmc.ChunkHandler; @@ -876,6 +880,17 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo /** + * Equivalent to <code>setFieldsOn(cl, false)</code>. + * + * @see #setFieldsOn(Class, boolean) + * + * @hide + */ + public static void setFieldsOn(Class<?> cl) { + setFieldsOn(cl, false); + } + + /** * Reflectively sets static fields of a class based on internal debugging * properties. This method is a no-op if android.util.Config.DEBUG is * false. @@ -887,7 +902,7 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * Class setup: define a class whose only fields are non-final, static * primitive types (except for "char") or Strings. In a static block * after the field definitions/initializations, pass the class to - * this method, Debug.setFieldsOn(). Example: + * this method, Debug.setFieldsOn(). Example: * <pre> * package com.example; * @@ -899,12 +914,18 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * public static String ns = null; * public static boolean b = false; * public static int i = 5; + * @Debug.DebugProperty * public static float f = 0.1f; + * @@Debug.DebugProperty * public static double d = 0.5d; * * // This MUST appear AFTER all fields are defined and initialized! * static { + * // Sets all the fields * Debug.setFieldsOn(MyDebugVars.class); + * + * // Sets only the fields annotated with @Debug.DebugProperty + * // Debug.setFieldsOn(MyDebugVars.class, true); * } * } * </pre> @@ -917,25 +938,31 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo * {@hide} * * @param cl The class to (possibly) modify + * @param partial If false, sets all static fields, otherwise, only set + * fields with the {@link android.os.Debug.DebugProperty} + * annotation * @throws IllegalArgumentException if any fields are final or non-static, * or if the type of the field does not match the type of * the internal debugging property value. */ - public static void setFieldsOn(Class<?> cl) { + public static void setFieldsOn(Class<?> cl, boolean partial) { if (Config.DEBUG) { if (debugProperties != null) { /* Only look for fields declared directly by the class, * so we don't mysteriously change static fields in superclasses. */ for (Field field : cl.getDeclaredFields()) { - final String propertyName = cl.getName() + "." + field.getName(); - boolean isStatic = Modifier.isStatic(field.getModifiers()); - boolean isFinal = Modifier.isFinal(field.getModifiers()); - if (!isStatic || isFinal) { - throw new IllegalArgumentException(propertyName + - " must be static and non-final"); + if (!partial || field.getAnnotation(DebugProperty.class) != null) { + final String propertyName = cl.getName() + "." + field.getName(); + boolean isStatic = Modifier.isStatic(field.getModifiers()); + boolean isFinal = Modifier.isFinal(field.getModifiers()); + + if (!isStatic || isFinal) { + throw new IllegalArgumentException(propertyName + + " must be static and non-final"); + } + modifyFieldIfSet(field, debugProperties, propertyName); } - modifyFieldIfSet(field, debugProperties, propertyName); } } } else { @@ -944,4 +971,15 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo ") called in non-DEBUG build"); } } + + /** + * Annotation to put on fields you want to set with + * {@link Debug#setFieldsOn(Class, boolean)}. + * + * @hide + */ + @Target({ ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface DebugProperty { + } } diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java index 47497e5..3679e47 100644 --- a/core/java/android/os/Power.java +++ b/core/java/android/os/Power.java @@ -80,10 +80,9 @@ public class Power public static native int setLastUserActivityTimeout(long ms); /** - * Turn the device off. - * - * This method is considered deprecated in favor of - * {@link android.policy.ShutdownThread.shutdownAfterDisablingRadio()}. + * Low-level function turn the device off immediately, without trying + * to be clean. Most people should use + * {@link android.internal.app.ShutdownThread} for a clean shutdown. * * @deprecated * @hide diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index daa99c2..8f12355 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -438,6 +438,7 @@ public class Time { * * @param s the string to parse * @return true if the resulting time value is in UTC time + * @throws android.util.TimeFormatException if s cannot be parsed. */ public boolean parse3339(String s) { if (nativeParse3339(s)) { diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 095f4f4..e4dd020 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -99,4 +99,24 @@ public class DisplayMetrics { xdpi = DEVICE_DENSITY; ydpi = DEVICE_DENSITY; } + + /** + * Set the display metrics' density and update parameters depend on it. + * @hide + */ + public void updateDensity(float newDensity) { + float ratio = newDensity / density; + density = newDensity; + scaledDensity = density; + widthPixels *= ratio; + heightPixels *= ratio; + xdpi *= ratio; + ydpi *= ratio; + } + + public String toString() { + return "DisplayMetrics{density=" + density + ", width=" + widthPixels + + ", height=" + heightPixels + ", scaledDensity=" + scaledDensity + + ", xdpi=" + xdpi + ", ydpi=" + ydpi + "}"; + } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 71da9cf..49e4e4c 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -257,6 +257,23 @@ public class SurfaceView extends View { } @Override + public boolean dispatchTouchEvent(MotionEvent event) { + // SurfaceView uses pre-scaled size unless fixed size is requested. This hook + // scales the event back to the pre-scaled coordinates for such surface. + if (mRequestedWidth < 0 && mAppScale != 1.0f) { + MotionEvent scaledBack = MotionEvent.obtain(event); + scaledBack.scale(mAppScale); + try { + return super.dispatchTouchEvent(scaledBack); + } finally { + scaledBack.recycle(); + } + } else { + return super.dispatchTouchEvent(event); + } + } + + @Override protected void dispatchDraw(Canvas canvas) { // if SKIP_DRAW is cleared, draw() has already punched a hole if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { @@ -278,7 +295,13 @@ public class SurfaceView extends View { if (myWidth <= 0) myWidth = getWidth(); int myHeight = mRequestedHeight; if (myHeight <= 0) myHeight = getHeight(); - + + // Use original size for surface unless fixed size is requested. + if (mRequestedWidth <= 0) { + myWidth *= mAppScale; + myHeight *= mAppScale; + } + getLocationInWindow(mLocation); final boolean creating = mWindow == null; final boolean formatChanged = mFormat != mRequestedFormat; @@ -304,8 +327,9 @@ public class SurfaceView extends View { mFormat = mRequestedFormat; mType = mRequestedType; - mLayout.x = mLeft; - mLayout.y = mTop; + // Scaling window's layout here beause mLayout is not used elsewhere. + mLayout.x = (int) (mLeft * mAppScale); + mLayout.y = (int) (mTop * mAppScale); mLayout.width = (int) (getWidth() * mAppScale); mLayout.height = (int) (getHeight() * mAppScale); mLayout.format = mRequestedFormat; @@ -334,7 +358,7 @@ public class SurfaceView extends View { mSurfaceLock.lock(); mDrawingStopped = !visible; final int relayoutResult = mSession.relayout( - mWindow, mLayout, (int) (mWidth * mAppScale), (int) (mHeight * mAppScale), + mWindow, mLayout, mWidth, mHeight, visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets, mVisibleInsets, mSurface); @@ -358,7 +382,7 @@ public class SurfaceView extends View { synchronized (mCallbacks) { callbacks = new SurfaceHolder.Callback[mCallbacks.size()]; mCallbacks.toArray(callbacks); - } + } if (visibleChanged) { mIsCreating = true; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d042f28..16b70ed 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -49,6 +49,7 @@ import android.util.Poolable; import android.util.Pool; import android.util.Pools; import android.util.PoolableManager; +import android.util.Config; import android.view.ContextMenu.ContextMenuInfo; import android.view.animation.Animation; import android.view.inputmethod.InputConnection; @@ -1405,6 +1406,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback { static final int SCROLL_CONTAINER_ADDED = 0x00100000; /** + * View flag indicating whether this view was invalidated (fully or partially.) + * + * @hide + */ + static final int DIRTY = 0x00200000; + + /** + * View flag indicating whether this view was invalidated by an opaque + * invalidate request. + * + * @hide + */ + static final int DIRTY_OPAQUE = 0x00400000; + + /** + * Mask for {@link #DIRTY} and {@link #DIRTY_OPAQUE}. + * + * @hide + */ + static final int DIRTY_MASK = 0x00600000; + + /** * The parent this view is attached to. * {@hide} * @@ -1420,7 +1443,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback { /** * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(flagMapping = { + @ViewDebug.FlagToString(mask = FORCE_LAYOUT, equals = FORCE_LAYOUT, + name = "FORCE_LAYOUT"), + @ViewDebug.FlagToString(mask = LAYOUT_REQUIRED, equals = LAYOUT_REQUIRED, + name = "LAYOUT_REQUIRED"), + @ViewDebug.FlagToString(mask = DRAWING_CACHE_VALID, equals = DRAWING_CACHE_VALID, + name = "DRAWING_CACHE_INVALID", outputIf = false), + @ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "DRAWN", outputIf = true), + @ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "NOT_DRAWN", outputIf = false), + @ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY_OPAQUE, name = "DIRTY_OPAQUE"), + @ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY, name = "DIRTY") + }) int mPrivateFlags; /** @@ -4522,6 +4556,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } /** + * Indicates whether this View is opaque. An opaque View guarantees that it will + * draw all the pixels overlapping its bounds using a fully opaque color. + * + * Subclasses of View should override this method whenever possible to indicate + * whether an instance is opaque. Opaque Views are treated in a special way by + * the View hierarchy, possibly allowing it to perform optimizations during + * invalidate/draw passes. + * + * @return True if this View is guaranteed to be fully opaque, false otherwise. + * + * @hide Pending API council approval + */ + @ViewDebug.ExportedProperty + public boolean isOpaque() { + return mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE; + } + + /** * @return A handler associated with the thread running the View. This * handler can be used to pump events in the UI events queue. */ @@ -5601,7 +5653,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE); } - if (ViewRoot.PROFILE_DRAWING) { + if (Config.DEBUG && ViewDebug.profileDrawing) { EventLog.writeEvent(60002, hashCode()); } @@ -5694,6 +5746,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); } + mPrivateFlags &= ~DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); @@ -5740,7 +5793,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { canvas = new Canvas(bitmap); } - if ((backgroundColor&0xff000000) != 0) { + if ((backgroundColor & 0xff000000) != 0) { bitmap.eraseColor(backgroundColor); } @@ -5748,6 +5801,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback { final int restoreCount = canvas.save(); canvas.translate(-mScrollX, -mScrollY); + // Temporarily remove the dirty mask + int flags = mPrivateFlags; + mPrivateFlags &= ~DIRTY_MASK; + // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { dispatchDraw(canvas); @@ -5755,6 +5812,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback { draw(canvas); } + mPrivateFlags = flags; + canvas.restoreToCount(restoreCount); if (attachInfo != null) { @@ -5875,7 +5934,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); } - mPrivateFlags |= DRAWN; + final int privateFlags = mPrivateFlags; + final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && + (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); + mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN; /* * Draw traversal performs several drawing steps which must be executed @@ -5892,22 +5954,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback { // Step 1, draw the background, if needed int saveCount; - final Drawable background = mBGDrawable; - if (background != null) { - final int scrollX = mScrollX; - final int scrollY = mScrollY; + if (!dirtyOpaque) { + final Drawable background = mBGDrawable; + if (background != null) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; - if (mBackgroundSizeChanged) { - background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); - mBackgroundSizeChanged = false; - } + if (mBackgroundSizeChanged) { + background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); + mBackgroundSizeChanged = false; + } - if ((scrollX | scrollY) == 0) { - background.draw(canvas); - } else { - canvas.translate(scrollX, scrollY); - background.draw(canvas); - canvas.translate(-scrollX, -scrollY); + if ((scrollX | scrollY) == 0) { + background.draw(canvas); + } else { + canvas.translate(scrollX, scrollY); + background.draw(canvas); + canvas.translate(-scrollX, -scrollY); + } } } @@ -5917,7 +5981,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content - onDraw(canvas); + if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); @@ -6020,7 +6084,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } // Step 3, draw the content - onDraw(canvas); + if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); @@ -7123,6 +7187,60 @@ public class View implements Drawable.Callback, KeyEvent.Callback { } /** + * @param consistency The type of consistency. See ViewDebug for more information. + * + * @hide + */ + protected boolean dispatchConsistencyCheck(int consistency) { + return onConsistencyCheck(consistency); + } + + /** + * Method that subclasses should implement to check their consistency. The type of + * consistency check is indicated by the bit field passed as a parameter. + * + * @param consistency The type of consistency. See ViewDebug for more information. + * + * @throws IllegalStateException if the view is in an inconsistent state. + * + * @hide + */ + protected boolean onConsistencyCheck(int consistency) { + boolean result = true; + + final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0; + final boolean checkDrawing = (consistency & ViewDebug.CONSISTENCY_DRAWING) != 0; + + if (checkLayout) { + if (getParent() == null) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, + "View " + this + " does not have a parent."); + } + + if (mAttachInfo == null) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, + "View " + this + " is not attached to a window."); + } + } + + if (checkDrawing) { + // Do not check the DIRTY/DRAWN flags because views can call invalidate() + // from their draw() method + + if ((mPrivateFlags & DRAWN) != DRAWN && + (mPrivateFlags & DRAWING_CACHE_VALID) == DRAWING_CACHE_VALID) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, + "View " + this + " was invalidated but its drawing cache is valid."); + } + } + + return result; + } + + /** * Prints information about this view in the log output, with the tag * {@link #VIEW_LOG_TAG}. * @@ -8049,7 +8167,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * window. */ static class AttachInfo { - interface Callbacks { void playSoundEffect(int effectId); boolean performHapticFeedback(int effectId, boolean always); @@ -8179,6 +8296,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback { long mDrawingTime; /** + * Indicates whether or not ignoring the DIRTY_MASK flags. + */ + boolean mIgnoreDirtyState; + + /** * Indicates whether the view's window is currently in touch mode. */ boolean mInTouchMode; diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 367c9a2..aaaadef 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -54,6 +54,27 @@ import java.lang.reflect.AccessibleObject; */ public class ViewDebug { /** + * Log tag used to log errors related to the consistency of the view hierarchy. + * + * @hide + */ + public static final String CONSISTENCY_LOG_TAG = "ViewConsistency"; + + /** + * Flag indicating the consistency check should check layout-related properties. + * + * @hide + */ + public static final int CONSISTENCY_LAYOUT = 0x1; + + /** + * Flag indicating the consistency check should check drawing-related properties. + * + * @hide + */ + public static final int CONSISTENCY_DRAWING = 0x2; + + /** * Enables or disables view hierarchy tracing. Any invoker of * {@link #trace(View, android.view.ViewDebug.HierarchyTraceType)} should first * check that this value is set to true as not to affect performance. @@ -80,6 +101,49 @@ public class ViewDebug { static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent"; /** + * Profiles drawing times in the events log. + * + * @hide + */ + @Debug.DebugProperty + public static boolean profileDrawing = false; + + /** + * Profiles layout times in the events log. + * + * @hide + */ + @Debug.DebugProperty + public static boolean profileLayout = false; + + /** + * Profiles real fps (times between draws) and displays the result. + * + * @hide + */ + @Debug.DebugProperty + public static boolean showFps = false; + + /** + * <p>Enables or disables views consistency check. Even when this property is enabled, + * view consistency checks happen only if {@link android.util.Config#DEBUG} is set + * to true. The value of this property can be configured externally in one of the + * following files:</p> + * <ul> + * <li>/system/debug.prop</li> + * <li>/debug.prop</li> + * <li>/data/debug.prop</li> + * </ul> + * @hide + */ + @Debug.DebugProperty + public static boolean consistencyCheckEnabled = false; + + static { + Debug.setFieldsOn(ViewDebug.class, true); + } + + /** * This annotation can be used to mark fields and methods to be dumped by * the view server. Only non-void methods with no arguments can be annotated * by this annotation. @@ -123,7 +187,7 @@ public class ViewDebug { * of an array: * * <pre> - * @ViewDebug.ExportedProperty(mapping = { + * @ViewDebug.ExportedProperty(indexMapping = { * @ViewDebug.IntToString(from = 0, to = "INVALID"), * @ViewDebug.IntToString(from = 1, to = "FIRST"), * @ViewDebug.IntToString(from = 2, to = "SECOND") @@ -139,6 +203,25 @@ public class ViewDebug { IntToString[] indexMapping() default { }; /** + * A flags mapping can be defined to map flags encoded in an integer to + * specific strings. A mapping can be used to see human readable values + * for the flags of an integer: + * + * <pre> + * @ViewDebug.ExportedProperty(flagMapping = { + * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = ENABLED, name = "ENABLED"), + * @ViewDebug.FlagToString(mask = ENABLED_MASK, equals = DISABLED, name = "DISABLED"), + * }) + * private int mFlags; + * <pre> + * + * A specified String is output when the following is true: + * + * @return An array of int to String mappings + */ + FlagToString[] flagMapping() default { }; + + /** * When deep export is turned on, this property is not dumped. Instead, the * properties contained in this property are dumped. Each child property * is prefixed with the name of this property. @@ -182,7 +265,45 @@ public class ViewDebug { */ String to(); } - + + /** + * Defines a mapping from an flag to a String. Such a mapping can be used + * in a @ExportedProperty to provide more meaningful values to the end user. + * + * @see android.view.ViewDebug.ExportedProperty + */ + @Target({ ElementType.TYPE }) + @Retention(RetentionPolicy.RUNTIME) + public @interface FlagToString { + /** + * The mask to apply to the original value. + * + * @return An arbitrary int value. + */ + int mask(); + + /** + * The value to compare to the result of: + * <code>original value & {@link #mask()}</code>. + * + * @return An arbitrary value. + */ + int equals(); + + /** + * The String to use in place of the original int value. + * + * @return An arbitrary non-null String. + */ + String name(); + + /** + * Indicates whether to output the flag when the test is true, + * or false. Defaults to true. + */ + boolean outputIf() default true; + } + /** * This annotation can be used to mark fields and methods to be dumped when * the view is captured. Methods with this annotation must have no arguments @@ -975,6 +1096,13 @@ public class ViewDebug { final int id = (Integer) methodValue; methodValue = resolveId(context, id); } else { + final FlagToString[] flagsMapping = property.flagMapping(); + if (flagsMapping.length > 0) { + final int intValue = (Integer) methodValue; + final String valuePrefix = prefix + method.getName() + '_'; + exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); + } + final IntToString[] mapping = property.mapping(); if (mapping.length > 0) { final int intValue = (Integer) methodValue; @@ -1036,6 +1164,13 @@ public class ViewDebug { final int id = field.getInt(view); fieldValue = resolveId(context, id); } else { + final FlagToString[] flagsMapping = property.flagMapping(); + if (flagsMapping.length > 0) { + final int intValue = field.getInt(view); + final String valuePrefix = prefix + field.getName() + '_'; + exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); + } + final IntToString[] mapping = property.mapping(); if (mapping.length > 0) { final int intValue = field.getInt(view); @@ -1093,6 +1228,23 @@ public class ViewDebug { out.write(' '); } + private static void exportUnrolledFlags(BufferedWriter out, FlagToString[] mapping, + int intValue, String prefix) throws IOException { + + final int count = mapping.length; + for (int j = 0; j < count; j++) { + final FlagToString flagMapping = mapping[j]; + final boolean ifTrue = flagMapping.outputIf(); + final int maskResult = intValue & flagMapping.mask(); + final boolean test = maskResult == flagMapping.equals(); + if ((test && ifTrue) || (!test && !ifTrue)) { + final String name = flagMapping.name(); + final String value = "0x" + Integer.toHexString(maskResult); + writeEntry(out, prefix, name, "", value); + } + } + } + private static void exportUnrolledArray(Context context, BufferedWriter out, ExportedProperty property, int[] array, String prefix, String suffix) throws IOException { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e686d1c..db5177f 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -32,6 +32,7 @@ import android.util.AttributeSet; import android.util.EventLog; import android.util.Log; import android.util.SparseArray; +import android.util.Config; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.LayoutAnimationController; @@ -1402,7 +1403,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - // Clear the flag as early as possible to allow draw() implementations + // Sets the flag as early as possible to allow draw() implementations // to call invalidate() successfully when doing animations child.mPrivateFlags |= DRAWN; @@ -1481,6 +1482,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW); } + child.mPrivateFlags &= ~DIRTY_MASK; child.dispatchDraw(canvas); } else { child.draw(canvas); @@ -1494,7 +1496,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager cachePaint.setAlpha(255); mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE; } - if (ViewRoot.PROFILE_DRAWING) { + if (Config.DEBUG && ViewDebug.profileDrawing) { EventLog.writeEvent(60003, hashCode()); } canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint); @@ -1796,7 +1798,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean preventRequestLayout) { child.mParent = null; addViewInner(child, index, params, preventRequestLayout); - child.mPrivateFlags |= DRAWN; + child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN; return true; } @@ -2210,7 +2212,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager addInArray(child, index); child.mParent = this; - child.mPrivateFlags |= DRAWN; + child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN; if (child.hasFocus()) { requestChildFocus(child, child.findFocus()); @@ -2320,15 +2322,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // ourselves and the parent to make sure the invalidate request goes // through final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION; - + + // Check whether the child that requests the invalidate is fully opaque + final boolean isOpaque = child.isOpaque(); + // Mark the child as dirty, using the appropriate flag + // Make sure we do not set both flags at the same time + final int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY; + do { + View view = null; + if (parent instanceof View) { + view = (View) parent; + } + if (drawAnimation) { - if (parent instanceof View) { - ((View) parent).mPrivateFlags |= DRAW_ANIMATION; + if (view != null) { + view.mPrivateFlags |= DRAW_ANIMATION; } else if (parent instanceof ViewRoot) { ((ViewRoot) parent).mIsAnimating = true; } } + + // If the parent is dirty opaque or not dirty, mark it dirty with the opaque + // flag coming from the child that initiated the invalidate + if (view != null && (view.mPrivateFlags & DIRTY_MASK) != DIRTY) { + view.mPrivateFlags = (view.mPrivateFlags & ~DIRTY_MASK) | opaqueFlag; + } + parent = parent.invalidateChildInParent(location, dirty); } while (parent != null); } @@ -2732,6 +2752,61 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * @hide + */ + @Override + protected boolean dispatchConsistencyCheck(int consistency) { + boolean result = super.dispatchConsistencyCheck(consistency); + + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + if (!children[i].dispatchConsistencyCheck(consistency)) result = false; + } + + return result; + } + + /** + * @hide + */ + @Override + protected boolean onConsistencyCheck(int consistency) { + boolean result = super.onConsistencyCheck(consistency); + + final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0; + final boolean checkDrawing = (consistency & ViewDebug.CONSISTENCY_DRAWING) != 0; + + if (checkLayout) { + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + if (children[i].getParent() != this) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, + "View " + children[i] + " has no parent/a parent that is not " + this); + } + } + } + + if (checkDrawing) { + // If this group is dirty, check that the parent is dirty as well + if ((mPrivateFlags & DIRTY_MASK) != 0) { + final ViewParent parent = getParent(); + if (parent != null && !(parent instanceof ViewRoot)) { + if ((((View) parent).mPrivateFlags & DIRTY_MASK) == 0) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, + "ViewGroup " + this + " is dirty but its parent is not: " + this); + } + } + } + } + + return result; + } + + /** * {@inheritDoc} */ @Override diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 0b03626..dbfb194 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -75,13 +75,6 @@ public final class ViewRoot extends Handler implements ViewParent, private static final boolean DEBUG_IMF = false || LOCAL_LOGV; private static final boolean WATCH_POINTER = false; - static final boolean PROFILE_DRAWING = false; - private static final boolean PROFILE_LAYOUT = false; - // profiles real fps (times between draws) and displays the result - private static final boolean SHOW_FPS = false; - // used by SHOW_FPS - private static int sDrawTime; - /** * Maximum time we allow the user to roll the trackball enough to generate * a key event, before resetting the counters. @@ -97,6 +90,8 @@ public final class ViewRoot extends Handler implements ViewParent, static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); + private static int sDrawTime; + long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); final TrackballAxis mTrackballAxisY = new TrackballAxis(); @@ -128,9 +123,10 @@ public final class ViewRoot extends Handler implements ViewParent, int mHeight; Rect mDirty; // will be a graphics.Region soon boolean mIsAnimating; - // TODO: change these to scaler class. - float mAppScale; - float mAppScaleInverted; // = 1.0f / mAppScale + // TODO: change these to scalar class. + private float mAppScale; + private float mAppScaleInverted; // = 1.0f / mAppScale + private int[] mWindowLayoutParamsBackup = null; final View.AttachInfo mAttachInfo; @@ -389,6 +385,9 @@ public final class ViewRoot extends Handler implements ViewParent, if (mView == null) { mView = view; mAppScale = mView.getContext().getApplicationScale(); + if (mAppScale != 1.0f) { + mWindowLayoutParamsBackup = new int[4]; + } mAppScaleInverted = 1.0f / mAppScale; mWindowAttributes.copyFrom(attrs); mSoftInputMode = attrs.softInputMode; @@ -478,7 +477,6 @@ public final class ViewRoot extends Handler implements ViewParent, synchronized (this) { int oldSoftInputMode = mWindowAttributes.softInputMode; mWindowAttributes.copyFrom(attrs); - mWindowAttributes.scale(mAppScale); if (newView) { mSoftInputMode = attrs.softInputMode; @@ -795,7 +793,7 @@ public final class ViewRoot extends Handler implements ViewParent, final Rect frame = mWinFrame; boolean initialized = false; boolean contentInsetsChanged = false; - boolean visibleInsetsChanged = false; + boolean visibleInsetsChanged; try { boolean hadSurface = mSurface.isValid(); int fl = 0; @@ -936,14 +934,22 @@ public final class ViewRoot extends Handler implements ViewParent, if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v( "ViewRoot", "Laying out " + host + " to (" + host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")"); - long startTime; - if (PROFILE_LAYOUT) { + long startTime = 0L; + if (Config.DEBUG && ViewDebug.profileLayout) { startTime = SystemClock.elapsedRealtime(); } host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); - if (PROFILE_LAYOUT) { + if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { + if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) { + throw new IllegalStateException("The view hierarchy is an inconsistent state," + + "please refer to the logs with the tag " + + ViewDebug.CONSISTENCY_LOG_TAG + " for more infomation."); + } + } + + if (Config.DEBUG && ViewDebug.profileLayout) { EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime); } @@ -1135,8 +1141,7 @@ public final class ViewRoot extends Handler implements ViewParent, } int yoff; - final boolean scrolling = mScroller != null - && mScroller.computeScrollOffset(); + final boolean scrolling = mScroller != null && mScroller.computeScrollOffset(); if (scrolling) { yoff = mScroller.getCurrY(); } else { @@ -1151,13 +1156,14 @@ public final class ViewRoot extends Handler implements ViewParent, if (mUseGL) { if (!dirty.isEmpty()) { Canvas canvas = mGlCanvas; - if (mGL!=null && canvas != null) { + if (mGL != null && canvas != null) { mGL.glDisable(GL_SCISSOR_TEST); mGL.glClearColor(0, 0, 0, 0); mGL.glClear(GL_COLOR_BUFFER_BIT); mGL.glEnable(GL_SCISSOR_TEST); mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mAttachInfo.mIgnoreDirtyState = true; mView.mPrivateFlags |= View.DRAWN; float scale = mAppScale; @@ -1168,14 +1174,19 @@ public final class ViewRoot extends Handler implements ViewParent, canvas.scale(scale, scale); } mView.draw(canvas); + if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { + mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); + } } finally { canvas.restoreToCount(saveCount); } + mAttachInfo.mIgnoreDirtyState = false; + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); checkEglErrors(); - if (SHOW_FPS) { + if (Config.DEBUG && ViewDebug.showFps) { int now = (int)SystemClock.elapsedRealtime(); if (sDrawTime != 0) { nativeShowFPS(canvas, now - sDrawTime); @@ -1191,8 +1202,10 @@ public final class ViewRoot extends Handler implements ViewParent, return; } - if (fullRedrawNeeded) + if (fullRedrawNeeded) { + mAttachInfo.mIgnoreDirtyState = true; dirty.union(0, 0, (int) (mWidth * mAppScale), (int) (mHeight * mAppScale)); + } if (DEBUG_ORIENTATION || DEBUG_DRAW) { Log.v("ViewRoot", "Draw " + mView + "/" @@ -1204,7 +1217,18 @@ public final class ViewRoot extends Handler implements ViewParent, Canvas canvas; try { + int left = dirty.left; + int top = dirty.top; + int right = dirty.right; + int bottom = dirty.bottom; + canvas = surface.lockCanvas(dirty); + + if (left != dirty.left || top != dirty.top || right != dirty.right || + bottom != dirty.bottom) { + mAttachInfo.mIgnoreDirtyState = true; + } + // TODO: Do this in native canvas.setDensityScale(mDensity); } catch (Surface.OutOfResourcesException e) { @@ -1216,7 +1240,7 @@ public final class ViewRoot extends Handler implements ViewParent, try { if (!dirty.isEmpty() || mIsAnimating) { - long startTime; + long startTime = 0L; if (DEBUG_ORIENTATION || DEBUG_DRAW) { Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w=" @@ -1224,7 +1248,7 @@ public final class ViewRoot extends Handler implements ViewParent, //canvas.drawARGB(255, 255, 0, 0); } - if (PROFILE_DRAWING) { + if (Config.DEBUG && ViewDebug.profileDrawing) { startTime = SystemClock.elapsedRealtime(); } @@ -1232,12 +1256,11 @@ public final class ViewRoot extends Handler implements ViewParent, // need to clear it before drawing so that the child will // properly re-composite its drawing on a transparent // background. This automatically respects the clip/dirty region - if (!canvas.isOpaque()) { - canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); - } else if (yoff != 0) { - // If we are applying an offset, we need to clear the area - // where the offset doesn't appear to avoid having garbage - // left in the blank areas. + // or + // If we are applying an offset, we need to clear the area + // where the offset doesn't appear to avoid having garbage + // left in the blank areas. + if (!canvas.isOpaque() || yoff != 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } @@ -1247,9 +1270,10 @@ public final class ViewRoot extends Handler implements ViewParent, mView.mPrivateFlags |= View.DRAWN; float scale = mAppScale; - Context cxt = mView.getContext(); if (DEBUG_DRAW) { - Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + ", appScale=" + mAppScale); + Context cxt = mView.getContext(); + Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + + ", appScale=" + mAppScale); } int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); try { @@ -1260,10 +1284,15 @@ public final class ViewRoot extends Handler implements ViewParent, } mView.draw(canvas); } finally { + mAttachInfo.mIgnoreDirtyState = false; canvas.restoreToCount(saveCount); } - if (SHOW_FPS) { + if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { + mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); + } + + if (Config.DEBUG && ViewDebug.showFps) { int now = (int)SystemClock.elapsedRealtime(); if (sDrawTime != 0) { nativeShowFPS(canvas, now - sDrawTime); @@ -1271,7 +1300,7 @@ public final class ViewRoot extends Handler implements ViewParent, sDrawTime = now; } - if (PROFILE_DRAWING) { + if (Config.DEBUG && ViewDebug.profileDrawing) { EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); } } @@ -2309,12 +2338,22 @@ public final class ViewRoot extends Handler implements ViewParent, private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { + + boolean restore = false; + if (params != null && mAppScale != 1.0f) { + restore = true; + params.scale(mAppScale, mWindowLayoutParamsBackup); + } int relayoutResult = sWindowSession.relayout( mWindow, params, (int) (mView.mMeasuredWidth * mAppScale), (int) (mView.mMeasuredHeight * mAppScale), viewVisibility, insetsPending, mWinFrame, mPendingContentInsets, mPendingVisibleInsets, mSurface); + if (restore) { + params.restore(mWindowLayoutParamsBackup); + } + mPendingContentInsets.scale(mAppScaleInverted); mPendingVisibleInsets.scale(mAppScaleInverted); mWinFrame.scale(mAppScaleInverted); @@ -2416,7 +2455,7 @@ public final class ViewRoot extends Handler implements ViewParent, msg.arg2 = handled ? 1 : 0; sendMessage(msg); } - + public void dispatchResized(int w, int h, Rect coveredInsets, Rect visibleInsets, boolean reportDraw) { if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": w=" + w diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index f6a171d..c69c281 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -18,6 +18,7 @@ package android.view; import android.content.pm.ActivityInfo; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -954,19 +955,42 @@ public interface WindowManager extends ViewManager { return sb.toString(); } - void scale(float scale) { + /** + * Scale the layout params' coordinates and size. + * Returns the original info as a backup so that the caller can + * restore the layout params; + */ + void scale(float scale, int[] backup) { if (scale != 1.0f) { + backup[0] = x; + backup[1] = y; x *= scale; y *= scale; if (width > 0) { + backup[2] = width; width *= scale; } if (height > 0) { + backup[3] = height; height *= scale; } } } + /** + * Restore the layout params' coordinates and size. + */ + void restore(int[] backup) { + x = backup[0]; + y = backup[1]; + if (width > 0) { + width = backup[2]; + } + if (height > 0) { + height = backup[3]; + } + } + private CharSequence mTitle = ""; } } diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java index be15ef8..667cb2c 100644 --- a/core/java/android/webkit/CallbackProxy.java +++ b/core/java/android/webkit/CallbackProxy.java @@ -99,6 +99,7 @@ class CallbackProxy extends Handler { private static final int RECEIVED_CERTIFICATE = 124; private static final int SWITCH_OUT_HISTORY = 125; private static final int EXCEEDED_DATABASE_QUOTA = 126; + private static final int JS_TIMEOUT = 127; // Message triggered by the client to resume execution private static final int NOTIFY = 200; @@ -548,6 +549,18 @@ class CallbackProxy extends Handler { } break; + case JS_TIMEOUT: + if(mWebChromeClient != null) { + final JsResult res = (JsResult) msg.obj; + if(mWebChromeClient.onJsTimeout()) { + res.confirm(); + } else { + res.cancel(); + } + res.setReady(); + } + break; + case RECEIVED_CERTIFICATE: mWebView.setCertificate((SslCertificate) msg.obj); break; @@ -1073,4 +1086,25 @@ class CallbackProxy extends Handler { sendMessage(exceededQuota); } + /** + * @hide pending API council approval + */ + public boolean onJsTimeout() { + //always interrupt timedout JS by default + if (mWebChromeClient == null) { + return true; + } + JsResult result = new JsResult(this, true); + Message timeout = obtainMessage(JS_TIMEOUT, result); + synchronized (this) { + sendMessage(timeout); + try { + wait(); + } catch (InterruptedException e) { + Log.e(LOGTAG, "Caught exception while waiting for jsUnload"); + Log.e(LOGTAG, Log.getStackTraceString(e)); + } + } + return result.getResult(); + } } diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index c0c6775..e8c2279 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -647,8 +647,6 @@ public final class CookieManager { // another file in the local web server directory. Still // "localhost" is the best pseudo domain name. ret[0] = "localhost"; - } else if (!ret[0].equals("localhost")) { - return null; } } else if (index == ret[0].lastIndexOf(PERIOD)) { // cookie host must have at least two periods diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 0b874fa..bd64f89 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -174,4 +174,19 @@ public class WebChromeClient { // WebCore will interpret this that new quota was declined. quotaUpdater.updateQuota(currentQuota); } + + /** + * Tell the client that a JavaScript execution timeout has occured. And the + * client may decide whether or not to interrupt the execution. If the + * client returns true, the JavaScript will be interrupted. If the client + * returns false, the execution will continue. Note that in the case of + * continuing execution, the timeout counter will be reset, and the callback + * will continue to occur if the script does not finish at the next check + * point. + * @return boolean Whether the JavaScript execution should be interrupted. + * @hide pending API Council approval + */ + public boolean onJsTimeout() { + return true; + } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 5a2cd26..883aa28 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -141,6 +141,7 @@ public class WebSettings { private boolean mBlockNetworkLoads; private boolean mJavaScriptEnabled = false; private boolean mPluginsEnabled = false; + private long mWebStorageDefaultQuota = 0; private boolean mJavaScriptCanOpenWindowsAutomatically = false; private boolean mUseDoubleTree = false; private boolean mUseWideViewport = false; @@ -903,6 +904,18 @@ public class WebSettings { } /** + * @hide + * Set the default quota for WebStorage DBs + * @param quota the default quota in bytes + */ + public synchronized void setWebStorageDefaultQuota(long quota) { + if (mWebStorageDefaultQuota != quota) { + mWebStorageDefaultQuota = quota; + postSync(); + } + } + + /** * Set the path to where database storage API databases should be saved. * This will update WebCore when the Sync runs in the C++ side. * @param databasePath String path to the directory where databases should @@ -996,6 +1009,15 @@ public class WebSettings { } /** + * @hide + * Return the default quota for WebStorage DBs + * @return the default quota in bytes + */ + public synchronized long getWebStorageDefaultQuota() { + return mWebStorageDefaultQuota; + } + + /** * Tell javascript to open windows automatically. This applies to the * javascript function window.open(). * @param flag True if javascript can open windows automatically. diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java index a0faf76..f27360d 100644 --- a/core/java/android/webkit/WebStorage.java +++ b/core/java/android/webkit/WebStorage.java @@ -16,6 +16,16 @@ package android.webkit; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.HashMap; +import java.util.Vector; + /** * Functionality for manipulating the webstorage databases. */ @@ -32,4 +42,242 @@ public final class WebStorage { public interface QuotaUpdater { public void updateQuota(long newQuota); }; + + // Log tag + private static final String TAG = "webstorage"; + + // Global instance of a WebStorage + private static WebStorage sWebStorage; + + // We keep a copy of the origins, quotas and usages + // that we protect via a lock and update in syncValues() + private static Lock mLock = new ReentrantLock(); + private static Condition mCacheUpdated = mLock.newCondition(); + + // Message ids + static final int UPDATE = 0; + static final int SET_QUOTA_ORIGIN = 1; + static final int DELETE_ORIGIN = 2; + static final int DELETE_ALL = 3; + + private Vector <String> mOrigins; + private HashMap <String, Long> mQuotas = new HashMap<String, Long>(); + private HashMap <String, Long> mUsages = new HashMap<String, Long>(); + + private Handler mHandler = null; + + private class Origin { + String mOrigin = null; + long mQuota = 0; + + public Origin(String origin, long quota) { + mOrigin = origin; + mQuota = quota; + } + + public Origin(String origin) { + mOrigin = origin; + } + + public String getOrigin() { + return mOrigin; + } + + public long getQuota() { + return mQuota; + } + } + + /** + * @hide + * Message handler + */ + public void createHandler() { + if (mHandler == null) { + mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SET_QUOTA_ORIGIN: { + Origin website = (Origin) msg.obj; + nativeSetQuotaForOrigin(website.getOrigin(), + website.getQuota()); + syncValues(); + } break; + + case DELETE_ORIGIN: { + Origin website = (Origin) msg.obj; + nativeDeleteOrigin(website.getOrigin()); + syncValues(); + } break; + + case DELETE_ALL: + nativeDeleteAllDatabases(); + syncValues(); + break; + + case UPDATE: + syncValues(); + break; + } + } + }; + } + } + + /** + * @hide + * Returns a list of origins having a database + */ + public Vector getOrigins() { + Vector ret = null; + mLock.lock(); + try { + update(); + mCacheUpdated.await(); + ret = mOrigins; + } catch (InterruptedException e) { + Log.e(TAG, "Exception while waiting on the updated origins", e); + } finally { + mLock.unlock(); + } + return ret; + } + + /** + * @hide + * Returns the use for a given origin + */ + public long getUsageForOrigin(String origin) { + long ret = 0; + if (origin == null) { + return ret; + } + mLock.lock(); + try { + update(); + mCacheUpdated.await(); + Long usage = mUsages.get(origin); + if (usage != null) { + ret = usage.longValue(); + } + } catch (InterruptedException e) { + Log.e(TAG, "Exception while waiting on the updated origins", e); + } finally { + mLock.unlock(); + } + return ret; + } + + /** + * @hide + * Returns the quota for a given origin + */ + public long getQuotaForOrigin(String origin) { + long ret = 0; + if (origin == null) { + return ret; + } + mLock.lock(); + try { + update(); + mCacheUpdated.await(); + Long quota = mQuotas.get(origin); + if (quota != null) { + ret = quota.longValue(); + } + } catch (InterruptedException e) { + Log.e(TAG, "Exception while waiting on the updated origins", e); + } finally { + mLock.unlock(); + } + return ret; + } + + /** + * @hide + * Set the quota for a given origin + */ + public void setQuotaForOrigin(String origin, long quota) { + if (origin != null) { + postMessage(Message.obtain(null, SET_QUOTA_ORIGIN, + new Origin(origin, quota))); + } + } + + /** + * @hide + * Delete a given origin + */ + public void deleteOrigin(String origin) { + if (origin != null) { + postMessage(Message.obtain(null, DELETE_ORIGIN, + new Origin(origin))); + } + } + + /** + * @hide + * Delete all databases + */ + public void deleteAllDatabases() { + postMessage(Message.obtain(null, DELETE_ALL)); + } + + /** + * Utility function to send a message to our handler + */ + private void postMessage(Message msg) { + if (mHandler != null) { + mHandler.sendMessage(msg); + } + } + + /** + * @hide + * Get the global instance of WebStorage. + * @return A single instance of WebStorage. + */ + public static WebStorage getInstance() { + if (sWebStorage == null) { + sWebStorage = new WebStorage(); + } + return sWebStorage; + } + + /** + * @hide + * Post a Sync request + */ + public void update() { + postMessage(Message.obtain(null, UPDATE)); + } + + /** + * Run on the webcore thread + * sync the local cached values with the real ones + */ + private void syncValues() { + mLock.lock(); + Vector tmp = nativeGetOrigins(); + mOrigins = new Vector<String>(); + mQuotas.clear(); + mUsages.clear(); + for (int i = 0; i < tmp.size(); i++) { + String origin = (String) tmp.get(i); + mOrigins.add(origin); + mQuotas.put(origin, new Long(nativeGetQuotaForOrigin(origin))); + mUsages.put(origin, new Long(nativeGetUsageForOrigin(origin))); + } + mCacheUpdated.signal(); + mLock.unlock(); + } + + // Native functions + private static native Vector nativeGetOrigins(); + private static native long nativeGetUsageForOrigin(String origin); + private static native long nativeGetQuotaForOrigin(String origin); + private static native void nativeSetQuotaForOrigin(String origin, long quota); + private static native void nativeDeleteOrigin(String origin); + private static native void nativeDeleteAllDatabases(); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 935e928..5b67b74 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnCancelListener; +import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -60,6 +61,7 @@ import android.view.inputmethod.InputMethodManager; import android.webkit.TextDialog.AutoCompleteAdapter; import android.webkit.WebViewCore.EventHub; import android.widget.AbsoluteLayout; +import android.widget.Adapter; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.FrameLayout; @@ -4402,7 +4404,7 @@ public class WebView extends AbsoluteLayout View v = mTextEntry; int x = viewToContent((v.getLeft() + v.getRight()) >> 1); int y = viewToContent((v.getTop() + v.getBottom()) >> 1); - nativeMotionUp(x, y, mNavSlop, true); + nativeMotionUp(x, y, mNavSlop); } } @@ -4414,7 +4416,7 @@ public class WebView extends AbsoluteLayout // mLastTouchX and mLastTouchY are the point in the current viewport int contentX = viewToContent((int) mLastTouchX + mScrollX); int contentY = viewToContent((int) mLastTouchY + mScrollY); - if (nativeMotionUp(contentX, contentY, mNavSlop, true)) { + if (nativeMotionUp(contentX, contentY, mNavSlop)) { if (mLogEvent) { Checkin.updateStats(mContext.getContentResolver(), Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0); @@ -4933,7 +4935,10 @@ public class WebView extends AbsoluteLayout @Override public boolean hasStableIds() { - return true; + // AdapterView's onChanged method uses this to determine whether + // to restore the old state. Return false so that the old (out + // of date) state does not replace the new, valid state. + return false; } private Container item(int position) { @@ -4997,6 +5002,51 @@ public class WebView extends AbsoluteLayout } } + /* + * Whenever the data set changes due to filtering, this class ensures + * that the checked item remains checked. + */ + private class SingleDataSetObserver extends DataSetObserver { + private long mCheckedId; + private ListView mListView; + private Adapter mAdapter; + + /* + * Create a new observer. + * @param id The ID of the item to keep checked. + * @param l ListView for getting and clearing the checked states + * @param a Adapter for getting the IDs + */ + public SingleDataSetObserver(long id, ListView l, Adapter a) { + mCheckedId = id; + mListView = l; + mAdapter = a; + } + + public void onChanged() { + // The filter may have changed which item is checked. Find the + // item that the ListView thinks is checked. + int position = mListView.getCheckedItemPosition(); + long id = mAdapter.getItemId(position); + if (mCheckedId != id) { + // Clear the ListView's idea of the checked item, since + // it is incorrect + mListView.clearChoices(); + // Search for mCheckedId. If it is in the filtered list, + // mark it as checked + int count = mAdapter.getCount(); + for (int i = 0; i < count; i++) { + if (mAdapter.getItemId(i) == mCheckedId) { + mListView.setItemChecked(i, true); + break; + } + } + } + } + + public void onInvalidate() {} + } + public void run() { final ListView listView = (ListView) LayoutInflater.from(mContext) .inflate(com.android.internal.R.layout.select_dialog, null); @@ -5030,8 +5080,7 @@ public class WebView extends AbsoluteLayout // filtered. Do not allow filtering on multiple lists until // that bug is fixed. - // Disable filter altogether - // listView.setTextFilterEnabled(!mMultiple); + listView.setTextFilterEnabled(!mMultiple); if (mMultiple) { listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); int length = mSelectedArray.length; @@ -5051,6 +5100,9 @@ public class WebView extends AbsoluteLayout listView.setSelection(mSelection); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); listView.setItemChecked(mSelection, true); + DataSetObserver observer = new SingleDataSetObserver( + adapter.getItemId(mSelection), listView, adapter); + adapter.registerDataSetObserver(observer); } } dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @@ -5120,13 +5172,12 @@ public class WebView extends AbsoluteLayout // called by JNI private void sendMotionUp(int touchGeneration, int buildGeneration, - int frame, int node, int x, int y, int size, boolean isClick, + int frame, int node, int x, int y, int size, boolean retry) { WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); touchUpData.mMoveGeneration = touchGeneration; touchUpData.mBuildGeneration = buildGeneration; touchUpData.mSize = size; - touchUpData.mIsClick = isClick; touchUpData.mRetry = retry; mFocusData.mFrame = touchUpData.mFrame = frame; mFocusData.mNode = touchUpData.mNode = node; @@ -5263,7 +5314,7 @@ public class WebView extends AbsoluteLayout private native void nativeInstrumentReport(); private native void nativeMarkNodeInvalid(int node); // return true if the page has been scrolled - private native boolean nativeMotionUp(int x, int y, int slop, boolean isClick); + private native boolean nativeMotionUp(int x, int y, int slop); // returns false if it handled the key private native boolean nativeMoveFocus(int keyCode, int count, boolean noScroll); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index b364952..1c83264 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -143,6 +143,8 @@ final class WebViewCore { // The WebIconDatabase needs to be initialized within the UI thread so // just request the instance here. WebIconDatabase.getInstance(); + // Create the WebStorage singleton + WebStorage.getInstance(); // Send a message to initialize the WebViewCore. Message init = sWebCoreHandler.obtainMessage( WebCoreThread.INITIALIZE, this); @@ -162,6 +164,8 @@ final class WebViewCore { mSettings.syncSettingsAndCreateHandler(mBrowserFrame); // Create the handler and transfer messages for the IconDatabase WebIconDatabase.getInstance().createHandler(); + // Create the handler for WebStorage + WebStorage.getInstance().createHandler(); // The transferMessages call will transfer all pending messages to the // WebCore thread handler. mEventHub.transferMessages(); @@ -284,6 +288,16 @@ final class WebViewCore { return mCallbackProxy.onJsBeforeUnload(url, message); } + /** + * + * Callback to notify that a JavaScript execution timeout has occured. + * @return True if the JavaScript execution should be interrupted. False + * will continue the execution. + */ + protected boolean jsInterrupt() { + return mCallbackProxy.onJsTimeout(); + } + //------------------------------------------------------------------------- // JNI methods //------------------------------------------------------------------------- @@ -370,7 +384,7 @@ final class WebViewCore { private native void nativeTouchUp(int touchGeneration, int buildGeneration, int framePtr, int nodePtr, int x, int y, - int size, boolean isClick, boolean retry); + int size, boolean retry); private native boolean nativeHandleTouchEvent(int action, int x, int y); @@ -526,7 +540,6 @@ final class WebViewCore { int mX; int mY; int mSize; - boolean mIsClick; boolean mRetry; } @@ -892,7 +905,7 @@ final class WebViewCore { touchUpData.mBuildGeneration, touchUpData.mFrame, touchUpData.mNode, touchUpData.mX, touchUpData.mY, - touchUpData.mSize, touchUpData.mIsClick, + touchUpData.mSize, touchUpData.mRetry); break; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 767c7e7..1ca59b2 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -433,7 +433,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private InputConnection mDefInputConnection; private InputConnectionWrapper mPublicInputConnection; - + + private Runnable mClearScrollingCache; + /** * Interface definition for a callback to be invoked when the list or grid * has been scrolled. @@ -2299,6 +2301,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } if (more) { + invalidate(); mLastFlingY = y; post(this); } else { @@ -2322,16 +2325,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } private void clearScrollingCache() { - if (mCachingStarted) { - setChildrenDrawnWithCacheEnabled(false); - if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { - setChildrenDrawingCacheEnabled(false); - } - if (!isAlwaysDrawnWithCacheEnabled()) { - invalidate(); - } - mCachingStarted = false; + if (mClearScrollingCache == null) { + mClearScrollingCache = new Runnable() { + public void run() { + if (mCachingStarted) { + mCachingStarted = false; + setChildrenDrawnWithCacheEnabled(false); + if ((mPersistentDrawingCache & PERSISTENT_SCROLLING_CACHE) == 0) { + setChildrenDrawingCacheEnabled(false); + } + if (!isAlwaysDrawnWithCacheEnabled()) { + invalidate(); + } + } + } + }; } + post(mClearScrollingCache); } /** @@ -2788,7 +2798,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te int screenHeight = getResources().getDisplayMetrics().heightPixels; final int[] xy = new int[2]; getLocationOnScreen(xy); - // TODO: The 20 below should come from the theme and be expressed in dip + // TODO: The 20 below should come from the theme // TODO: And the gravity should be defined in the theme as well final int bottomGap = screenHeight - xy[1] - getHeight() + (int) (mDensityScale * 20); if (!mPopup.isShowing()) { @@ -3180,7 +3190,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // Reclaim views on screen for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - AbsListView.LayoutParams lp = (AbsListView.LayoutParams)child.getLayoutParams(); + AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // Don't reclaim header or footer views, or views that should be ignored if (lp != null && mRecycler.shouldRecycleViewType(lp.viewType)) { views.add(child); @@ -3195,6 +3205,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * @hide + */ + @Override + protected boolean onConsistencyCheck(int consistency) { + boolean result = super.onConsistencyCheck(consistency); + + final boolean checkLayout = (consistency & ViewDebug.CONSISTENCY_LAYOUT) != 0; + + if (checkLayout) { + // The active recycler must be empty + final View[] activeViews = mRecycler.mActiveViews; + int count = activeViews.length; + for (int i = 0; i < count; i++) { + if (activeViews[i] != null) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, + "AbsListView " + this + " has a view in its active recycler: " + + activeViews[i]); + } + } + + // All views in the recycler must NOT be on screen and must NOT have a parent + final ArrayList<View> scrap = mRecycler.mCurrentScrap; + if (!checkScrap(scrap)) result = false; + final ArrayList<View>[] scraps = mRecycler.mScrapViews; + count = scraps.length; + for (int i = 0; i < count; i++) { + if (!checkScrap(scraps[i])) result = false; + } + } + + return result; + } + + private boolean checkScrap(ArrayList<View> scrap) { + if (scrap == null) return true; + boolean result = true; + + final int count = scrap.size(); + for (int i = 0; i < count; i++) { + final View view = scrap.get(i); + if (view.getParent() != null) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this + + " has a view in its scrap heap still attached to a parent: " + view); + } + if (indexOfChild(view) >= 0) { + result = false; + android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this + + " has a view in its scrap heap that is also a direct child: " + view); + } + } + + return result; + } + + /** * Sets the recycler listener to be notified whenever a View is set aside in * the recycler for later reuse. This listener can be used to free resources * associated to the View. diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 8da7c6b..bfc5f08 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -126,6 +126,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe // Indicates whether this AutoCompleteTextView is attached to a window or not // The widget is attached to a window when mAttachCount > 0 private int mAttachCount; + + private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener; public AutoCompleteTextView(Context context) { this(context, null); @@ -186,6 +188,28 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe setFocusable(true); addTextChangedListener(new MyWatcher()); + + mPassThroughClickListener = new PassThroughClickListener(); + super.setOnClickListener(mPassThroughClickListener); + } + + @Override + public void setOnClickListener(OnClickListener listener) { + mPassThroughClickListener.mWrapped = listener; + } + + /** + * Private hook into the on click event, dispatched from {@link PassThroughClickListener} + */ + private void onClickImpl() { + // if drop down should always visible, bring it back in front of the soft + // keyboard when the user touches the text field + if (mDropDownAlwaysVisible + && mPopup.isShowing() + && mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED) { + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + showDropDown(); + } } /** @@ -1130,9 +1154,14 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } } - // Max height available on the screen for a popup - final int maxHeight = - mPopup.getMaxAvailableHeight(getDropDownAnchorView(), mDropDownVerticalOffset); + // Max height available on the screen for a popup. If this AutoCompleteTextView has + // the dropDownAlwaysVisible attribute, and the input method is not currently required, + // we then we ask for the height ignoring any bottom decorations like the input method. + // Otherwise we respect the input method. + boolean ignoreBottomDecorations = mDropDownAlwaysVisible && + mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; + final int maxHeight = mPopup.getMaxAvailableHeight( + getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations); final int measuredHeight = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED, 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights; @@ -1214,7 +1243,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - mPopup.update(); + showDropDown(); } return false; } @@ -1353,4 +1382,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe */ CharSequence fixText(CharSequence invalidText); } + + /** + * Allows us a private hook into the on click event without preventing users from setting + * their own click listener. + */ + private class PassThroughClickListener implements OnClickListener { + + private View.OnClickListener mWrapped; + + /** {@inheritDoc} */ + public void onClick(View v) { + onClickImpl(); + + if (mWrapped != null) mWrapped.onClick(v); + } + } + } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 66c162e..5472d68 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; import android.os.Parcel; @@ -113,7 +114,11 @@ public class ListView extends AbsListView { Drawable mDivider; int mDividerHeight; + + private boolean mIsCacheColorOpaque; + private boolean mDividerIsOpaque; private boolean mClipDivider; + private boolean mHeaderDividersEnabled; private boolean mFooterDividersEnabled; @@ -2776,6 +2781,20 @@ public class ListView extends AbsListView { return mItemsCanFocus; } + /** + * @hide Pending API council approval. + */ + @Override + public boolean isOpaque() { + return (mCachingStarted && mIsCacheColorOpaque && mDividerIsOpaque) || super.isOpaque(); + } + + @Override + public void setCacheColorHint(int color) { + mIsCacheColorOpaque = (color >>> 24) == 0xFF; + super.setCacheColorHint(color); + } + @Override protected void dispatchDraw(Canvas canvas) { // Draw the dividers @@ -2897,6 +2916,7 @@ public class ListView extends AbsListView { mClipDivider = false; } mDivider = divider; + mDividerIsOpaque = divider == null || divider.getOpacity() == PixelFormat.OPAQUE; requestLayoutIfNecessary(); } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 2c9714e..78c7bd8 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -25,6 +25,7 @@ import android.view.WindowManager; import android.view.Gravity; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.view.WindowManagerImpl; import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.View.OnTouchListener; import android.graphics.PixelFormat; @@ -72,8 +73,8 @@ public class PopupWindow { */ public static final int INPUT_METHOD_NOT_NEEDED = 2; - private final Context mContext; - private final WindowManager mWindowManager; + private Context mContext; + private WindowManager mWindowManager; private boolean mIsShowing; private boolean mIsDropdown; @@ -158,8 +159,7 @@ public class PopupWindow { */ public PopupWindow(Context context, AttributeSet attrs, int defStyle) { mContext = context; - mWindowManager = (WindowManager)context.getSystemService( - Context.WINDOW_SERVICE); + mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); TypedArray a = context.obtainStyledAttributes( @@ -272,11 +272,11 @@ public class PopupWindow { * @param height the popup's height * @param focusable true if the popup can be focused, false otherwise */ - public PopupWindow(View contentView, int width, int height, - boolean focusable) { - mContext = contentView.getContext(); - mWindowManager = (WindowManager)mContext.getSystemService( - Context.WINDOW_SERVICE); + public PopupWindow(View contentView, int width, int height, boolean focusable) { + if (contentView != null) { + mContext = contentView.getContext(); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + } setContentView(contentView); setWidth(width); setHeight(height); @@ -373,6 +373,14 @@ public class PopupWindow { } mContentView = contentView; + + if (mContext == null) { + mContext = mContentView.getContext(); + } + + if (mWindowManager == null) { + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + } } /** @@ -747,6 +755,11 @@ public class PopupWindow { * @param p the layout parameters of the popup's content view */ private void preparePopup(WindowManager.LayoutParams p) { + if (mContentView == null || mContext == null || mWindowManager == null) { + throw new IllegalStateException("You must specify a valid content view by " + + "calling setContentView() before attempting to show the popup."); + } + if (mBackground != null) { final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams(); int height = ViewGroup.LayoutParams.FILL_PARENT; @@ -948,14 +961,38 @@ public class PopupWindow { * shown. */ public int getMaxAvailableHeight(View anchor, int yOffset) { + return getMaxAvailableHeight(anchor, yOffset, false); + } + + /** + * Returns the maximum height that is available for the popup to be + * completely shown, optionally ignoring any bottom decorations such as + * the input method. It is recommended that this height be the maximum for + * the popup's height, otherwise it is possible that the popup will be + * clipped. + * + * @param anchor The view on which the popup window must be anchored. + * @param yOffset y offset from the view's bottom edge + * @param ignoreBottomDecorations if true, the height returned will be + * all the way to the bottom of the display, ignoring any + * bottom decorations + * @return The maximum available height for the popup to be completely + * shown. + * + * @hide Pending API council approval. + */ + public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) { final Rect displayFrame = new Rect(); anchor.getWindowVisibleDisplayFrame(displayFrame); final int[] anchorPos = mDrawingLocation; anchor.getLocationOnScreen(anchorPos); - final int distanceToBottom = displayFrame.bottom - - (anchorPos[1] + anchor.getHeight()) - yOffset; + int bottomEdge = displayFrame.bottom; + if (ignoreBottomDecorations) { + bottomEdge = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight(); + } + final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset; final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset; // anchorPos[1] is distance from anchor to top of screen @@ -1116,7 +1153,7 @@ public class PopupWindow { p.flags = newFlags; update = true; } - + if (update) { mWindowManager.updateViewLayout(mPopupView, p); } diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index c4f0abd..edbb3db 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -823,7 +823,7 @@ public class RelativeLayout extends ViewGroup { @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf") }, mapping = { @ViewDebug.IntToString(from = TRUE, to = "true"), - @ViewDebug.IntToString(from = 0, to = "NO_ID") + @ViewDebug.IntToString(from = 0, to = "FALSE/NO_ID") }) private int[] mRules = new int[VERB_COUNT]; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index c6b0187..adfc74f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4434,29 +4434,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean reportExtractedText() { final InputMethodState ims = mInputMethodState; - final boolean contentChanged = ims.mContentChanged; - if (ims != null && (contentChanged || ims.mSelectionModeChanged)) { - ims.mContentChanged = false; - ims.mSelectionModeChanged = false; - final ExtractedTextRequest req = mInputMethodState.mExtracting; - if (req != null) { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start=" - + ims.mChangedStart + " end=" + ims.mChangedEnd - + " delta=" + ims.mChangedDelta); - if (ims.mChangedStart < 0 && !contentChanged) { - ims.mChangedStart = EXTRACT_NOTHING; - } - if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, - ims.mChangedDelta, ims.mTmpExtracted)) { - if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start=" - + ims.mTmpExtracted.partialStartOffset - + " end=" + ims.mTmpExtracted.partialEndOffset - + ": " + ims.mTmpExtracted.text); - imm.updateExtractedText(this, req.token, - mInputMethodState.mTmpExtracted); - return true; + if (ims != null) { + final boolean contentChanged = ims.mContentChanged; + if (contentChanged || ims.mSelectionModeChanged) { + ims.mContentChanged = false; + ims.mSelectionModeChanged = false; + final ExtractedTextRequest req = mInputMethodState.mExtracting; + if (req != null) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start=" + + ims.mChangedStart + " end=" + ims.mChangedEnd + + " delta=" + ims.mChangedDelta); + if (ims.mChangedStart < 0 && !contentChanged) { + ims.mChangedStart = EXTRACT_NOTHING; + } + if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, + ims.mChangedDelta, ims.mTmpExtracted)) { + if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start=" + + ims.mTmpExtracted.partialStartOffset + + " end=" + ims.mTmpExtracted.partialEndOffset + + ": " + ims.mTmpExtracted.text); + imm.updateExtractedText(this, req.token, + mInputMethodState.mTmpExtracted); + return true; + } } } } |