diff options
Diffstat (limited to 'core/java')
51 files changed, 1218 insertions, 552 deletions
diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java index 448b2c0..acc8549 100644 --- a/core/java/android/accounts/ChooseAccountTypeActivity.java +++ b/core/java/android/accounts/ChooseAccountTypeActivity.java @@ -43,7 +43,7 @@ import java.util.Set; * @hide */ public class ChooseAccountTypeActivity extends Activity { - private static final String TAG = "AccountManager"; + private static final String TAG = "AccountChooser"; private HashMap<String, AuthInfo> mTypeToAuthenticatorInfo = new HashMap<String, AuthInfo>(); private ArrayList<AuthInfo> mAuthenticatorInfosToDisplay; @@ -52,6 +52,11 @@ public class ChooseAccountTypeActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseAccountTypeActivity.onCreate(savedInstanceState=" + + savedInstanceState + ")"); + } + // Read the validAccountTypes, if present, and add them to the setOfAllowableAccountTypes Set<String> setOfAllowableAccountTypes = null; String[] validAccountTypes = getIntent().getStringArrayExtra( @@ -111,8 +116,10 @@ public class ChooseAccountTypeActivity extends Activity { Bundle bundle = new Bundle(); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type); setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); - Log.d(TAG, "ChooseAccountTypeActivity.setResultAndFinish: " - + "selected account type " + type); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseAccountTypeActivity.setResultAndFinish: " + + "selected account type " + type); + } finish(); } diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 8cc2002..5f38eb4 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -47,7 +47,7 @@ import java.util.Set; */ public class ChooseTypeAndAccountActivity extends Activity implements AccountManagerCallback<Bundle> { - private static final String TAG = "AccountManager"; + private static final String TAG = "AccountChooser"; /** * A Parcelable ArrayList of Account objects that limits the choosable accounts to those @@ -100,13 +100,39 @@ public class ChooseTypeAndAccountActivity extends Activity public static final String EXTRA_DESCRIPTION_TEXT_OVERRIDE = "descriptionTextOverride"; + public static final int REQUEST_NULL = 0; + public static final int REQUEST_CHOOSE_TYPE = 1; + public static final int REQUEST_ADD_ACCOUNT = 2; + + private static final String KEY_INSTANCE_STATE_PENDING_REQUEST = "pendingRequest"; + private static final String KEY_INSTANCE_STATE_EXISTING_ACCOUNTS = "existingAccounts"; + private ArrayList<AccountInfo> mAccountInfos; + private int mPendingRequest = REQUEST_NULL; + private Parcelable[] mExistingAccounts = null; + private Parcelable[] mSavedAccounts = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onCreate(savedInstanceState=" + + savedInstanceState + ")"); + } + setContentView(R.layout.choose_type_and_account); + if (savedInstanceState != null) { + mPendingRequest = savedInstanceState.getInt(KEY_INSTANCE_STATE_PENDING_REQUEST); + mSavedAccounts = + savedInstanceState.getParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS); + mExistingAccounts = null; + } else { + mPendingRequest = REQUEST_NULL; + mSavedAccounts = null; + mExistingAccounts = null; + } + // save some items we use frequently final AccountManager accountManager = AccountManager.get(this); final Intent intent = getIntent(); @@ -171,20 +197,6 @@ public class ChooseTypeAndAccountActivity extends Activity account.equals(selectedAccount))); } - // If there are no allowable accounts go directly to add account - if (mAccountInfos.isEmpty()) { - startChooseAccountTypeActivity(); - return; - } - - // if there is only one allowable account return it - if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false) - && mAccountInfos.size() == 1) { - Account account = mAccountInfos.get(0).account; - setResultAndFinish(account.name, account.type); - return; - } - // there is more than one allowable account. initialize the list adapter to allow // the user to select an account. ListView list = (ListView) findViewById(android.R.id.list); @@ -204,6 +216,37 @@ public class ChooseTypeAndAccountActivity extends Activity startChooseAccountTypeActivity(); } }); + + if (mPendingRequest == REQUEST_NULL) { + // If there are no allowable accounts go directly to add account + if (mAccountInfos.isEmpty()) { + startChooseAccountTypeActivity(); + return; + } + + // if there is only one allowable account return it + if (!intent.getBooleanExtra(EXTRA_ALWAYS_PROMPT_FOR_ACCOUNT, false) + && mAccountInfos.size() == 1) { + Account account = mAccountInfos.get(0).account; + setResultAndFinish(account.name, account.type); + return; + } + } + } + + @Override + protected void onDestroy() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onDestroy()"); + } + super.onDestroy(); + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(KEY_INSTANCE_STATE_PENDING_REQUEST, mPendingRequest); + outState.putParcelableArray(KEY_INSTANCE_STATE_EXISTING_ACCOUNTS, mExistingAccounts); } // Called when the choose account type activity (for adding an account) returns. @@ -212,20 +255,75 @@ public class ChooseTypeAndAccountActivity extends Activity @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - if (resultCode == RESULT_OK && data != null) { - String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); - if (accountType != null) { - runAddAccountForAuthenticator(accountType); - return; + if (Log.isLoggable(TAG, Log.VERBOSE)) { + if (data != null && data.getExtras() != null) data.getExtras().keySet(); + Bundle extras = data != null ? data.getExtras() : null; + Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult(reqCode=" + requestCode + + ", resCode=" + resultCode + ", extras=" + extras + ")"); + } + + // we got our result, so clear the fact that we had a pending request + mPendingRequest = REQUEST_NULL; + mExistingAccounts = null; + + if (resultCode == RESULT_CANCELED) { + return; + } + + if (resultCode == RESULT_OK) { + if (requestCode == REQUEST_CHOOSE_TYPE) { + if (data != null) { + String accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + if (accountType != null) { + runAddAccountForAuthenticator(accountType); + return; + } + } + Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find account " + + "type, pretending the request was canceled"); + } else if (requestCode == REQUEST_ADD_ACCOUNT) { + String accountName = null; + String accountType = null; + + if (data != null) { + accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); + accountType = data.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE); + } + + if (accountName == null || accountType == null) { + Account[] currentAccounts = AccountManager.get(this).getAccounts(); + Set<Account> preExistingAccounts = new HashSet<Account>(); + for (Parcelable accountParcel : mSavedAccounts) { + preExistingAccounts.add((Account) accountParcel); + } + for (Account account : currentAccounts) { + if (!preExistingAccounts.contains(account)) { + accountName = account.name; + accountType = account.type; + break; + } + } + } + + if (accountName != null || accountType != null) { + setResultAndFinish(accountName, accountType); + return; + } } + Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: unable to find added " + + "account, pretending the request was canceled"); + } + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled"); } - Log.d(TAG, "ChooseTypeAndAccountActivity.onActivityResult: canceled"); setResult(Activity.RESULT_CANCELED); finish(); } protected void runAddAccountForAuthenticator(String type) { - Log.d(TAG, "selected account type " + type); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "runAddAccountForAuthenticator: " + type); + } final Bundle options = getIntent().getBundleExtra( ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_OPTIONS_BUNDLE); final String[] requiredFeatures = getIntent().getStringArrayExtra( @@ -233,20 +331,19 @@ public class ChooseTypeAndAccountActivity extends Activity final String authTokenType = getIntent().getStringExtra( ChooseTypeAndAccountActivity.EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING); AccountManager.get(this).addAccount(type, authTokenType, requiredFeatures, - options, this, this, null /* Handler */); + options, null /* activity */, this /* callback */, null /* Handler */); } public void run(final AccountManagerFuture<Bundle> accountManagerFuture) { try { final Bundle accountManagerResult = accountManagerFuture.getResult(); - final String name = accountManagerResult.getString(AccountManager.KEY_ACCOUNT_NAME); - final String type = accountManagerResult.getString(AccountManager.KEY_ACCOUNT_TYPE); - if (name != null && type != null) { - final Bundle bundle = new Bundle(); - bundle.putString(AccountManager.KEY_ACCOUNT_NAME, name); - bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, type); - setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); - finish(); + final Intent intent = (Intent)accountManagerResult.getParcelable( + AccountManager.KEY_INTENT); + if (intent != null) { + mPendingRequest = REQUEST_ADD_ACCOUNT; + mExistingAccounts = AccountManager.get(this).getAccounts(); + intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); + startActivityForResult(intent, REQUEST_ADD_ACCOUNT); return; } } catch (OperationCanceledException e) { @@ -297,12 +394,17 @@ public class ChooseTypeAndAccountActivity extends Activity bundle.putString(AccountManager.KEY_ACCOUNT_NAME, accountName); bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType); setResult(Activity.RESULT_OK, new Intent().putExtras(bundle)); - Log.d(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: " - + "selected account " + accountName + ", " + accountType); + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseTypeAndAccountActivity.setResultAndFinish: " + + "selected account " + accountName + ", " + accountType); + } finish(); } private void startChooseAccountTypeActivity() { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "ChooseAccountTypeActivity.startChooseAccountTypeActivity()"); + } final Intent intent = new Intent(this, ChooseAccountTypeActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); intent.putExtra(EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, @@ -313,7 +415,8 @@ public class ChooseTypeAndAccountActivity extends Activity getIntent().getStringArrayExtra(EXTRA_ADD_ACCOUNT_REQUIRED_FEATURES_STRING_ARRAY)); intent.putExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING, getIntent().getStringExtra(EXTRA_ADD_ACCOUNT_AUTH_TOKEN_TYPE_STRING)); - startActivityForResult(intent, 0); + startActivityForResult(intent, REQUEST_CHOOSE_TYPE); + mPendingRequest = REQUEST_CHOOSE_TYPE; } private static class AccountInfo { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d2facdc..8afe9bf 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -490,6 +490,15 @@ public final class ActivityThread { // Formatting for checkin service - update version if row format changes private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1; + private void updatePendingConfiguration(Configuration config) { + synchronized (mPackages) { + if (mPendingConfiguration == null || + mPendingConfiguration.isOtherSeqNewer(config)) { + mPendingConfiguration = config; + } + } + } + public final void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges) { queueOrSendMessage( @@ -530,8 +539,8 @@ public final class ActivityThread { // we use token to identify this activity without having to send the // activity itself back to the activity manager. (matters more with ipc) public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, CompatibilityInfo compatInfo, Bundle state, - List<ResultInfo> pendingResults, + ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) { ActivityClientRecord r = new ActivityClientRecord(); @@ -553,6 +562,8 @@ public final class ActivityThread { r.profileFd = profileFd; r.autoStopProfiler = autoStopProfiler; + updatePendingConfiguration(curConfig); + queueOrSendMessage(H.LAUNCH_ACTIVITY, r); } @@ -697,12 +708,7 @@ public final class ActivityThread { } public void scheduleConfigurationChanged(Configuration config) { - synchronized (mPackages) { - if (mPendingConfiguration == null || - mPendingConfiguration.isOtherSeqNewer(config)) { - mPendingConfiguration = config; - } - } + updatePendingConfiguration(config); queueOrSendMessage(H.CONFIGURATION_CHANGED, config); } @@ -1966,6 +1972,9 @@ public final class ActivityThread { mProfiler.autoStopProfiler = r.autoStopProfiler; } + // Make sure we are running with the most recent config. + handleConfigurationChanged(null, null); + if (localLOGV) Slog.v( TAG, "Handling launch of " + r); Activity a = performLaunchActivity(r, customIntent); diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index cde06cd..c4a4fea 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -132,6 +132,7 @@ public abstract class ApplicationThreadNative extends Binder IBinder b = data.readStrongBinder(); int ident = data.readInt(); ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); + Configuration curConfig = Configuration.CREATOR.createFromParcel(data); CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data); Bundle state = data.readBundle(); List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR); @@ -142,7 +143,7 @@ public abstract class ApplicationThreadNative extends Binder ParcelFileDescriptor profileFd = data.readInt() != 0 ? data.readFileDescriptor() : null; boolean autoStopProfiler = data.readInt() != 0; - scheduleLaunchActivity(intent, b, ident, info, compatInfo, state, ri, pi, + scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, state, ri, pi, notResumed, isForward, profileName, profileFd, autoStopProfiler); return true; } @@ -630,10 +631,10 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, CompatibilityInfo compatInfo, Bundle state, - List<ResultInfo> pendingResults, - List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, - String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) + ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + Bundle state, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); @@ -641,6 +642,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeStrongBinder(token); data.writeInt(ident); info.writeToParcel(data, 0); + curConfig.writeToParcel(data, 0); compatInfo.writeToParcel(data, 0); data.writeBundle(state); data.writeTypedList(pendingResults); diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 5d200b4..1253fe7 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -53,10 +53,10 @@ public interface IApplicationThread extends IInterface { void scheduleResumeActivity(IBinder token, boolean isForward) throws RemoteException; void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException; void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, CompatibilityInfo compatInfo, Bundle state, - List<ResultInfo> pendingResults, - List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, - String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) + ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo, + Bundle state, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward, + String profileName, ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException; void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index 8aee65c..615e8ce 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -29,6 +29,7 @@ import dalvik.system.BlockGuard; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -107,7 +108,8 @@ final class SharedPreferencesImpl implements SharedPreferences { FileStatus stat = new FileStatus(); if (FileUtils.getFileStatus(mFile.getPath(), stat) && mFile.canRead()) { try { - FileInputStream str = new FileInputStream(mFile); + BufferedInputStream str = new BufferedInputStream( + new FileInputStream(mFile), 16*1024); map = XmlUtils.readMapXml(str); str.close(); } catch (XmlPullParserException e) { diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index ca64c88..c5ee48d 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -40,15 +40,20 @@ public class StatusBarManager { public static final int DISABLE_NOTIFICATION_TICKER = View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER; public static final int DISABLE_SYSTEM_INFO = View.STATUS_BAR_DISABLE_SYSTEM_INFO; - public static final int DISABLE_NAVIGATION = View.STATUS_BAR_DISABLE_NAVIGATION; + public static final int DISABLE_HOME = View.STATUS_BAR_DISABLE_HOME; + public static final int DISABLE_RECENT = View.STATUS_BAR_DISABLE_RECENT; public static final int DISABLE_BACK = View.STATUS_BAR_DISABLE_BACK; public static final int DISABLE_CLOCK = View.STATUS_BAR_DISABLE_CLOCK; + @Deprecated + public static final int DISABLE_NAVIGATION = + View.STATUS_BAR_DISABLE_HOME | View.STATUS_BAR_DISABLE_RECENT; + public static final int DISABLE_NONE = 0x00000000; public static final int DISABLE_MASK = DISABLE_EXPAND | DISABLE_NOTIFICATION_ICONS | DISABLE_NOTIFICATION_ALERTS | DISABLE_NOTIFICATION_TICKER - | DISABLE_SYSTEM_INFO| DISABLE_NAVIGATION | DISABLE_BACK | DISABLE_CLOCK; + | DISABLE_SYSTEM_INFO | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK | DISABLE_CLOCK; private Context mContext; private IStatusBarService mService; diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 8057d4b..e452f1f 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -22,10 +22,6 @@ import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; import android.database.Cursor; -import android.database.CursorToBulkCursorAdaptor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; import android.database.SQLException; import android.net.Uri; import android.os.AsyncTask; @@ -168,22 +164,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return ContentProvider.this; } - /** - * Remote version of a query, which returns an IBulkCursor. The bulk - * cursor should be wrapped with BulkCursorToCursorAdaptor before use. - */ - public IBulkCursor bulkQuery(Uri uri, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - IContentObserver observer, CursorWindow window) { - enforceReadPermission(uri); - Cursor cursor = ContentProvider.this.query(uri, projection, - selection, selectionArgs, sortOrder); - if (cursor == null) { - return null; - } - return new CursorToBulkCursorAdaptor(cursor, observer, - ContentProvider.this.getClass().getName(), - hasWritePermission(uri), window); + @Override + public String getProviderName() { + return getContentProvider().getClass().getName(); } public Cursor query(Uri uri, String[] projection, diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 9a20951..b089bf2 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -20,6 +20,7 @@ import android.content.res.AssetFileDescriptor; import android.database.BulkCursorNative; import android.database.BulkCursorToCursorAdaptor; import android.database.Cursor; +import android.database.CursorToBulkCursorAdaptor; import android.database.CursorWindow; import android.database.DatabaseUtils; import android.database.IBulkCursor; @@ -65,6 +66,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return new ContentProviderProxy(obj); } + /** + * Gets the name of the content provider. + * Should probably be part of the {@link IContentProvider} interface. + * @return The content provider name. + */ + public abstract String getProviderName(); + @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { @@ -98,33 +106,24 @@ abstract public class ContentProviderNative extends Binder implements IContentPr } String sortOrder = data.readString(); - IContentObserver observer = IContentObserver.Stub. - asInterface(data.readStrongBinder()); - CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); - - // Flag for whether caller wants the number of - // rows in the cursor and the position of the - // "_id" column index (or -1 if non-existent) - // Only to be returned if binder != null. - boolean wantsCursorMetadata = data.readInt() != 0; - - IBulkCursor bulkCursor = bulkQuery(url, projection, selection, - selectionArgs, sortOrder, observer, window); - if (bulkCursor != null) { - final IBinder binder = bulkCursor.asBinder(); - if (wantsCursorMetadata) { - final int count = bulkCursor.count(); - final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( - bulkCursor.getColumnNames()); - - reply.writeNoException(); - reply.writeStrongBinder(binder); - reply.writeInt(count); - reply.writeInt(index); - } else { - reply.writeNoException(); - reply.writeStrongBinder(binder); - } + IContentObserver observer = IContentObserver.Stub.asInterface( + data.readStrongBinder()); + + Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder); + if (cursor != null) { + CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor( + cursor, observer, getProviderName()); + final IBinder binder = adaptor.asBinder(); + final int count = adaptor.count(); + final int index = BulkCursorToCursorAdaptor.findRowIdColumnIndex( + adaptor.getColumnNames()); + final boolean wantsAllOnMoveCalls = adaptor.getWantsAllOnMoveCalls(); + + reply.writeNoException(); + reply.writeStrongBinder(binder); + reply.writeInt(count); + reply.writeInt(index); + reply.writeInt(wantsAllOnMoveCalls ? 1 : 0); } else { reply.writeNoException(); reply.writeStrongBinder(null); @@ -324,12 +323,9 @@ final class ContentProviderProxy implements IContentProvider return mRemote; } - // Like bulkQuery() but sets up provided 'adaptor' if not null. - private IBulkCursor bulkQueryInternal( - Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - IContentObserver observer, CursorWindow window, - BulkCursorToCursorAdaptor adaptor) throws RemoteException { + public Cursor query(Uri url, String[] projection, String selection, + String[] selectionArgs, String sortOrder) throws RemoteException { + BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { @@ -355,63 +351,35 @@ final class ContentProviderProxy implements IContentProvider data.writeString(selectionArgs[i]); } data.writeString(sortOrder); - data.writeStrongBinder(observer.asBinder()); - window.writeToParcel(data, 0); - - // Flag for whether or not we want the number of rows in the - // cursor and the position of the "_id" column index (or -1 if - // non-existent). Only to be returned if binder != null. - final boolean wantsCursorMetadata = (adaptor != null); - data.writeInt(wantsCursorMetadata ? 1 : 0); + data.writeStrongBinder(adaptor.getObserver().asBinder()); mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); - IBulkCursor bulkCursor = null; - IBinder bulkCursorBinder = reply.readStrongBinder(); - if (bulkCursorBinder != null) { - bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder); - - if (wantsCursorMetadata) { - int rowCount = reply.readInt(); - int idColumnPosition = reply.readInt(); - if (bulkCursor != null) { - adaptor.set(bulkCursor, rowCount, idColumnPosition); - } - } + IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder()); + if (bulkCursor != null) { + int rowCount = reply.readInt(); + int idColumnPosition = reply.readInt(); + boolean wantsAllOnMoveCalls = reply.readInt() != 0; + adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls); + } else { + adaptor.close(); + adaptor = null; } - return bulkCursor; + return adaptor; + } catch (RemoteException ex) { + adaptor.close(); + throw ex; + } catch (RuntimeException ex) { + adaptor.close(); + throw ex; } finally { data.recycle(); reply.recycle(); } } - public IBulkCursor bulkQuery(Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, - CursorWindow window) throws RemoteException { - return bulkQueryInternal( - url, projection, selection, selectionArgs, sortOrder, - observer, window, - null /* BulkCursorToCursorAdaptor */); - } - - public Cursor query(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder) throws RemoteException { - //TODO make a pool of windows so we can reuse memory dealers - CursorWindow window = new CursorWindow(false /* window will be used remotely */); - BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); - IBulkCursor bulkCursor = bulkQueryInternal( - url, projection, selection, selectionArgs, sortOrder, - adaptor.getObserver(), window, - adaptor); - if (bulkCursor == null) { - return null; - } - return adaptor; - } - public String getType(Uri url) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 72bc9c2..2a67ff8 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -18,9 +18,6 @@ package android.content; import android.content.res.AssetFileDescriptor; import android.database.Cursor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -36,13 +33,6 @@ import java.util.ArrayList; * @hide */ public interface IContentProvider extends IInterface { - /** - * @hide - hide this because return type IBulkCursor and parameter - * IContentObserver are system private classes. - */ - public IBulkCursor bulkQuery(Uri url, String[] projection, - String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, - CursorWindow window) throws RemoteException; public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder) throws RemoteException; public String getType(Uri url) throws RemoteException; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2be5153..45a42e4 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -57,8 +57,8 @@ import java.util.Set; * * <p>An Intent provides a facility for performing late runtime binding between the code in * different applications. Its most significant use is in the launching of activities, where it - * can be thought of as the glue between activities. It is basically a passive data structure - * holding an abstract description of an action to be performed.</p> + * can be thought of as the glue between activities. It is basically a passive data structure + * holding an abstract description of an action to be performed.</p> * * <div class="special reference"> * <h3>Developer Guides</h3> @@ -2566,7 +2566,7 @@ public class Intent implements Parcelable, Cloneable { */ public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; - + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). @@ -5291,7 +5291,7 @@ public class Intent implements Parcelable, Cloneable { if (r != null) { mSourceBounds = new Rect(r); } else { - r = null; + mSourceBounds = null; } } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index a3bcc28..decb974 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -362,4 +362,6 @@ interface IPackageManager { void verifyPendingInstall(int id, int verificationCode); VerifierDeviceIdentity getVerifierDeviceIdentity(); + + boolean isFirstBoot(); } diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index 3f23b89..ee6aec6 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -78,13 +78,11 @@ public abstract class AbstractCursor implements CrossProcessCursor { } public void deactivate() { - deactivateInternal(); + onDeactivateOrClose(); } - /** - * @hide - */ - public void deactivateInternal() { + /** @hide */ + protected void onDeactivateOrClose() { if (mSelfObserver != null) { mContentResolver.unregisterContentObserver(mSelfObserver); mSelfObserverRegistered = false; @@ -108,7 +106,7 @@ public abstract class AbstractCursor implements CrossProcessCursor { public void close() { mClosed = true; mContentObservable.unregisterAll(); - deactivateInternal(); + onDeactivateOrClose(); } /** diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java index bfc8123..d0aedd2 100644 --- a/core/java/android/database/AbstractWindowedCursor.java +++ b/core/java/android/database/AbstractWindowedCursor.java @@ -19,6 +19,11 @@ package android.database; /** * A base class for Cursors that store their data in {@link CursorWindow}s. * <p> + * The cursor owns the cursor window it uses. When the cursor is closed, + * its window is also closed. Likewise, when the window used by the cursor is + * changed, its old window is closed. This policy of strict ownership ensures + * that cursor windows are not leaked. + * </p><p> * Subclasses are responsible for filling the cursor window with data during * {@link #onMove(int, int)}, allocating a new cursor window if necessary. * During {@link #requery()}, the existing cursor window should be cleared and @@ -180,4 +185,27 @@ public abstract class AbstractWindowedCursor extends AbstractCursor { mWindow = null; } } + + /** + * If there is a window, clear it. + * Otherwise, creates a local window. + * + * @param name The window name. + * @hide + */ + protected void clearOrCreateLocalWindow(String name) { + if (mWindow == null) { + // If there isn't a window set already it will only be accessed locally + mWindow = new CursorWindow(name, true /* the window is local only */); + } else { + mWindow.clear(); + } + } + + /** @hide */ + @Override + protected void onDeactivateOrClose() { + super.onDeactivateOrClose(); + closeWindow(); + } } diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java index 9925a9a..20a9c67 100644 --- a/core/java/android/database/BulkCursorNative.java +++ b/core/java/android/database/BulkCursorNative.java @@ -62,13 +62,13 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor data.enforceInterface(IBulkCursor.descriptor); int startPos = data.readInt(); CursorWindow window = getWindow(startPos); + reply.writeNoException(); if (window == null) { reply.writeInt(0); - return true; + } else { + reply.writeInt(1); + window.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } - reply.writeNoException(); - reply.writeInt(1); - window.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; } @@ -109,9 +109,8 @@ public abstract class BulkCursorNative extends Binder implements IBulkCursor case REQUERY_TRANSACTION: { data.enforceInterface(IBulkCursor.descriptor); IContentObserver observer = - IContentObserver.Stub.asInterface(data.readStrongBinder()); - CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); - int count = requery(observer, window); + IContentObserver.Stub.asInterface(data.readStrongBinder()); + int count = requery(observer); reply.writeNoException(); reply.writeInt(count); reply.writeBundle(getExtras()); @@ -294,13 +293,12 @@ final class BulkCursorProxy implements IBulkCursor { } } - public int requery(IContentObserver observer, CursorWindow window) throws RemoteException { + public int requery(IContentObserver observer) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); try { data.writeInterfaceToken(IBulkCursor.descriptor); data.writeStrongInterface(observer); - window.writeToParcel(data, 0); boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0); DatabaseUtils.readExceptionFromParcel(reply); diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java index 9c1b26d..885046b 100644 --- a/core/java/android/database/BulkCursorToCursorAdaptor.java +++ b/core/java/android/database/BulkCursorToCursorAdaptor.java @@ -21,44 +21,30 @@ import android.os.RemoteException; import android.util.Log; /** - * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local - * process. + * Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local process. * * {@hide} */ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { private static final String TAG = "BulkCursor"; - private SelfContentObserver mObserverBridge; + private SelfContentObserver mObserverBridge = new SelfContentObserver(this); private IBulkCursor mBulkCursor; private int mCount; private String[] mColumns; private boolean mWantsAllOnMoveCalls; - public void set(IBulkCursor bulkCursor) { - mBulkCursor = bulkCursor; - - try { - mCount = mBulkCursor.count(); - mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls(); - - // Search for the rowID column index and set it for our parent - mColumns = mBulkCursor.getColumnNames(); - mRowIdColumnIndex = findRowIdColumnIndex(mColumns); - } catch (RemoteException ex) { - Log.e(TAG, "Setup failed because the remote process is dead"); - } - } - /** - * Version of set() that does fewer Binder calls if the caller - * already knows BulkCursorToCursorAdaptor's properties. + * Initializes the adaptor. + * Must be called before first use. */ - public void set(IBulkCursor bulkCursor, int count, int idIndex) { + public void initialize(IBulkCursor bulkCursor, int count, int idIndex, + boolean wantsAllOnMoveCalls) { mBulkCursor = bulkCursor; mColumns = null; // lazily retrieved mCount = count; mRowIdColumnIndex = idIndex; + mWantsAllOnMoveCalls = wantsAllOnMoveCalls; } /** @@ -80,31 +66,34 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { * * @return A SelfContentObserver hooked up to this Cursor */ - public synchronized IContentObserver getObserver() { - if (mObserverBridge == null) { - mObserverBridge = new SelfContentObserver(this); - } + public IContentObserver getObserver() { return mObserverBridge.getContentObserver(); } + private void throwIfCursorIsClosed() { + if (mBulkCursor == null) { + throw new StaleDataException("Attempted to access a cursor after it has been closed."); + } + } + @Override public int getCount() { + throwIfCursorIsClosed(); return mCount; } @Override public boolean onMove(int oldPosition, int newPosition) { + throwIfCursorIsClosed(); + try { // Make sure we have the proper window - if (mWindow != null) { - if (newPosition < mWindow.getStartPosition() || - newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - mWindow = mBulkCursor.getWindow(newPosition); - } else if (mWantsAllOnMoveCalls) { - mBulkCursor.onMove(newPosition); - } - } else { - mWindow = mBulkCursor.getWindow(newPosition); + if (mWindow == null + || newPosition < mWindow.getStartPosition() + || newPosition >= mWindow.getStartPosition() + mWindow.getNumRows()) { + setWindow(mBulkCursor.getWindow(newPosition)); + } else if (mWantsAllOnMoveCalls) { + mBulkCursor.onMove(newPosition); } } catch (RemoteException ex) { // We tried to get a window and failed @@ -126,32 +115,36 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { // which is what actually makes the data set invalid. super.deactivate(); - try { - mBulkCursor.deactivate(); - } catch (RemoteException ex) { - Log.w(TAG, "Remote process exception when deactivating"); + if (mBulkCursor != null) { + try { + mBulkCursor.deactivate(); + } catch (RemoteException ex) { + Log.w(TAG, "Remote process exception when deactivating"); + } } - mWindow = null; } @Override public void close() { super.close(); - try { - mBulkCursor.close(); - } catch (RemoteException ex) { - Log.w(TAG, "Remote process exception when closing"); + + if (mBulkCursor != null) { + try { + mBulkCursor.close(); + } catch (RemoteException ex) { + Log.w(TAG, "Remote process exception when closing"); + } finally { + mBulkCursor = null; + } } - mWindow = null; } @Override public boolean requery() { + throwIfCursorIsClosed(); + try { - int oldCount = mCount; - //TODO get the window from a pool somewhere to avoid creating the memory dealer - mCount = mBulkCursor.requery(getObserver(), new CursorWindow( - false /* the window will be accessed across processes */)); + mCount = mBulkCursor.requery(getObserver()); if (mCount != -1) { mPos = -1; closeWindow(); @@ -174,6 +167,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public String[] getColumnNames() { + throwIfCursorIsClosed(); + if (mColumns == null) { try { mColumns = mBulkCursor.getColumnNames(); @@ -187,6 +182,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public Bundle getExtras() { + throwIfCursorIsClosed(); + try { return mBulkCursor.getExtras(); } catch (RemoteException e) { @@ -198,6 +195,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor { @Override public Bundle respond(Bundle extras) { + throwIfCursorIsClosed(); + try { return mBulkCursor.respond(extras); } catch (RemoteException e) { diff --git a/core/java/android/database/CrossProcessCursor.java b/core/java/android/database/CrossProcessCursor.java index 77ba3a5..8e6a5aa 100644 --- a/core/java/android/database/CrossProcessCursor.java +++ b/core/java/android/database/CrossProcessCursor.java @@ -16,7 +16,7 @@ package android.database; -public interface CrossProcessCursor extends Cursor{ +public interface CrossProcessCursor extends Cursor { /** * returns a pre-filled window, return NULL if no such window */ diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java index 8fa4d3b..dd2c9b7 100644 --- a/core/java/android/database/CursorToBulkCursorAdaptor.java +++ b/core/java/android/database/CursorToBulkCursorAdaptor.java @@ -24,19 +24,38 @@ import android.util.Log; /** * Wraps a BulkCursor around an existing Cursor making it remotable. + * <p> + * If the wrapped cursor is a {@link AbstractWindowedCursor} then it owns + * the cursor window. Otherwise, the adaptor takes ownership of the + * cursor itself and ensures it gets closed as needed during deactivation + * and requeries. + * </p> * * {@hide} */ public final class CursorToBulkCursorAdaptor extends BulkCursorNative implements IBinder.DeathRecipient { private static final String TAG = "Cursor"; - private final CrossProcessCursor mCursor; - private CursorWindow mWindow; + + private final Object mLock = new Object(); private final String mProviderName; private ContentObserverProxy mObserver; - private static final class ContentObserverProxy extends ContentObserver - { + /** + * The cursor that is being adapted. + * This field is set to null when the cursor is closed. + */ + private CrossProcessCursor mCursor; + + /** + * The cursor window used by the cross process cursor. + * This field is always null for abstract windowed cursors since they are responsible + * for managing the lifetime of their window. + */ + private CursorWindow mWindowForNonWindowedCursor; + private boolean mWindowForNonWindowedCursorWasFilled; + + private static final class ContentObserverProxy extends ContentObserver { protected IContentObserver mRemote; public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) { @@ -69,102 +88,171 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } - public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName, - boolean allowWrite, CursorWindow window) { + public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, + String providerName) { try { mCursor = (CrossProcessCursor) cursor; - if (mCursor instanceof AbstractWindowedCursor) { - AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor; - if (windowedCursor.hasWindow()) { - if (Log.isLoggable(TAG, Log.VERBOSE) || false) { - Log.v(TAG, "Cross process cursor has a local window before setWindow in " - + providerName, new RuntimeException()); - } - } - windowedCursor.setWindow(window); - } else { - mWindow = window; - mCursor.fillWindow(0, window); - } } catch (ClassCastException e) { - // TODO Implement this case. throw new UnsupportedOperationException( "Only CrossProcessCursor cursors are supported across process for now", e); } mProviderName = providerName; - createAndRegisterObserverProxy(observer); + synchronized (mLock) { + createAndRegisterObserverProxyLocked(observer); + } + } + + private void closeWindowForNonWindowedCursorLocked() { + if (mWindowForNonWindowedCursor != null) { + mWindowForNonWindowedCursor.close(); + mWindowForNonWindowedCursor = null; + mWindowForNonWindowedCursorWasFilled = false; + } } - + + private void disposeLocked() { + if (mCursor != null) { + unregisterObserverProxyLocked(); + mCursor.close(); + mCursor = null; + } + + closeWindowForNonWindowedCursorLocked(); + } + + private void throwIfCursorIsClosed() { + if (mCursor == null) { + throw new StaleDataException("Attempted to access a cursor after it has been closed."); + } + } + + @Override public void binderDied() { - mCursor.close(); - if (mWindow != null) { - mWindow.close(); + synchronized (mLock) { + disposeLocked(); } } - + + @Override public CursorWindow getWindow(int startPos) { - mCursor.moveToPosition(startPos); - - if (mWindow != null) { - if (startPos < mWindow.getStartPosition() || - startPos >= (mWindow.getStartPosition() + mWindow.getNumRows())) { - mCursor.fillWindow(startPos, mWindow); - } - return mWindow; - } else { - return ((AbstractWindowedCursor)mCursor).getWindow(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + CursorWindow window; + if (mCursor instanceof AbstractWindowedCursor) { + AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor)mCursor; + window = windowedCursor.getWindow(); + if (window == null) { + window = new CursorWindow(mProviderName, false /*localOnly*/); + windowedCursor.setWindow(window); + } + + mCursor.moveToPosition(startPos); + } else { + window = mWindowForNonWindowedCursor; + if (window == null) { + window = new CursorWindow(mProviderName, false /*localOnly*/); + mWindowForNonWindowedCursor = window; + } + + mCursor.moveToPosition(startPos); + + if (!mWindowForNonWindowedCursorWasFilled + || startPos < window.getStartPosition() + || startPos >= window.getStartPosition() + window.getNumRows()) { + mCursor.fillWindow(startPos, window); + mWindowForNonWindowedCursorWasFilled = true; + } + } + + // Acquire a reference before returning from this RPC. + // The Binder proxy will decrement the reference count again as part of writing + // the CursorWindow to the reply parcel as a return value. + if (window != null) { + window.acquireReference(); + } + return window; } } + @Override public void onMove(int position) { - mCursor.onMove(mCursor.getPosition(), position); + synchronized (mLock) { + throwIfCursorIsClosed(); + + mCursor.onMove(mCursor.getPosition(), position); + } } + @Override public int count() { - return mCursor.getCount(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getCount(); + } } + @Override public String[] getColumnNames() { - return mCursor.getColumnNames(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getColumnNames(); + } } + @Override public void deactivate() { - maybeUnregisterObserverProxy(); - mCursor.deactivate(); + synchronized (mLock) { + if (mCursor != null) { + unregisterObserverProxyLocked(); + mCursor.deactivate(); + } + + closeWindowForNonWindowedCursorLocked(); + } } + @Override public void close() { - maybeUnregisterObserverProxy(); - mCursor.close(); + synchronized (mLock) { + disposeLocked(); + } } - public int requery(IContentObserver observer, CursorWindow window) { - if (mWindow == null) { - ((AbstractWindowedCursor)mCursor).setWindow(window); - } - try { - if (!mCursor.requery()) { - return -1; + @Override + public int requery(IContentObserver observer) { + synchronized (mLock) { + throwIfCursorIsClosed(); + + closeWindowForNonWindowedCursorLocked(); + + try { + if (!mCursor.requery()) { + return -1; + } + } catch (IllegalStateException e) { + IllegalStateException leakProgram = new IllegalStateException( + mProviderName + " Requery misuse db, mCursor isClosed:" + + mCursor.isClosed(), e); + throw leakProgram; } - } catch (IllegalStateException e) { - IllegalStateException leakProgram = new IllegalStateException( - mProviderName + " Requery misuse db, mCursor isClosed:" + - mCursor.isClosed(), e); - throw leakProgram; - } - - if (mWindow != null) { - mCursor.fillWindow(0, window); - mWindow = window; + + unregisterObserverProxyLocked(); + createAndRegisterObserverProxyLocked(observer); + return mCursor.getCount(); } - maybeUnregisterObserverProxy(); - createAndRegisterObserverProxy(observer); - return mCursor.getCount(); } + @Override public boolean getWantsAllOnMoveCalls() { - return mCursor.getWantsAllOnMoveCalls(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getWantsAllOnMoveCalls(); + } } /** @@ -173,7 +261,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative * @param observer the IContentObserver that wants to monitor the cursor * @throws IllegalStateException if an observer is already registered */ - private void createAndRegisterObserverProxy(IContentObserver observer) { + private void createAndRegisterObserverProxyLocked(IContentObserver observer) { if (mObserver != null) { throw new IllegalStateException("an observer is already registered"); } @@ -182,7 +270,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } /** Unregister the observer if it is already registered. */ - private void maybeUnregisterObserverProxy() { + private void unregisterObserverProxyLocked() { if (mObserver != null) { mCursor.unregisterContentObserver(mObserver); mObserver.unlinkToDeath(this); @@ -190,11 +278,21 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } + @Override public Bundle getExtras() { - return mCursor.getExtras(); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.getExtras(); + } } + @Override public Bundle respond(Bundle extras) { - return mCursor.respond(extras); + synchronized (mLock) { + throwIfCursorIsClosed(); + + return mCursor.respond(extras); + } } } diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 2e3ef28..a18a721 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -16,11 +16,12 @@ package android.database; +import dalvik.system.CloseGuard; + import android.content.res.Resources; import android.database.sqlite.SQLiteClosable; import android.database.sqlite.SQLiteException; import android.os.Binder; -import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.Process; @@ -29,6 +30,13 @@ import android.util.SparseIntArray; /** * A buffer containing multiple cursor rows. + * <p> + * A {@link CursorWindow} is read-write when created and used locally. When sent + * to a remote process (by writing it to a {@link Parcel}), the remote process + * receives a read-only view of the cursor window. Typically the cursor window + * will be allocated by the producer, filled with data, and then sent to the + * consumer for reading. + * </p> */ public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; @@ -48,10 +56,13 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private int mStartPos; - private static native int nativeInitializeEmpty(int cursorWindowSize, boolean localOnly); - private static native int nativeInitializeFromBinder(IBinder nativeBinder); + private final CloseGuard mCloseGuard = CloseGuard.get(); + + private static native int nativeCreate(String name, + int cursorWindowSize, boolean localOnly); + private static native int nativeCreateFromParcel(Parcel parcel); private static native void nativeDispose(int windowPtr); - private static native IBinder nativeGetBinder(int windowPtr); + private static native void nativeWriteToParcel(int windowPtr, Parcel parcel); private static native void nativeClear(int windowPtr); @@ -75,38 +86,59 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private static native boolean nativePutNull(int windowPtr, int row, int column); /** - * Creates a new empty cursor window. + * Creates a new empty cursor window and gives it a name. * <p> * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to * set the number of columns before adding any rows to the cursor. * </p> * + * @param name The name of the cursor window, or null if none. * @param localWindow True if this window will be used in this process only, * false if it might be sent to another processes. + * + * @hide */ - public CursorWindow(boolean localWindow) { + public CursorWindow(String name, boolean localWindow) { mStartPos = 0; - mWindowPtr = nativeInitializeEmpty(sCursorWindowSize, localWindow); + mWindowPtr = nativeCreate(name, sCursorWindowSize, localWindow); if (mWindowPtr == 0) { throw new CursorWindowAllocationException("Cursor window allocation of " + (sCursorWindowSize / 1024) + " kb failed. " + printStats()); } + mCloseGuard.open("close"); recordNewWindow(Binder.getCallingPid(), mWindowPtr); } + /** + * Creates a new empty cursor window. + * <p> + * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to + * set the number of columns before adding any rows to the cursor. + * </p> + * + * @param localWindow True if this window will be used in this process only, + * false if it might be sent to another processes. + */ + public CursorWindow(boolean localWindow) { + this(null, localWindow); + } + private CursorWindow(Parcel source) { - IBinder binder = source.readStrongBinder(); mStartPos = source.readInt(); - mWindowPtr = nativeInitializeFromBinder(binder); + mWindowPtr = nativeCreateFromParcel(source); if (mWindowPtr == 0) { throw new CursorWindowAllocationException("Cursor window could not be " + "created from binder."); } + mCloseGuard.open("close"); } @Override protected void finalize() throws Throwable { try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } dispose(); } finally { super.finalize(); @@ -114,6 +146,9 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { } private void dispose() { + if (mCloseGuard != null) { + mCloseGuard.close(); + } if (mWindowPtr != 0) { recordClosingOfWindow(mWindowPtr); nativeDispose(mWindowPtr); @@ -675,8 +710,12 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { - dest.writeStrongBinder(nativeGetBinder(mWindowPtr)); dest.writeInt(mStartPos); + nativeWriteToParcel(mWindowPtr, dest); + + if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) { + releaseReference(); + } } @Override diff --git a/core/java/android/database/IBulkCursor.java b/core/java/android/database/IBulkCursor.java index 244c88f..7c96797 100644 --- a/core/java/android/database/IBulkCursor.java +++ b/core/java/android/database/IBulkCursor.java @@ -56,7 +56,7 @@ public interface IBulkCursor extends IInterface { public void close() throws RemoteException; - public int requery(IContentObserver observer, CursorWindow window) throws RemoteException; + public int requery(IContentObserver observer) throws RemoteException; boolean getWantsAllOnMoveCalls() throws RemoteException; diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 81fe824..a1c36e2 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -89,8 +89,6 @@ public class SQLiteCursor extends AbstractWindowedCursor { * @param query the {@link SQLiteQuery} object associated with this cursor object. */ public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) { - // The AbstractCursor constructor needs to do some setup. - super(); if (query == null) { throw new IllegalArgumentException("query object cannot be null"); } @@ -157,12 +155,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { } private void fillWindow(int startPos) { - if (mWindow == null) { - // If there isn't a window set already it will only be accessed locally - mWindow = new CursorWindow(true /* the window is local only */); - } else { - mWindow.clear(); - } + clearOrCreateLocalWindow(getDatabase().getPath()); mWindow.setStartPosition(startPos); int count = getQuery().fillWindow(mWindow); if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0 @@ -214,16 +207,9 @@ public class SQLiteCursor extends AbstractWindowedCursor { return mColumns; } - private void deactivateCommon() { - if (false) Log.v(TAG, "<<< Releasing cursor " + this); - closeWindow(); - if (false) Log.v("DatabaseWindow", "closing window in release()"); - } - @Override public void deactivate() { super.deactivate(); - deactivateCommon(); mDriver.cursorDeactivated(); } @@ -231,7 +217,6 @@ public class SQLiteCursor extends AbstractWindowedCursor { public void close() { super.close(); synchronized (this) { - deactivateCommon(); mQuery.close(); mDriver.cursorClosed(); } diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index d65e6df..d338764 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -337,7 +337,7 @@ public class Camera { * Camera objects are locked by default unless {@link #unlock()} is * called. Normally {@link #reconnect()} is used instead. * - * <p>Since API level 13, camera is automatically locked for applications in + * <p>Since API level 14, camera is automatically locked for applications in * {@link android.media.MediaRecorder#start()}. Applications can use the * camera (ex: zoom) after recording starts. There is no need to call this * after recording starts or stops. @@ -356,7 +356,7 @@ public class Camera { * which will re-acquire the lock and allow you to continue using the * camera. * - * <p>Since API level 13, camera is automatically locked for applications in + * <p>Since API level 14, camera is automatically locked for applications in * {@link android.media.MediaRecorder#start()}. Applications can use the * camera (ex: zoom) after recording starts. There is no need to call this * after recording starts or stops. @@ -781,7 +781,7 @@ public class Camera { * @see android.hardware.Camera.Parameters#setAutoWhiteBalanceLock(boolean) */ void onAutoFocus(boolean success, Camera camera); - }; + } /** * Starts camera auto-focus and registers a callback function to run when @@ -804,11 +804,17 @@ public class Camera { * {@link android.hardware.Camera.Parameters#FLASH_MODE_OFF}, flash may be * fired during auto-focus, depending on the driver and camera hardware.<p> * - * Auto-exposure lock {@link android.hardware.Camera.Parameters#getAutoExposureLock()} + * <p>Auto-exposure lock {@link android.hardware.Camera.Parameters#getAutoExposureLock()} * and auto-white balance locks {@link android.hardware.Camera.Parameters#getAutoWhiteBalanceLock()} * do not change during and after autofocus. But auto-focus routine may stop * auto-exposure and auto-white balance transiently during focusing. * + * <p>Stopping preview with {@link #stopPreview()}, or triggering still + * image capture with {@link #takePicture(Camera.ShutterCallback, + * Camera.PictureCallback, Camera.PictureCallback)}, will not change the + * the focus position. Applications must call cancelAutoFocus to reset the + * focus.</p> + * * @param cb the callback to run * @see #cancelAutoFocus() * @see android.hardware.Camera.Parameters#setAutoExposureLock(boolean) @@ -1059,8 +1065,7 @@ public class Camera { /** * Notify the listener of the detected faces in the preview frame. * - * @param faces The detected faces in a list sorted by the confidence score. - * The highest scored face is the first element. + * @param faces The detected faces in a list * @param camera The {@link Camera} service object */ void onFaceDetection(Face[] faces, Camera camera); @@ -1121,7 +1126,7 @@ public class Camera { /** * Information about a face identified through camera face detection. - * + * * <p>When face detection is used with a camera, the {@link FaceDetectionListener} returns a * list of face objects for use in focusing and metering.</p> * @@ -1140,7 +1145,9 @@ public class Camera { * the field of view. For example, suppose the size of the viewfinder UI * is 800x480. The rect passed from the driver is (-1000, -1000, 0, 0). * The corresponding viewfinder rect should be (0, 0, 400, 240). The - * width and height of the rect will not be 0 or negative. + * width and height of the rect will not be 0 or negative. The + * coordinates can be smaller than -1000 or bigger than 1000. But at + * least one vertex will be within (-1000, -1000) and (1000, 1000). * * <p>The direction is relative to the sensor orientation, that is, what * the sensor sees. The direction is not affected by the rotation or @@ -1464,6 +1471,8 @@ public class Camera { private static final String KEY_MAX_NUM_DETECTED_FACES_SW = "max-num-detected-faces-sw"; private static final String KEY_RECORDING_HINT = "recording-hint"; private static final String KEY_VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported"; + private static final String KEY_VIDEO_STABILIZATION = "video-stabilization"; + private static final String KEY_VIDEO_STABILIZATION_SUPPORTED = "video-stabilization-supported"; // Parameter key suffix for supported values. private static final String SUPPORTED_VALUES_SUFFIX = "-values"; @@ -1651,9 +1660,18 @@ public class Camera { * call {@link #takePicture(Camera.ShutterCallback, * Camera.PictureCallback, Camera.PictureCallback)} in this mode but the * subject may not be in focus. Auto focus starts when the parameter is - * set. Applications should not call {@link - * #autoFocus(AutoFocusCallback)} in this mode. To stop continuous - * focus, applications should change the focus mode to other modes. + * set. + * + * <p>Since API level 14, applications can call {@link + * #autoFocus(AutoFocusCallback)} in this mode. The focus callback will + * immediately return with a boolean that indicates whether the focus is + * sharp or not. The focus position is locked after autoFocus call. If + * applications want to resume the continuous focus, cancelAutoFocus + * must be called. Restarting the preview will not resume the continuous + * autofocus. To stop continuous focus, applications should change the + * focus mode to other modes. + * + * @see #FOCUS_MODE_CONTINUOUS_PICTURE */ public static final String FOCUS_MODE_CONTINUOUS_VIDEO = "continuous-video"; @@ -1661,13 +1679,17 @@ public class Camera { * Continuous auto focus mode intended for taking pictures. The camera * continuously tries to focus. The speed of focus change is more * aggressive than {@link #FOCUS_MODE_CONTINUOUS_VIDEO}. Auto focus - * starts when the parameter is set. If applications call {@link - * #autoFocus(AutoFocusCallback)} in this mode, the focus callback will - * immediately return with a boolean that indicates whether the focus is - * sharp or not. The apps can then decide if they want to take a picture - * immediately or to change the focus mode to auto, and run a full - * autofocus cycle. To stop continuous focus, applications should change - * the focus mode to other modes. + * starts when the parameter is set. + * + * <p>If applications call {@link #autoFocus(AutoFocusCallback)} in this + * mode, the focus callback will immediately return with a boolean that + * indicates whether the focus is sharp or not. The apps can then decide + * if they want to take a picture immediately or to change the focus + * mode to auto, and run a full autofocus cycle. The focus position is + * locked after autoFocus call. If applications want to resume the + * continuous focus, cancelAutoFocus must be called. Restarting the + * preview will not resume the continuous autofocus. To stop continuous + * focus, applications should change the focus mode to other modes. * * @see #FOCUS_MODE_CONTINUOUS_VIDEO */ @@ -2443,7 +2465,7 @@ public class Camera { * * @param value new white balance. * @see #getWhiteBalance() - * @see #setAutoWhiteBalanceLock() + * @see #setAutoWhiteBalanceLock(boolean) */ public void setWhiteBalance(String value) { set(KEY_WHITE_BALANCE, value); @@ -3059,8 +3081,9 @@ public class Camera { * when using zoom.</p> * * <p>Focus area only has effect if the current focus mode is - * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, or - * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}.</p> + * {@link #FOCUS_MODE_AUTO}, {@link #FOCUS_MODE_MACRO}, + * {@link #FOCUS_MODE_CONTINUOUS_VIDEO}, or + * {@link #FOCUS_MODE_CONTINUOUS_PICTURE}.</p> * * @return a list of current focus areas */ @@ -3208,6 +3231,59 @@ public class Camera { return TRUE.equals(str); } + /** + * <p>Enables and disables video stabilization. Use + * {@link #isVideoStabilizationSupported} to determine if calling this + * method is valid.</p> + * + * <p>Video stabilization reduces the shaking due to the motion of the + * camera in both the preview stream and in recorded videos, including + * data received from the preview callback. It does not reduce motion + * blur in images captured with + * {@link Camera#takePicture takePicture}.</p> + * + * <p>Video stabilization can be enabled and disabled while preview or + * recording is active, but toggling it may cause a jump in the video + * stream that may be undesirable in a recorded video.</p> + * + * @param toggle Set to true to enable video stabilization, and false to + * disable video stabilization. + * @see #isVideoStabilizationSupported() + * @see #getVideoStabilization() + * @hide + */ + public void setVideoStabilization(boolean toggle) { + set(KEY_VIDEO_STABILIZATION, toggle ? TRUE : FALSE); + } + + /** + * Get the current state of video stabilization. See + * {@link #setVideoStabilization} for details of video stabilization. + * + * @return true if video stabilization is enabled + * @see #isVideoStabilizationSupported() + * @see #setVideoStabilization(boolean) + * @hide + */ + public boolean getVideoStabilization() { + String str = get(KEY_VIDEO_STABILIZATION); + return TRUE.equals(str); + } + + /** + * Returns true if video stabilization is supported. See + * {@link #setVideoStabilization} for details of video stabilization. + * + * @return true if video stabilization is supported + * @see #setVideoStabilization(boolean) + * @see #getVideoStabilization() + * @hide + */ + public boolean isVideoStabilizationSupported() { + String str = get(KEY_VIDEO_STABILIZATION_SUPPORTED); + return TRUE.equals(str); + } + // Splits a comma delimited string to an ArrayList of String. // Return null if the passing string is null or the size is 0. private ArrayList<String> split(String str) { diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index aaad8a1..1b24f0c 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -40,6 +40,8 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { public long limitBytes; public long lastSnooze; + private static final long DEFAULT_MTU = 1500; + public NetworkPolicy(NetworkTemplate template, int cycleDay, long warningBytes, long limitBytes, long lastSnooze) { this.template = checkNotNull(template, "missing NetworkTemplate"); @@ -71,6 +73,17 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { return 0; } + /** + * Test if given measurement is near enough to {@link #limitBytes} to be + * considered over-limit. + */ + public boolean isOverLimit(long totalBytes) { + // over-estimate, since kernel will trigger limit once first packet + // trips over limit. + totalBytes += 2 * DEFAULT_MTU; + return limitBytes != LIMIT_DISABLED && totalBytes >= limitBytes; + } + /** {@inheritDoc} */ public int compareTo(NetworkPolicy another) { if (another == null || another.limitBytes == LIMIT_DISABLED) { diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 8483b4f..4bc0892 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -187,6 +187,59 @@ public final class ContactsContract { public static final String DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query"; /** + * <p> + * API for obtaining a pre-authorized version of a URI that normally requires special + * permission (beyond READ_CONTACTS) to read. The caller obtaining the pre-authorized URI + * must already have the necessary permissions to access the URI; otherwise a + * {@link SecurityException} will be thrown. + * </p> + * <p> + * The authorized URI returned in the bundle contains an expiring token that allows the + * caller to execute the query without having the special permissions that would normally + * be required. + * </p> + * <p> + * This API does not access disk, and should be safe to invoke from the UI thread. + * </p> + * <p> + * Example usage: + * <pre> + * Uri profileUri = ContactsContract.Profile.CONTENT_VCARD_URI; + * Bundle uriBundle = new Bundle(); + * uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, uri); + * Bundle authResponse = getContext().getContentResolver().call( + * ContactsContract.AUTHORITY_URI, + * ContactsContract.Authorization.AUTHORIZATION_METHOD, + * null, // String arg, not used. + * uriBundle); + * if (authResponse != null) { + * Uri preauthorizedProfileUri = (Uri) authResponse.getParcelable( + * ContactsContract.Authorization.KEY_AUTHORIZED_URI); + * // This pre-authorized URI can be queried by a caller without READ_PROFILE + * // permission. + * } + * </pre> + * </p> + * @hide + */ + public static final class Authorization { + /** + * The method to invoke to create a pre-authorized URI out of the input argument. + */ + public static final String AUTHORIZATION_METHOD = "authorize"; + + /** + * The key to set in the outbound Bundle with the URI that should be authorized. + */ + public static final String KEY_URI_TO_AUTHORIZE = "uri_to_authorize"; + + /** + * The key to retrieve from the returned Bundle to obtain the pre-authorized URI. + */ + public static final String KEY_AUTHORIZED_URI = "authorized_uri"; + } + + /** * @hide */ public static final class Preferences { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index bc05078..5754e60 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4053,6 +4053,14 @@ public final class Settings { public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout"; /** + * Duration in milliseconds before pre-authorized URIs for the contacts + * provider should expire. + * @hide + */ + public static final String CONTACTS_PREAUTH_URI_EXPIRATION = + "contacts_preauth_uri_expiration"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -4087,7 +4095,9 @@ public final class Settings { MOUNT_UMS_AUTOSTART, MOUNT_UMS_PROMPT, MOUNT_UMS_NOTIFY_ENABLED, - UI_NIGHT_MODE + UI_NIGHT_MODE, + LOCK_SCREEN_OWNER_INFO, + LOCK_SCREEN_OWNER_INFO_ENABLED }; /** diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 3bd0f76..ad2283e 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -117,6 +117,11 @@ public class Display { outSize.x = getRawWidth(); outSize.y = getRawHeight(); } + if (false) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.v(TAG, "Returning display size: " + outSize, here); + } if (DEBUG_DISPLAY_SIZE && doCompat) Slog.v( TAG, "Returning display size: " + outSize); } catch (RemoteException e) { diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index d57132a..1697382 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -502,9 +502,23 @@ public class TextureView extends View { * @see #isAvailable() * @see #getBitmap(int, int) * @see #getBitmap() + * + * @throws IllegalStateException if the hardware rendering context cannot be + * acquired to capture the bitmap */ public Bitmap getBitmap(Bitmap bitmap) { if (bitmap != null && isAvailable()) { + AttachInfo info = mAttachInfo; + if (info != null && info.mHardwareRenderer != null && + info.mHardwareRenderer.isEnabled()) { + if (!info.mHardwareRenderer.validate()) { + throw new IllegalStateException("Could not acquire hardware rendering context"); + } + } + + applyUpdate(); + applyTransformMatrix(); + mLayer.copyInto(bitmap); } return bitmap; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 15544cc..73e5026 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1486,7 +1486,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal | AccessibilityEvent.TYPE_VIEW_FOCUSED | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER - | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT; + | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT + | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED; /** * Temporary Rect currently for use in setBackground(). This will probably @@ -1890,12 +1891,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked * out of the public fields to keep the undefined bits out of the developer's way. * - * Flag to hide only the navigation buttons. Don't use this + * Flag to hide only the home button. Don't use this * unless you're a special part of the system UI (i.e., setup wizard, keyguard). - * - * THIS DOES NOT DISABLE THE BACK BUTTON */ - public static final int STATUS_BAR_DISABLE_NAVIGATION = 0x00200000; + public static final int STATUS_BAR_DISABLE_HOME = 0x00200000; /** * @hide @@ -1903,7 +1902,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked * out of the public fields to keep the undefined bits out of the developer's way. * - * Flag to hide only the back button. Don't use this + * Flag to hide only the back button. Don't use this * unless you're a special part of the system UI (i.e., setup wizard, keyguard). */ public static final int STATUS_BAR_DISABLE_BACK = 0x00400000; @@ -1921,6 +1920,28 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked + * out of the public fields to keep the undefined bits out of the developer's way. + * + * Flag to hide only the recent apps button. Don't use this + * unless you're a special part of the system UI (i.e., setup wizard, keyguard). + */ + public static final int STATUS_BAR_DISABLE_RECENT = 0x01000000; + + /** + * @hide + * + * NOTE: This flag may only be used in subtreeSystemUiVisibility, etc. etc. + * + * This hides HOME and RECENT and is provided for compatibility with interim implementations. + */ + @Deprecated + public static final int STATUS_BAR_DISABLE_NAVIGATION = + STATUS_BAR_DISABLE_HOME | STATUS_BAR_DISABLE_RECENT; + + /** + * @hide */ public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF; @@ -10125,6 +10146,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return mHardwareLayer; } + /** + * Destroys this View's hardware layer if possible. + * + * @return True if the layer was destroyed, false otherwise. + * + * @see #setLayerType(int, android.graphics.Paint) + * @see #LAYER_TYPE_HARDWARE + */ boolean destroyLayer() { if (mHardwareLayer != null) { mHardwareLayer.destroy(); diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index bfd2959..17bdff2 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -104,23 +104,23 @@ public interface WindowManagerPolicy { */ public final static String EXTRA_HDMI_PLUGGED_STATE = "state"; - // flags for interceptKeyTq /** - * Pass this event to the user / app. To be returned from {@link #interceptKeyTq}. + * Pass this event to the user / app. To be returned from + * {@link #interceptKeyBeforeQueueing}. */ public final static int ACTION_PASS_TO_USER = 0x00000001; /** * This key event should extend the user activity timeout and turn the lights on. - * To be returned from {@link #interceptKeyTq}. Do not return this and - * {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}. + * To be returned from {@link #interceptKeyBeforeQueueing}. + * Do not return this and {@link #ACTION_GO_TO_SLEEP} or {@link #ACTION_PASS_TO_USER}. */ public final static int ACTION_POKE_USER_ACTIVITY = 0x00000002; /** * This key event should put the device to sleep (and engage keyguard if necessary) - * To be returned from {@link #interceptKeyTq}. Do not return this and - * {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. + * To be returned from {@link #interceptKeyBeforeQueueing}. + * Do not return this and {@link #ACTION_POKE_USER_ACTIVITY} or {@link #ACTION_PASS_TO_USER}. */ public final static int ACTION_GO_TO_SLEEP = 0x00000004; @@ -677,10 +677,12 @@ public interface WindowManagerPolicy { * event will normally go. * @param event The key event. * @param policyFlags The policy flags associated with the key. - * @return Returns true if the policy consumed the event and it should - * not be further dispatched. + * @return 0 if the key should be dispatched immediately, -1 if the key should + * not be dispatched ever, or a positive value indicating the number of + * milliseconds by which the key dispatch should be delayed before trying + * again. */ - public boolean interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags); + public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags); /** * Called from the input dispatcher thread when an application did not handle diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 25bc559..86dd9df 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -200,15 +200,6 @@ import java.util.List; * <li>{@link #getBeforeText()} - The text of the source before the change.</li> * <li>{@link #getContentDescription()} - The content description of the source.</li> * </ul> - * <em>Note:</em> This event type is not dispatched to descendants though - * {@link android.view.View#dispatchPopulateAccessibilityEvent(AccessibilityEvent) - * View.dispatchPopulateAccessibilityEvent(AccessibilityEvent)}, hence the event - * source {@link android.view.View} and the sub-tree rooted at it will not receive - * calls to {@link android.view.View#onPopulateAccessibilityEvent(AccessibilityEvent) - * View.onPopulateAccessibilityEvent(AccessibilityEvent)}. The preferred way to add - * text content to such events is by setting the - * {@link android.R.styleable#View_contentDescription contentDescription} of the source - * view.</br> * </p> * <p> * <b>View text selection changed</b> - represents the event of changing the text diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index c8b67a8..2cc928f 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -1204,22 +1204,20 @@ class BrowserFrame extends Handler { * We delegate the request to CallbackProxy, and route its response to * {@link #nativeSslClientCert(int, X509Certificate)}. */ - private void requestClientCert(int handle, byte[] host_and_port_bytes) { - String host_and_port = new String(host_and_port_bytes, Charsets.UTF_8); + private void requestClientCert(int handle, String hostAndPort) { SslClientCertLookupTable table = SslClientCertLookupTable.getInstance(); - if (table.IsAllowed(host_and_port)) { + if (table.IsAllowed(hostAndPort)) { // previously allowed nativeSslClientCert(handle, - table.PrivateKey(host_and_port), - table.CertificateChain(host_and_port)); - } else if (table.IsDenied(host_and_port)) { + table.PrivateKey(hostAndPort), + table.CertificateChain(hostAndPort)); + } else if (table.IsDenied(hostAndPort)) { // previously denied nativeSslClientCert(handle, null, null); } else { // previously ignored or new mCallbackProxy.onReceivedClientCertRequest( - new ClientCertRequestHandler(this, handle, host_and_port, table), - host_and_port); + new ClientCertRequestHandler(this, handle, hostAndPort, table), hostAndPort); } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 9c44138..f1c2bde 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -211,6 +211,7 @@ public class WebSettings { private ZoomDensity mDefaultZoom = ZoomDensity.MEDIUM; private RenderPriority mRenderPriority = RenderPriority.NORMAL; private int mOverrideCacheMode = LOAD_DEFAULT; + private int mDoubleTapZoom = 100; private boolean mSaveFormData = true; private boolean mAutoFillEnabled = false; private boolean mSavePassword = true; @@ -769,6 +770,27 @@ public class WebSettings { } /** + * Set the double-tap zoom of the page in percent. Default is 100. + * @param doubleTapZoom A percent value for increasing or decreasing the double-tap zoom. + * @hide + */ + public void setDoubleTapZoom(int doubleTapZoom) { + if (mDoubleTapZoom != doubleTapZoom) { + mDoubleTapZoom = doubleTapZoom; + mWebView.updateDoubleTapZoom(); + } + } + + /** + * Get the double-tap zoom of the page in percent. + * @return A percent value describing the double-tap zoom. + * @hide + */ + public int getDoubleTapZoom() { + return mDoubleTapZoom; + } + + /** * Set the default zoom density of the page. This should be called from UI * thread. * @param zoom A ZoomDensity value diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 66371f5..b0ecf09 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -1002,6 +1002,9 @@ import junit.framework.Assert; | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; int imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_FLAG_NO_FULLSCREEN; + if (!mWebView.nativeFocusCandidateIsSpellcheck()) { + inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + } if (TEXT_AREA != type && mWebView.nativeFocusCandidateHasNextTextfield()) { imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index f46af51..1c22a0b 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2074,14 +2074,17 @@ public class WebView extends AbsoluteLayout * If the value of the encoding parameter is 'base64', then the data must * be encoded as base64. Otherwise, the data must use ASCII encoding for * octets inside the range of safe URL characters and use the standard %xx - * hex encoding of URLs for octets outside that range. + * hex encoding of URLs for octets outside that range. For example, + * '#', '%', '\', '?' should be replaced by %23, %25, %27, %3f respectively. * <p> * The 'data' scheme URL formed by this method uses the default US-ASCII * charset. If you need need to set a different charset, you should form a - * 'data' scheme URL which specifies a charset parameter and call - * {@link #loadUrl(String)} instead. + * 'data' scheme URL which explicitly specifies a charset parameter in the + * mediatype portion of the URL and call {@link #loadUrl(String)} instead. + * Note that the charset obtained from the mediatype portion of a data URL + * always overrides that specified in the HTML or XML document itself. * @param data A String of data in the given encoding. - * @param mimeType The MIMEType of the data, e.g. 'text/html'. + * @param mimeType The MIME type of the data, e.g. 'text/html'. * @param encoding The encoding of the data. */ public void loadData(String data, String mimeType, String encoding) { @@ -2985,6 +2988,13 @@ public class WebView extends AbsoluteLayout return false; } + /** + * Update the double-tap zoom. + */ + /* package */ void updateDoubleTapZoom() { + mZoomManager.updateDoubleTapZoom(); + } + private int computeRealHorizontalScrollRange() { if (mDrawHistory) { return mHistoryWidth; @@ -7415,6 +7425,10 @@ public class WebView extends AbsoluteLayout } } + void sendPluginDrawMsg() { + mWebViewCore.sendMessage(EventHub.PLUGIN_SURFACE_READY); + } + /** * Returns plugin bounds if x/y in content coordinates corresponds to a * plugin. Otherwise a NULL rectangle is returned. @@ -9454,6 +9468,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeFocusCandidateIsTextInput(); /* package */ native int nativeFocusCandidateMaxLength(); /* package */ native boolean nativeFocusCandidateIsAutoComplete(); + /* package */ native boolean nativeFocusCandidateIsSpellcheck(); /* package */ native String nativeFocusCandidateName(); private native Rect nativeFocusCandidateNodeBounds(); /** diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 44688b8..1294a28 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1032,6 +1032,8 @@ public final class WebViewCore { static final int EXECUTE_JS = 194; + static final int PLUGIN_SURFACE_READY = 195; + // private message ids private static final int DESTROY = 200; @@ -1587,6 +1589,10 @@ public final class WebViewCore { nativeFullScreenPluginHidden(msg.arg1); break; + case PLUGIN_SURFACE_READY: + nativePluginSurfaceReady(); + break; + case ADD_PACKAGE_NAMES: if (BrowserFrame.sJavaBridge == null) { throw new IllegalStateException("No WebView " + @@ -2826,6 +2832,7 @@ public final class WebViewCore { private native void nativeResume(); private native void nativeFreeMemory(); private native void nativeFullScreenPluginHidden(int npp); + private native void nativePluginSurfaceReady(); private native boolean nativeValidNodeAndBounds(int frame, int node, Rect bounds); diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 7f526e7..206142a 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -145,11 +145,11 @@ class ZoomManager { private float mInvDefaultScale; /* - * The scale factor that is used to determine the zoom level for reading text. - * The value is initially set to equal the display density. - * TODO: Support changing this in WebSettings + * The logical density of the display. This is a scaling factor for the + * Density Independent Pixel unit, where one DIP is one pixel on an + * approximately 160 dpi screen (see android.util.DisplayMetrics.density) */ - private float mReadingLevelScale; + private float mDisplayDensity; /* * The scale factor that is used as the minimum increment when going from @@ -233,11 +233,11 @@ class ZoomManager { public void init(float density) { assert density > 0; + mDisplayDensity = density; setDefaultZoomScale(density); mActualScale = density; mInvActualScale = 1 / density; - mReadingLevelScale = density; - mTextWrapScale = density; + mTextWrapScale = getReadingLevelScale(); } /** @@ -310,8 +310,11 @@ class ZoomManager { return mInitialScale > 0 ? mInitialScale : mDefaultScale; } + /** + * Returns the zoom scale used for reading text on a double-tap. + */ public final float getReadingLevelScale() { - return mReadingLevelScale; + return mDisplayDensity * mWebView.getSettings().getDoubleTapZoom() / 100.0f; } public final float getInvDefaultScale() { @@ -510,6 +513,13 @@ class ZoomManager { return mZoomScale != 0 || mInHWAcceleratedZoom; } + public void updateDoubleTapZoom() { + if (mInZoomOverview) { + mTextWrapScale = getReadingLevelScale(); + refreshZoomScale(true); + } + } + public void refreshZoomScale(boolean reflowText) { setZoomScale(mActualScale, reflowText, true); } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index df8eb05..bdaf89e 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -335,6 +335,9 @@ public abstract class AbsSeekBar extends ProgressBar { mTouchDownX = event.getX(); } else { setPressed(true); + if (mThumb != null) { + invalidate(mThumb.getBounds()); // This may be within the padding region + } onStartTrackingTouch(); trackTouchEvent(event); attemptClaimDrag(); @@ -348,6 +351,9 @@ public abstract class AbsSeekBar extends ProgressBar { final float x = event.getX(); if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) { setPressed(true); + if (mThumb != null) { + invalidate(mThumb.getBounds()); // This may be within the padding region + } onStartTrackingTouch(); trackTouchEvent(event); attemptClaimDrag(); diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index fd19b5f..e16a8bd 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -922,15 +922,20 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { if (selectedView != null) { event.setEnabled(selectedView.isEnabled()); } + event.setCurrentItemIndex(getSelectedItemPosition()); event.setFromIndex(getFirstVisiblePosition()); event.setToIndex(getLastVisiblePosition()); event.setItemCount(getAdapter().getCount()); } private boolean isScrollableForAccessibility() { - final int itemCount = getAdapter().getCount(); - return itemCount > 0 - && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1); + T adapter = getAdapter(); + if (adapter != null) { + final int itemCount = adapter.getCount(); + return itemCount > 0 + && (getFirstVisiblePosition() > 0 || getLastVisiblePosition() < itemCount - 1); + } + return false; } @Override diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 51506e8..083a952 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -29,12 +29,14 @@ import android.os.Handler; import android.os.SystemClock; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.widget.AbsListView.OnScrollListener; /** * Helper class for AbsListView to draw and control the Fast Scroll thumb */ class FastScroller { + private static final String TAG = "FastScroller"; // Minimum number of pages to justify showing a fast scroll thumb private static int MIN_PAGES = 4; @@ -81,15 +83,15 @@ class FastScroller { private Drawable mOverlayDrawableLeft; private Drawable mOverlayDrawableRight; - private int mThumbH; - private int mThumbW; - private int mThumbY; + int mThumbH; + int mThumbW; + int mThumbY; private RectF mOverlayPos; private int mOverlaySize; - private AbsListView mList; - private boolean mScrollCompleted; + AbsListView mList; + boolean mScrollCompleted; private int mVisibleItem; private Paint mPaint; private int mListOffset; @@ -105,7 +107,7 @@ class FastScroller { private Handler mHandler = new Handler(); - private BaseAdapter mListAdapter; + BaseAdapter mListAdapter; private SectionIndexer mSectionIndexer; private boolean mChangedBounds; @@ -118,10 +120,36 @@ class FastScroller { private boolean mMatchDragPosition; + float mInitialTouchY; + boolean mPendingDrag; + private int mScaledTouchSlop; + private static final int FADE_TIMEOUT = 1500; + private static final int PENDING_DRAG_DELAY = 180; private final Rect mTmpRect = new Rect(); + private final Runnable mDeferStartDrag = new Runnable() { + public void run() { + if (mList.mIsAttached) { + beginDrag(); + + final int viewHeight = mList.getHeight(); + // Jitter + int newThumbY = (int) mInitialTouchY - mThumbH + 10; + if (newThumbY < 0) { + newThumbY = 0; + } else if (newThumbY + mThumbH > viewHeight) { + newThumbY = viewHeight - mThumbH; + } + mThumbY = newThumbY; + scrollTo((float) mThumbY / (viewHeight - mThumbH)); + } + + mPendingDrag = false; + } + }; + public FastScroller(Context context, AbsListView listView) { mList = listView; init(context); @@ -264,6 +292,8 @@ class FastScroller { ta.recycle(); + mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + mMatchDragPosition = context.getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.HONEYCOMB; @@ -456,7 +486,7 @@ class FastScroller { return mSections; } - private void getSectionsFromIndexer() { + void getSectionsFromIndexer() { Adapter adapter = mList.getAdapter(); mSectionIndexer = null; if (adapter instanceof HeaderViewListAdapter) { @@ -489,7 +519,7 @@ class FastScroller { mListAdapter = null; } - private void scrollTo(float position) { + void scrollTo(float position) { int count = mList.getCount(); mScrollCompleted = false; float fThreshold = (1.0f / count) / 8; @@ -647,12 +677,45 @@ class FastScroller { cancelFling.recycle(); } + void cancelPendingDrag() { + mList.removeCallbacks(mDeferStartDrag); + mPendingDrag = false; + } + + void startPendingDrag() { + mPendingDrag = true; + mList.postDelayed(mDeferStartDrag, PENDING_DRAG_DELAY); + } + + void beginDrag() { + setState(STATE_DRAGGING); + if (mListAdapter == null && mList != null) { + getSectionsFromIndexer(); + } + if (mList != null) { + mList.requestDisallowInterceptTouchEvent(true); + mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + } + + cancelFling(); + } + boolean onInterceptTouchEvent(MotionEvent ev) { - if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) { - if (isPointInside(ev.getX(), ev.getY())) { - setState(STATE_DRAGGING); - return true; - } + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if (mState > STATE_NONE && isPointInside(ev.getX(), ev.getY())) { + if (!mList.isInScrollingContainer()) { + beginDrag(); + return true; + } + mInitialTouchY = ev.getY(); + startPendingDrag(); + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + cancelPendingDrag(); + break; } return false; } @@ -666,19 +729,32 @@ class FastScroller { if (action == MotionEvent.ACTION_DOWN) { if (isPointInside(me.getX(), me.getY())) { - setState(STATE_DRAGGING); - if (mListAdapter == null && mList != null) { - getSectionsFromIndexer(); + if (!mList.isInScrollingContainer()) { + beginDrag(); + return true; } - if (mList != null) { - mList.requestDisallowInterceptTouchEvent(true); - mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + mInitialTouchY = me.getY(); + startPendingDrag(); + } + } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here + if (mPendingDrag) { + // Allow a tap to scroll. + beginDrag(); + + final int viewHeight = mList.getHeight(); + // Jitter + int newThumbY = (int) me.getY() - mThumbH + 10; + if (newThumbY < 0) { + newThumbY = 0; + } else if (newThumbY + mThumbH > viewHeight) { + newThumbY = viewHeight - mThumbH; } + mThumbY = newThumbY; + scrollTo((float) mThumbY / (viewHeight - mThumbH)); - cancelFling(); - return true; + cancelPendingDrag(); + // Will hit the STATE_DRAGGING check below } - } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here if (mState == STATE_DRAGGING) { if (mList != null) { // ViewGroup does the right thing already, but there might @@ -698,6 +774,23 @@ class FastScroller { return true; } } else if (action == MotionEvent.ACTION_MOVE) { + if (mPendingDrag) { + final float y = me.getY(); + if (Math.abs(y - mInitialTouchY) > mScaledTouchSlop) { + setState(STATE_DRAGGING); + if (mListAdapter == null && mList != null) { + getSectionsFromIndexer(); + } + if (mList != null) { + mList.requestDisallowInterceptTouchEvent(true); + mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + } + + cancelFling(); + cancelPendingDrag(); + // Will hit the STATE_DRAGGING check below + } + } if (mState == STATE_DRAGGING) { final int viewHeight = mList.getHeight(); // Jitter @@ -717,6 +810,8 @@ class FastScroller { } return true; } + } else if (action == MotionEvent.ACTION_CANCEL) { + cancelPendingDrag(); } return false; } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index 62b078f..510e2d4 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -44,7 +44,6 @@ public class SpellChecker implements SpellCheckerSessionListener { private final static int MAX_SPELL_BATCH_SIZE = 50; private final TextView mTextView; - private final Editable mText; final SpellCheckerSession mSpellCheckerSession; final int mCookie; @@ -64,7 +63,6 @@ public class SpellChecker implements SpellCheckerSessionListener { public SpellChecker(TextView textView) { mTextView = textView; - mText = (Editable) textView.getText(); final TextServicesManager textServicesManager = (TextServicesManager) textView.getContext(). getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); @@ -121,9 +119,9 @@ public class SpellChecker implements SpellCheckerSessionListener { return mLength - 1; } - private void addSpellCheckSpan(int start, int end) { + private void addSpellCheckSpan(Editable editable, int start, int end) { final int index = nextSpellCheckSpanIndex(); - mText.setSpan(mSpellCheckSpans[index], start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + editable.setSpan(mSpellCheckSpans[index], start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mIds[index] = mSpanSequenceCounter++; } @@ -168,8 +166,9 @@ public class SpellChecker implements SpellCheckerSessionListener { private void spellCheck() { if (mSpellCheckerSession == null) return; - final int selectionStart = Selection.getSelectionStart(mText); - final int selectionEnd = Selection.getSelectionEnd(mText); + Editable editable = (Editable) mTextView.getText(); + final int selectionStart = Selection.getSelectionStart(editable); + final int selectionEnd = Selection.getSelectionEnd(editable); TextInfo[] textInfos = new TextInfo[mLength]; int textInfosCount = 0; @@ -178,12 +177,12 @@ public class SpellChecker implements SpellCheckerSessionListener { final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[i]; if (spellCheckSpan.isSpellCheckInProgress()) continue; - final int start = mText.getSpanStart(spellCheckSpan); - final int end = mText.getSpanEnd(spellCheckSpan); + final int start = editable.getSpanStart(spellCheckSpan); + final int end = editable.getSpanEnd(spellCheckSpan); // Do not check this word if the user is currently editing it if (start >= 0 && end > start && (selectionEnd < start || selectionStart > end)) { - final String word = mText.subSequence(start, end).toString(); + final String word = editable.subSequence(start, end).toString(); spellCheckSpan.setSpellCheckInProgress(true); textInfos[textInfosCount++] = new TextInfo(word, mCookie, mIds[i]); } @@ -202,6 +201,8 @@ public class SpellChecker implements SpellCheckerSessionListener { @Override public void onGetSuggestions(SuggestionsInfo[] results) { + Editable editable = (Editable) mTextView.getText(); + for (int i = 0; i < results.length; i++) { SuggestionsInfo suggestionsInfo = results[i]; if (suggestionsInfo.getCookie() != mCookie) continue; @@ -217,9 +218,9 @@ public class SpellChecker implements SpellCheckerSessionListener { SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j]; if (!isInDictionary && looksLikeTypo) { - createMisspelledSuggestionSpan(suggestionsInfo, spellCheckSpan); + createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan); } - mText.removeSpan(spellCheckSpan); + editable.removeSpan(spellCheckSpan); break; } } @@ -234,18 +235,18 @@ public class SpellChecker implements SpellCheckerSessionListener { } } - private void createMisspelledSuggestionSpan(SuggestionsInfo suggestionsInfo, - SpellCheckSpan spellCheckSpan) { - final int start = mText.getSpanStart(spellCheckSpan); - final int end = mText.getSpanEnd(spellCheckSpan); + private void createMisspelledSuggestionSpan(Editable editable, + SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) { + final int start = editable.getSpanStart(spellCheckSpan); + final int end = editable.getSpanEnd(spellCheckSpan); // Other suggestion spans may exist on that region, with identical suggestions, filter // them out to avoid duplicates. First, filter suggestion spans on that exact region. - SuggestionSpan[] suggestionSpans = mText.getSpans(start, end, SuggestionSpan.class); + SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class); final int length = suggestionSpans.length; for (int i = 0; i < length; i++) { - final int spanStart = mText.getSpanStart(suggestionSpans[i]); - final int spanEnd = mText.getSpanEnd(suggestionSpans[i]); + final int spanStart = editable.getSpanStart(suggestionSpans[i]); + final int spanEnd = editable.getSpanEnd(suggestionSpans[i]); if (spanStart != start || spanEnd != end) { suggestionSpans[i] = null; break; @@ -293,7 +294,7 @@ public class SpellChecker implements SpellCheckerSessionListener { SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED); - mText.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // TODO limit to the word rectangle region mTextView.invalidate(); @@ -304,22 +305,24 @@ public class SpellChecker implements SpellCheckerSessionListener { private Object mRange = new Object(); public void init(int start, int end) { - mText.setSpan(mRange, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ((Editable) mTextView.getText()).setSpan(mRange, start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } public void close() { - mText.removeSpan(mRange); + ((Editable) mTextView.getText()).removeSpan(mRange); } public boolean isDone() { - return mText.getSpanStart(mRange) < 0; + return ((Editable) mTextView.getText()).getSpanStart(mRange) < 0; } public void parse() { + Editable editable = (Editable) mTextView.getText(); // Iterate over the newly added text and schedule new SpellCheckSpans - final int start = mText.getSpanStart(mRange); - final int end = mText.getSpanEnd(mRange); - mWordIterator.setCharSequence(mText, start, end); + final int start = editable.getSpanStart(mRange); + final int end = editable.getSpanEnd(mRange); + mWordIterator.setCharSequence(editable, start, end); // Move back to the beginning of the current word, if any int wordStart = mWordIterator.preceding(start); @@ -333,14 +336,16 @@ public class SpellChecker implements SpellCheckerSessionListener { wordEnd = mWordIterator.getEnd(wordStart); } if (wordEnd == BreakIterator.DONE) { - mText.removeSpan(mRange); + editable.removeSpan(mRange); return; } // We need to expand by one character because we want to include the spans that // end/start at position start/end respectively. - SpellCheckSpan[] spellCheckSpans = mText.getSpans(start-1, end+1, SpellCheckSpan.class); - SuggestionSpan[] suggestionSpans = mText.getSpans(start-1, end+1, SuggestionSpan.class); + SpellCheckSpan[] spellCheckSpans = editable.getSpans(start - 1, end + 1, + SpellCheckSpan.class); + SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1, + SuggestionSpan.class); int nbWordsChecked = 0; boolean scheduleOtherSpellCheck = false; @@ -350,20 +355,20 @@ public class SpellChecker implements SpellCheckerSessionListener { // A new word has been created across the interval boundaries with this edit. // Previous spans (ended on start / started on end) removed, not valid anymore if (wordStart < start && wordEnd > start) { - removeSpansAt(start, spellCheckSpans); - removeSpansAt(start, suggestionSpans); + removeSpansAt(editable, start, spellCheckSpans); + removeSpansAt(editable, start, suggestionSpans); } if (wordStart < end && wordEnd > end) { - removeSpansAt(end, spellCheckSpans); - removeSpansAt(end, suggestionSpans); + removeSpansAt(editable, end, spellCheckSpans); + removeSpansAt(editable, end, suggestionSpans); } // Do not create new boundary spans if they already exist boolean createSpellCheckSpan = true; if (wordEnd == start) { for (int i = 0; i < spellCheckSpans.length; i++) { - final int spanEnd = mText.getSpanEnd(spellCheckSpans[i]); + final int spanEnd = editable.getSpanEnd(spellCheckSpans[i]); if (spanEnd == start) { createSpellCheckSpan = false; break; @@ -373,7 +378,7 @@ public class SpellChecker implements SpellCheckerSessionListener { if (wordStart == end) { for (int i = 0; i < spellCheckSpans.length; i++) { - final int spanStart = mText.getSpanStart(spellCheckSpans[i]); + final int spanStart = editable.getSpanStart(spellCheckSpans[i]); if (spanStart == end) { createSpellCheckSpan = false; break; @@ -386,7 +391,7 @@ public class SpellChecker implements SpellCheckerSessionListener { scheduleOtherSpellCheck = true; break; } - addSpellCheckSpan(wordStart, wordEnd); + addSpellCheckSpan(editable, wordStart, wordEnd); nbWordsChecked++; } } @@ -401,23 +406,23 @@ public class SpellChecker implements SpellCheckerSessionListener { } if (scheduleOtherSpellCheck) { - mText.setSpan(mRange, wordStart, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + editable.setSpan(mRange, wordStart, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - mText.removeSpan(mRange); + editable.removeSpan(mRange); } spellCheck(); } - private <T> void removeSpansAt(int offset, T[] spans) { + private <T> void removeSpansAt(Editable editable, int offset, T[] spans) { final int length = spans.length; for (int i = 0; i < length; i++) { final T span = spans[i]; - final int start = mText.getSpanStart(span); + final int start = editable.getSpanStart(span); if (start > offset) continue; - final int end = mText.getSpanEnd(span); + final int end = editable.getSpanEnd(span); if (end < offset) continue; - mText.removeSpan(span); + editable.removeSpan(span); } } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 41daf70..041e8a4 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2476,6 +2476,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (gravity != mGravity) { invalidate(); + mLayoutAlignment = null; } mGravity = gravity; diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index 9fbbb3d..a0e125a 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -17,32 +17,79 @@ package com.android.internal.app; import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; import android.os.Bundle; +import android.os.Handler; +import android.os.Vibrator; import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; import android.widget.ImageView; import android.widget.Toast; public class PlatLogoActivity extends Activity { Toast mToast; + ImageView mContent; + Vibrator mZzz = new Vibrator(); + int mCount; + final Handler mHandler = new Handler(); + + Runnable mSuperLongPress = new Runnable() { + public void run() { + mCount++; + mZzz.vibrate(50 * mCount); + final float scale = 1f + 0.25f * mCount * mCount; + mContent.setScaleX(scale); + mContent.setScaleY(scale); + + if (mCount <= 3) { + mHandler.postDelayed(mSuperLongPress, ViewConfiguration.getLongPressTimeout()); + } else { + try { + startActivity(new Intent(Intent.ACTION_MAIN) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .setClassName("com.android.systemui","com.android.systemui.Nyandroid")); + } catch (ActivityNotFoundException ex) { + android.util.Log.e("PlatLogoActivity", "Couldn't find platlogo screensaver."); + } + finish(); + } + } + }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mToast = Toast.makeText(this, "REZZZZZZZ...", Toast.LENGTH_SHORT); + mToast = Toast.makeText(this, "Android 4.0: Ice Cream Sandwich", Toast.LENGTH_SHORT); - ImageView content = new ImageView(this); - content.setImageResource(com.android.internal.R.drawable.platlogo); - content.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - - setContentView(content); - } + mContent = new ImageView(this); + mContent.setImageResource(com.android.internal.R.drawable.platlogo); + mContent.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_UP) { - mToast.show(); - } - return super.dispatchTouchEvent(ev); + mContent.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + final int action = event.getAction(); + if (action == MotionEvent.ACTION_DOWN) { + mContent.setPressed(true); + mHandler.removeCallbacks(mSuperLongPress); + mCount = 0; + mHandler.postDelayed(mSuperLongPress, 2*ViewConfiguration.getLongPressTimeout()); + } else if (action == MotionEvent.ACTION_UP) { + if (mContent.isPressed()) { + mContent.setPressed(false); + mHandler.removeCallbacks(mSuperLongPress); + mToast.show(); + } + } + return true; + } + }); + + setContentView(mContent); } } diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java index daabf42..77d0c97 100644 --- a/core/java/com/android/internal/app/ShutdownThread.java +++ b/core/java/com/android/internal/app/ShutdownThread.java @@ -109,7 +109,6 @@ public final class ShutdownThread extends Thread { if (confirm) { final CloseDialogReceiver closer = new CloseDialogReceiver(context); final AlertDialog dialog = new AlertDialog.Builder(context) - .setIconAttribute(android.R.attr.alertDialogIcon) .setTitle(com.android.internal.R.string.power_off) .setMessage(resourceId) .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() { diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index aabea2c..f25d65f 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -31,9 +31,6 @@ import android.view.View; import android.view.View.MeasureSpec; import android.view.ViewConfiguration; import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageButton; import java.util.ArrayList; @@ -71,8 +68,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback(); int mOpenSubMenuId; - public ActionMenuPresenter() { - super(com.android.internal.R.layout.action_menu_layout, + public ActionMenuPresenter(Context context) { + super(context, com.android.internal.R.layout.action_menu_layout, com.android.internal.R.layout.action_menu_item_layout); } @@ -98,7 +95,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter int width = mWidthLimit; if (mReserveOverflow) { if (mOverflowButton == null) { - mOverflowButton = new OverflowMenuButton(mContext); + mOverflowButton = new OverflowMenuButton(mSystemContext); final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); mOverflowButton.measure(spec, spec); } @@ -215,7 +212,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (hasOverflow) { if (mOverflowButton == null) { - mOverflowButton = new OverflowMenuButton(mContext); + mOverflowButton = new OverflowMenuButton(mSystemContext); } ViewGroup parent = (ViewGroup) mOverflowButton.getParent(); if (parent != mMenuView) { diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java index bec437a..1e06b5a 100644 --- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java @@ -29,8 +29,10 @@ import java.util.ArrayList; * be reused if possible when items change. */ public abstract class BaseMenuPresenter implements MenuPresenter { + protected Context mSystemContext; protected Context mContext; protected MenuBuilder mMenu; + protected LayoutInflater mSystemInflater; protected LayoutInflater mInflater; private Callback mCallback; @@ -44,10 +46,13 @@ public abstract class BaseMenuPresenter implements MenuPresenter { /** * Construct a new BaseMenuPresenter. * + * @param context Context for generating system-supplied views * @param menuLayoutRes Layout resource ID for the menu container view * @param itemLayoutRes Layout resource ID for a single item view */ - public BaseMenuPresenter(int menuLayoutRes, int itemLayoutRes) { + public BaseMenuPresenter(Context context, int menuLayoutRes, int itemLayoutRes) { + mSystemContext = context; + mSystemInflater = LayoutInflater.from(context); mMenuLayoutRes = menuLayoutRes; mItemLayoutRes = itemLayoutRes; } @@ -62,7 +67,7 @@ public abstract class BaseMenuPresenter implements MenuPresenter { @Override public MenuView getMenuView(ViewGroup root) { if (mMenuView == null) { - mMenuView = (MenuView) mInflater.inflate(mMenuLayoutRes, root, false); + mMenuView = (MenuView) mSystemInflater.inflate(mMenuLayoutRes, root, false); mMenuView.initialize(mMenu); updateMenuView(true); } @@ -138,7 +143,7 @@ public abstract class BaseMenuPresenter implements MenuPresenter { * @return The new item view */ public MenuView.ItemView createItemView(ViewGroup parent) { - return (MenuView.ItemView) mInflater.inflate(mItemLayoutRes, parent, false); + return (MenuView.ItemView) mSystemInflater.inflate(mItemLayoutRes, parent, false); } /** diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java index 24ddad6..2439b5d 100644 --- a/core/java/com/android/internal/view/menu/IconMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java @@ -22,7 +22,6 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.SparseArray; import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -43,16 +42,15 @@ public class IconMenuPresenter extends BaseMenuPresenter { private static final String VIEWS_TAG = "android:menu:icon"; private static final String OPEN_SUBMENU_KEY = "android:menu:icon:submenu"; - public IconMenuPresenter() { - super(com.android.internal.R.layout.icon_menu_layout, + public IconMenuPresenter(Context context) { + super(new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu), + com.android.internal.R.layout.icon_menu_layout, com.android.internal.R.layout.icon_menu_item_layout); } @Override public void initForMenu(Context context, MenuBuilder menu) { - mContext = new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu); - mInflater = LayoutInflater.from(mContext); - mMenu = menu; + super.initForMenu(context, menu); mMaxItems = -1; } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index acffa5c..446dab1 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -207,7 +207,7 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi }); final MenuBuilder menu = (MenuBuilder) mode.getMenu(); - mActionMenuPresenter = new ActionMenuPresenter(); + mActionMenuPresenter = new ActionMenuPresenter(mContext); mActionMenuPresenter.setReserveOverflow(true); final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index 6d2e823..61bce60 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -373,7 +373,7 @@ public class ActionBarView extends AbsActionBarView { } } if (mActionMenuPresenter == null) { - mActionMenuPresenter = new ActionMenuPresenter(); + mActionMenuPresenter = new ActionMenuPresenter(mContext); mActionMenuPresenter.setCallback(cb); mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter); mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 97bbe52..a2fc6e2 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -17,8 +17,6 @@ package com.android.internal.widget; -import com.android.internal.R; - import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -34,11 +32,15 @@ import android.os.Debug; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; -import android.os.Vibrator; import android.util.AttributeSet; import android.util.Log; +import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.internal.R; import java.util.ArrayList; import java.util.List; @@ -57,9 +59,6 @@ public class LockPatternView extends View { private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h) private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h) - // Vibrator pattern for creating a tactile bump - private static final long[] DEFAULT_VIBE_PATTERN = {0, 1, 40, 41}; - private static final boolean PROFILE_DRAWING = false; private boolean mDrawingProfilingStarted = false; @@ -100,7 +99,7 @@ public class LockPatternView extends View { private DisplayMode mPatternDisplayMode = DisplayMode.Correct; private boolean mInputEnabled = true; private boolean mInStealthMode = false; - private boolean mTactileFeedbackEnabled = true; + private boolean mEnableHapticFeedback = true; private boolean mPatternInProgress = false; private float mDiameterFactor = 0.10f; // TODO: move to attrs @@ -125,11 +124,6 @@ public class LockPatternView extends View { private int mBitmapWidth; private int mBitmapHeight; - - private Vibrator vibe; // Vibrator for creating tactile feedback - - private long[] mVibePattern; - private int mAspect; private final Matrix mArrowMatrix = new Matrix(); private final Matrix mCircleMatrix = new Matrix(); @@ -248,7 +242,6 @@ public class LockPatternView extends View { public LockPatternView(Context context, AttributeSet attrs) { super(context, attrs); - vibe = new Vibrator(); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LockPatternView); @@ -293,26 +286,6 @@ public class LockPatternView extends View { mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight()); } - // allow vibration pattern to be customized - mVibePattern = loadVibratePattern(com.android.internal.R.array.config_virtualKeyVibePattern); - } - - private long[] loadVibratePattern(int id) { - int[] pattern = null; - try { - pattern = getResources().getIntArray(id); - } catch (Resources.NotFoundException e) { - Log.e(TAG, "Vibrate pattern missing, using default", e); - } - if (pattern == null) { - return DEFAULT_VIBE_PATTERN; - } - - long[] tmpPattern = new long[pattern.length]; - for (int i = 0; i < pattern.length; i++) { - tmpPattern[i] = pattern[i]; - } - return tmpPattern; } private Bitmap getBitmapFor(int resId) { @@ -330,7 +303,7 @@ public class LockPatternView extends View { * @return Whether the view has tactile feedback enabled. */ public boolean isTactileFeedbackEnabled() { - return mTactileFeedbackEnabled; + return mEnableHapticFeedback; } /** @@ -350,7 +323,7 @@ public class LockPatternView extends View { * @param tactileFeedbackEnabled Whether tactile feedback is enabled */ public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) { - mTactileFeedbackEnabled = tactileFeedbackEnabled; + mEnableHapticFeedback = tactileFeedbackEnabled; } /** @@ -401,6 +374,34 @@ public class LockPatternView extends View { invalidate(); } + private void notifyCellAdded() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternCellAdded(mPattern); + } + sendAccessEvent(R.string.lockscreen_access_pattern_cell_added); + } + + private void notifyPatternStarted() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternStart(); + } + sendAccessEvent(R.string.lockscreen_access_pattern_start); + } + + private void notifyPatternDetected() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternDetected(mPattern); + } + sendAccessEvent(R.string.lockscreen_access_pattern_detected); + } + + private void notifyPatternCleared() { + if (mOnPatternListener != null) { + mOnPatternListener.onPatternCleared(); + } + sendAccessEvent(R.string.lockscreen_access_pattern_cleared); + } + /** * Clear the pattern. */ @@ -543,8 +544,10 @@ public class LockPatternView extends View { addCellToPattern(fillInGapCell); } addCellToPattern(cell); - if (mTactileFeedbackEnabled){ - vibe.vibrate(mVibePattern, -1); // Generate tactile feedback + if (mEnableHapticFeedback) { + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } return cell; } @@ -554,9 +557,7 @@ public class LockPatternView extends View { private void addCellToPattern(Cell newCell) { mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true; mPattern.add(newCell); - if (mOnPatternListener != null) { - mOnPatternListener.onPatternCellAdded(mPattern); - } + notifyCellAdded(); } // helper method to find which cell a point maps to @@ -619,6 +620,27 @@ public class LockPatternView extends View { } @Override + public boolean onHoverEvent(MotionEvent event) { + if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + event.setAction(MotionEvent.ACTION_DOWN); + break; + case MotionEvent.ACTION_HOVER_MOVE: + event.setAction(MotionEvent.ACTION_MOVE); + break; + case MotionEvent.ACTION_HOVER_EXIT: + event.setAction(MotionEvent.ACTION_UP); + break; + } + onTouchEvent(event); + event.setAction(action); + } + return super.onHoverEvent(event); + } + + @Override public boolean onTouchEvent(MotionEvent event) { if (!mInputEnabled || !isEnabled()) { return false; @@ -636,10 +658,8 @@ public class LockPatternView extends View { return true; case MotionEvent.ACTION_CANCEL: resetPattern(); - if (mOnPatternListener != null) { - mPatternInProgress = false; - mOnPatternListener.onPatternCleared(); - } + mPatternInProgress = false; + notifyPatternCleared(); if (PROFILE_DRAWING) { if (mDrawingProfilingStarted) { Debug.stopMethodTracing(); @@ -661,9 +681,9 @@ public class LockPatternView extends View { final int patternSizePreHitDetect = mPattern.size(); Cell hitCell = detectAndAddHit(x, y); final int patternSize = mPattern.size(); - if (hitCell != null && (mOnPatternListener != null) && (patternSize == 1)) { + if (hitCell != null && patternSize == 1) { mPatternInProgress = true; - mOnPatternListener.onPatternStart(); + notifyPatternStarted(); } // note current x and y for rubber banding of in progress patterns final float dx = Math.abs(x - mInProgressX); @@ -778,11 +798,17 @@ public class LockPatternView extends View { } } + private void sendAccessEvent(int resId) { + setContentDescription(mContext.getString(resId)); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); + setContentDescription(null); + } + private void handleActionUp(MotionEvent event) { // report pattern detected - if (!mPattern.isEmpty() && mOnPatternListener != null) { + if (!mPattern.isEmpty()) { mPatternInProgress = false; - mOnPatternListener.onPatternDetected(mPattern); + notifyPatternDetected(); invalidate(); } if (PROFILE_DRAWING) { @@ -798,13 +824,13 @@ public class LockPatternView extends View { final float x = event.getX(); final float y = event.getY(); final Cell hitCell = detectAndAddHit(x, y); - if (hitCell != null && mOnPatternListener != null) { + if (hitCell != null) { mPatternInProgress = true; mPatternDisplayMode = DisplayMode.Correct; - mOnPatternListener.onPatternStart(); - } else if (mOnPatternListener != null) { + notifyPatternStarted(); + } else { mPatternInProgress = false; - mOnPatternListener.onPatternCleared(); + notifyPatternCleared(); } if (hitCell != null) { final float startX = getCenterXForColumn(hitCell.column); @@ -1061,7 +1087,7 @@ public class LockPatternView extends View { return new SavedState(superState, LockPatternUtils.patternToString(mPattern), mPatternDisplayMode.ordinal(), - mInputEnabled, mInStealthMode, mTactileFeedbackEnabled); + mInputEnabled, mInStealthMode, mEnableHapticFeedback); } @Override @@ -1074,7 +1100,7 @@ public class LockPatternView extends View { mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()]; mInputEnabled = ss.isInputEnabled(); mInStealthMode = ss.isInStealthMode(); - mTactileFeedbackEnabled = ss.isTactileFeedbackEnabled(); + mEnableHapticFeedback = ss.isTactileFeedbackEnabled(); } /** diff --git a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java index 01df48a..2e7810f 100644 --- a/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java +++ b/core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java @@ -26,6 +26,7 @@ import android.os.SystemClock; import android.os.Vibrator; import android.provider.Settings; import android.util.Log; +import android.view.HapticFeedbackConstants; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; @@ -52,7 +53,7 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { private final View mTargetView; private final KeyboardView mKeyboardView; private long[] mVibratePattern; - private final Vibrator mVibrator; + private boolean mEnableHaptics = false; public PasswordEntryKeyboardHelper(Context context, KeyboardView keyboardView, View targetView) { this(context, keyboardView, targetView, true); @@ -71,7 +72,10 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { mKeyboardView.getLayoutParams().height); } mKeyboardView.setOnKeyboardActionListener(this); - mVibrator = new Vibrator(); + } + + public void setEnableHaptics(boolean enabled) { + mEnableHaptics = enabled; } public boolean isAlpha() { @@ -230,6 +234,7 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { public void handleBackspace() { sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); + performHapticFeedback(); } private void handleShift() { @@ -272,8 +277,14 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener { } public void onPress(int primaryCode) { - if (mVibratePattern != null) { - mVibrator.vibrate(mVibratePattern, -1); + performHapticFeedback(); + } + + private void performHapticFeedback() { + if (mEnableHaptics) { + mKeyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING + | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } } diff --git a/core/java/com/android/internal/widget/TransportControlView.java b/core/java/com/android/internal/widget/TransportControlView.java index 1042a59..73d9f10 100644 --- a/core/java/com/android/internal/widget/TransportControlView.java +++ b/core/java/com/android/internal/widget/TransportControlView.java @@ -336,20 +336,27 @@ public class TransportControlView extends FrameLayout implements OnClickListener if (state == mPlayState) { return; } + final int imageResId; + final int imageDescId; switch (state) { case RemoteControlClient.PLAYSTATE_PLAYING: - mBtnPlay.setImageResource(com.android.internal.R.drawable.ic_media_pause); + imageResId = com.android.internal.R.drawable.ic_media_pause; + imageDescId = com.android.internal.R.string.lockscreen_transport_pause_description; break; case RemoteControlClient.PLAYSTATE_BUFFERING: - mBtnPlay.setImageResource(com.android.internal.R.drawable.ic_media_stop); + imageResId = com.android.internal.R.drawable.ic_media_stop; + imageDescId = com.android.internal.R.string.lockscreen_transport_stop_description; break; case RemoteControlClient.PLAYSTATE_PAUSED: default: - mBtnPlay.setImageResource(com.android.internal.R.drawable.ic_media_play); + imageResId = com.android.internal.R.drawable.ic_media_play; + imageDescId = com.android.internal.R.string.lockscreen_transport_play_description; break; } + mBtnPlay.setImageResource(imageResId); + mBtnPlay.setContentDescription(getResources().getString(imageDescId)); mPlayState = state; } |