summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountManagerService.java8
-rw-r--r--core/java/android/accounts/ChooseAccountTypeActivity.java13
-rw-r--r--core/java/android/accounts/ChooseTypeAndAccountActivity.java171
-rw-r--r--core/java/android/animation/LayoutTransition.java12
-rw-r--r--core/java/android/app/ActionBar.java9
-rw-r--r--core/java/android/app/Activity.java36
-rw-r--r--core/java/android/app/ActivityThread.java57
-rw-r--r--core/java/android/app/ApplicationThreadNative.java12
-rw-r--r--core/java/android/app/ContextImpl.java14
-rw-r--r--core/java/android/app/Fragment.java9
-rw-r--r--core/java/android/app/FragmentManager.java6
-rw-r--r--core/java/android/app/FragmentTransaction.java6
-rw-r--r--core/java/android/app/IApplicationThread.java8
-rw-r--r--core/java/android/app/Instrumentation.java3
-rw-r--r--core/java/android/app/IntentService.java6
-rw-r--r--core/java/android/app/LoadedApk.java3
-rw-r--r--core/java/android/app/LoaderManager.java6
-rw-r--r--core/java/android/app/PendingIntent.java4
-rw-r--r--core/java/android/app/Service.java12
-rw-r--r--core/java/android/app/SharedPreferencesImpl.java4
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl6
-rw-r--r--core/java/android/app/backup/WallpaperBackupHelper.java2
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java52
-rw-r--r--core/java/android/content/BroadcastReceiver.java13
-rw-r--r--core/java/android/content/ContentProvider.java23
-rw-r--r--core/java/android/content/ContentProviderNative.java468
-rw-r--r--core/java/android/content/ContentResolver.java25
-rw-r--r--core/java/android/content/CursorLoader.java1
-rw-r--r--core/java/android/content/IContentProvider.java10
-rw-r--r--core/java/android/content/Intent.java33
-rw-r--r--core/java/android/content/IntentFilter.java8
-rw-r--r--core/java/android/content/Loader.java8
-rw-r--r--core/java/android/content/SyncManager.java88
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/database/AbstractCursor.java15
-rw-r--r--core/java/android/database/AbstractWindowedCursor.java81
-rw-r--r--core/java/android/database/BulkCursorNative.java243
-rw-r--r--core/java/android/database/BulkCursorToCursorAdaptor.java100
-rw-r--r--core/java/android/database/CrossProcessCursor.java2
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java234
-rw-r--r--core/java/android/database/CursorWindow.java831
-rw-r--r--core/java/android/database/CursorWindowAllocationException.java15
-rw-r--r--core/java/android/database/CursorWrapper.java4
-rw-r--r--core/java/android/database/IBulkCursor.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java207
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java39
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java30
-rw-r--r--core/java/android/hardware/Camera.java140
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java37
-rw-r--r--core/java/android/net/ConnectivityManager.java4
-rw-r--r--core/java/android/net/DhcpStateMachine.java8
-rw-r--r--core/java/android/net/DnsPinger.java36
-rw-r--r--core/java/android/net/NetworkPolicy.java13
-rw-r--r--core/java/android/net/NetworkQuotaInfo.java2
-rw-r--r--core/java/android/net/NetworkStats.java126
-rw-r--r--core/java/android/net/NetworkStatsHistory.java25
-rw-r--r--core/java/android/net/http/SslError.java109
-rw-r--r--core/java/android/nfc/NfcAdapter.java55
-rw-r--r--core/java/android/os/AsyncTask.java7
-rw-r--r--core/java/android/os/Build.java10
-rw-r--r--core/java/android/os/Bundle.java51
-rw-r--r--core/java/android/os/Parcel.java6
-rw-r--r--core/java/android/os/StrictMode.java10
-rw-r--r--core/java/android/provider/CalendarContract.java169
-rw-r--r--core/java/android/provider/CallLog.java8
-rw-r--r--core/java/android/provider/ContactsContract.java37
-rw-r--r--core/java/android/provider/Settings.java4
-rwxr-xr-xcore/java/android/provider/Telephony.java3
-rw-r--r--core/java/android/server/BluetoothAdapterStateMachine.java26
-rw-r--r--core/java/android/server/BluetoothHealthProfileHandler.java3
-rwxr-xr-xcore/java/android/server/BluetoothService.java4
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java4
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java36
-rw-r--r--core/java/android/text/CharSequenceIterator.java100
-rw-r--r--core/java/android/text/DynamicLayout.java10
-rw-r--r--core/java/android/text/StaticLayout.java10
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java10
-rw-r--r--core/java/android/text/method/WordIterator.java151
-rw-r--r--core/java/android/text/style/SuggestionSpan.java32
-rw-r--r--core/java/android/view/ActionMode.java12
-rw-r--r--core/java/android/view/Display.java5
-rw-r--r--core/java/android/view/HardwareRenderer.java29
-rw-r--r--core/java/android/view/IWindow.aidl3
-rw-r--r--core/java/android/view/IWindowManager.aidl7
-rw-r--r--core/java/android/view/IWindowSession.aidl7
-rw-r--r--core/java/android/view/SurfaceView.java6
-rw-r--r--core/java/android/view/TextureView.java14
-rw-r--r--core/java/android/view/View.java145
-rw-r--r--core/java/android/view/ViewGroup.java30
-rw-r--r--core/java/android/view/ViewRootImpl.java124
-rw-r--r--core/java/android/view/WindowManager.java29
-rw-r--r--core/java/android/view/WindowManagerPolicy.java68
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java241
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java61
-rw-r--r--core/java/android/webkit/BrowserFrame.java32
-rw-r--r--core/java/android/webkit/CallbackProxy.java107
-rw-r--r--core/java/android/webkit/FindActionModeCallback.java24
-rw-r--r--core/java/android/webkit/HTML5Audio.java54
-rw-r--r--core/java/android/webkit/SelectActionModeCallback.java25
-rw-r--r--core/java/android/webkit/SslCertLookupTable.java27
-rw-r--r--core/java/android/webkit/SslErrorHandlerImpl.java69
-rw-r--r--core/java/android/webkit/WebSettings.java22
-rw-r--r--core/java/android/webkit/WebTextView.java31
-rw-r--r--core/java/android/webkit/WebView.java104
-rw-r--r--core/java/android/webkit/WebViewClient.java19
-rw-r--r--core/java/android/webkit/WebViewCore.java42
-rw-r--r--core/java/android/webkit/ZoomManager.java24
-rw-r--r--core/java/android/widget/AbsListView.java131
-rw-r--r--core/java/android/widget/AbsSeekBar.java2
-rw-r--r--core/java/android/widget/ActivityChooserView.java46
-rw-r--r--core/java/android/widget/AdapterView.java50
-rw-r--r--core/java/android/widget/AnalogClock.java10
-rw-r--r--core/java/android/widget/FastScroller.java139
-rw-r--r--core/java/android/widget/Gallery.java29
-rw-r--r--core/java/android/widget/HorizontalScrollView.java18
-rw-r--r--core/java/android/widget/ListView.java25
-rw-r--r--core/java/android/widget/ScrollView.java19
-rw-r--r--core/java/android/widget/SearchView.java13
-rw-r--r--core/java/android/widget/ShareActionProvider.java6
-rw-r--r--core/java/android/widget/SpellChecker.java277
-rw-r--r--core/java/android/widget/TextView.java281
-rw-r--r--core/java/android/widget/TimePicker.java4
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl1
-rw-r--r--core/java/com/android/internal/app/PlatLogoActivity.java73
-rw-r--r--core/java/com/android/internal/app/RingtonePickerActivity.java15
-rw-r--r--core/java/com/android/internal/app/ShutdownThread.java1
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java379
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java142
-rw-r--r--core/java/com/android/internal/policy/IFaceLockCallback.aidl1
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl1
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl1
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java5
-rw-r--r--core/java/com/android/internal/view/StandaloneActionMode.java8
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuPresenter.java11
-rw-r--r--core/java/com/android/internal/view/menu/BaseMenuPresenter.java11
-rw-r--r--core/java/com/android/internal/view/menu/ExpandedMenuView.java5
-rw-r--r--core/java/com/android/internal/view/menu/IconMenuPresenter.java10
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java21
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java8
-rw-r--r--core/java/com/android/internal/widget/ActionBarContextView.java2
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java2
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java93
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java134
-rw-r--r--core/java/com/android/internal/widget/PasswordEntryKeyboardHelper.java27
-rw-r--r--core/java/com/android/internal/widget/TransportControlView.java13
145 files changed, 4899 insertions, 2663 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 91def67..1ba8eee 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1033,8 +1033,12 @@ public class AccountManagerService
mContext.getString(R.string.permission_request_notification_with_subtitle,
account.name);
final int index = titleAndSubtitle.indexOf('\n');
- final String title = titleAndSubtitle.substring(0, index);
- final String subtitle = titleAndSubtitle.substring(index + 1);
+ String title = titleAndSubtitle;
+ String subtitle = "";
+ if (index > 0) {
+ title = titleAndSubtitle.substring(0, index);
+ subtitle = titleAndSubtitle.substring(index + 1);
+ }
n.setLatestEventInfo(mContext,
title, subtitle,
PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
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/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 355b1fc..f383af9 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -24,6 +24,7 @@ import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -615,10 +616,13 @@ public class LayoutTransition {
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
parent.getViewTreeObserver().removeOnPreDrawListener(this);
- int numChildren = parent.getChildCount();
- for (int i = 0; i < numChildren; ++i) {
- final View child = parent.getChildAt(i);
- child.removeOnLayoutChangeListener(layoutChangeListenerMap.get(child));
+ int count = layoutChangeListenerMap.size();
+ if (count > 0) {
+ Collection<View> views = layoutChangeListenerMap.keySet();
+ for (View view : views) {
+ View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view);
+ view.removeOnLayoutChangeListener(listener);
+ }
}
layoutChangeListenerMap.clear();
return true;
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 51c6f3a..24d3a6b 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -43,15 +43,18 @@ import android.widget.SpinnerAdapter;
* modify various characteristics of the action bar or remove it completely.</p>
* <p>From your activity, you can retrieve an instance of {@link ActionBar} by calling {@link
* android.app.Activity#getActionBar getActionBar()}.</p>
- * <p>For information about how to use the action bar, including how to add action items, navigation
- * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
- * Bar</a> developer guide.</p>
* <p>In some cases, the action bar may be overlayed by another bar that enables contextual actions,
* using an {@link android.view.ActionMode}. For example, when the user selects one or more items in
* your activity, you can enable an action mode that offers actions specific to the selected
* items, with a UI that temporarily replaces the action bar. Although the UI may occupy the
* same space, the {@link android.view.ActionMode} APIs are distinct and independent from those for
* {@link ActionBar}.
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to use the action bar, including how to add action items, navigation
+ * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
+ * Bar</a> developer guide.</p>
+ * </div>
*/
public abstract class ActionBar {
/**
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 034e3c7..8e8d37d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -111,18 +111,6 @@ import java.util.HashMap;
* {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
* declaration in their package's <code>AndroidManifest.xml</code>.</p>
*
- * <p>The Activity class is an important part of an application's overall lifecycle,
- * and the way activities are launched and put together is a fundamental
- * part of the platform's application model. For a detailed perspective on the structure of an
- * Android application and how activities behave, please read the
- * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a> and
- * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a>
- * documents.</p>
- *
- * <p>You can also find a detailed discussion about how to create activities in the
- * <a href="{@docRoot}guide/topics/fundamentals/activities.html">Activities</a>
- * document.</p>
- *
* <p>Topics covered here:
* <ol>
* <li><a href="#Fragments">Fragments</a>
@@ -133,7 +121,22 @@ import java.util.HashMap;
* <li><a href="#Permissions">Permissions</a>
* <li><a href="#ProcessLifecycle">Process Lifecycle</a>
* </ol>
- *
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>The Activity class is an important part of an application's overall lifecycle,
+ * and the way activities are launched and put together is a fundamental
+ * part of the platform's application model. For a detailed perspective on the structure of an
+ * Android application and how activities behave, please read the
+ * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a> and
+ * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a>
+ * developer guides.</p>
+ *
+ * <p>You can also find a detailed discussion about how to create activities in the
+ * <a href="{@docRoot}guide/topics/fundamentals/activities.html">Activities</a>
+ * developer guide.</p>
+ * </div>
+ *
* <a name="Fragments"></a>
* <h3>Fragments</h3>
*
@@ -3246,6 +3249,7 @@ public class Activity extends ContextThemeWrapper
try {
String resolvedType = null;
if (fillInIntent != null) {
+ fillInIntent.setAllowFds(false);
resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver());
}
int result = ActivityManagerNative.getDefault()
@@ -3370,6 +3374,7 @@ public class Activity extends ContextThemeWrapper
if (mParent == null) {
int result = IActivityManager.START_RETURN_INTENT_TO_CALLER;
try {
+ intent.setAllowFds(false);
result = ActivityManagerNative.getDefault()
.startActivity(mMainThread.getApplicationThread(),
intent, intent.resolveTypeIfNeeded(
@@ -3419,6 +3424,7 @@ public class Activity extends ContextThemeWrapper
public boolean startNextMatchingActivity(Intent intent) {
if (mParent == null) {
try {
+ intent.setAllowFds(false);
return ActivityManagerNative.getDefault()
.startNextMatchingActivity(mToken, intent);
} catch (RemoteException e) {
@@ -3692,6 +3698,9 @@ public class Activity extends ContextThemeWrapper
}
if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
+ if (resultData != null) {
+ resultData.setAllowFds(false);
+ }
if (ActivityManagerNative.getDefault()
.finishActivity(mToken, resultCode, resultData)) {
mFinished = true;
@@ -3812,6 +3821,7 @@ public class Activity extends ContextThemeWrapper
int flags) {
String packageName = getPackageName();
try {
+ data.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0776e10..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);
}
@@ -1478,7 +1484,7 @@ public final class ActivityThread {
}
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
- DisplayMetrics metrics = getDisplayMetricsLocked(compInfo, false);
+ DisplayMetrics metrics = getDisplayMetricsLocked(null, false);
r = new Resources(assets, metrics, getConfiguration(), compInfo);
if (false) {
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
@@ -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);
@@ -2674,6 +2683,7 @@ public final class ActivityThread {
// Next have the activity save its current state and managed dialogs...
if (!r.activity.mFinished && saveState) {
state = new Bundle();
+ state.setAllowFds(false);
mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
r.state = state;
}
@@ -2775,6 +2785,7 @@ public final class ActivityThread {
if (!r.activity.mFinished && saveState) {
if (r.state == null) {
state = new Bundle();
+ state.setAllowFds(false);
mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
r.state = state;
} else {
@@ -3306,6 +3317,7 @@ public final class ActivityThread {
}
if (r.state == null && !r.stopped && !r.isPreHoneycomb()) {
r.state = new Bundle();
+ r.state.setAllowFds(false);
mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
}
@@ -3473,7 +3485,7 @@ public final class ActivityThread {
return false;
}
int changes = mResConfiguration.updateFrom(config);
- DisplayMetrics dm = getDisplayMetricsLocked(compat, true);
+ DisplayMetrics dm = getDisplayMetricsLocked(null, true);
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
@@ -3514,7 +3526,20 @@ public final class ActivityThread {
return changes != 0;
}
-
+
+ final Configuration applyCompatConfiguration() {
+ Configuration config = mConfiguration;
+ if (mCompatConfiguration == null) {
+ mCompatConfiguration = new Configuration();
+ }
+ mCompatConfiguration.setTo(mConfiguration);
+ if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
+ mResCompatibilityInfo.applyToConfiguration(mCompatConfiguration);
+ config = mCompatConfiguration;
+ }
+ return config;
+ }
+
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
ArrayList<ComponentCallbacks2> callbacks = null;
@@ -3543,14 +3568,7 @@ public final class ActivityThread {
return;
}
mConfiguration.updateFrom(config);
- if (mCompatConfiguration == null) {
- mCompatConfiguration = new Configuration();
- }
- mCompatConfiguration.setTo(mConfiguration);
- if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
- mResCompatibilityInfo.applyToConfiguration(mCompatConfiguration);
- config = mCompatConfiguration;
- }
+ config = applyCompatConfiguration();
callbacks = collectComponentCallbacksLocked(false, config);
}
@@ -3749,6 +3767,7 @@ public final class ActivityThread {
* in AppBindData can be safely assumed to be up to date
*/
applyConfigurationToResourcesLocked(data.config, data.compatInfo);
+ applyCompatConfiguration();
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
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/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2139704..2bf1fb7 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -874,6 +874,7 @@ class ContextImpl extends Context {
try {
String resolvedType = null;
if (fillInIntent != null) {
+ fillInIntent.setAllowFds(false);
resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver());
}
int result = ActivityManagerNative.getDefault()
@@ -892,6 +893,7 @@ class ContextImpl extends Context {
public void sendBroadcast(Intent intent) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
+ intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, false, false);
@@ -903,6 +905,7 @@ class ContextImpl extends Context {
public void sendBroadcast(Intent intent, String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
+ intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, receiverPermission, false, false);
@@ -915,6 +918,7 @@ class ContextImpl extends Context {
String receiverPermission) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
+ intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, receiverPermission, true, false);
@@ -946,6 +950,7 @@ class ContextImpl extends Context {
}
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
+ intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, receiverPermission,
@@ -958,6 +963,7 @@ class ContextImpl extends Context {
public void sendStickyBroadcast(Intent intent) {
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
+ intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, false, true);
@@ -989,6 +995,7 @@ class ContextImpl extends Context {
}
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
+ intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, null,
@@ -1005,6 +1012,7 @@ class ContextImpl extends Context {
intent.setDataAndType(intent.getData(), resolvedType);
}
try {
+ intent.setAllowFds(false);
ActivityManagerNative.getDefault().unbroadcastIntent(
mMainThread.getApplicationThread(), intent);
} catch (RemoteException e) {
@@ -1069,6 +1077,7 @@ class ContextImpl extends Context {
@Override
public ComponentName startService(Intent service) {
try {
+ service.setAllowFds(false);
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()));
@@ -1086,6 +1095,7 @@ class ContextImpl extends Context {
@Override
public boolean stopService(Intent service) {
try {
+ service.setAllowFds(false);
int res = ActivityManagerNative.getDefault().stopService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()));
@@ -1116,6 +1126,7 @@ class ContextImpl extends Context {
< android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
flags |= BIND_WAIVE_PRIORITY;
}
+ service.setAllowFds(false);
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
@@ -1148,6 +1159,9 @@ class ContextImpl extends Context {
public boolean startInstrumentation(ComponentName className,
String profileFile, Bundle arguments) {
try {
+ if (arguments != null) {
+ arguments.setAllowFds(false);
+ }
return ActivityManagerNative.getDefault().startInstrumentation(
className, profileFile, 0, arguments, null);
} catch (RemoteException e) {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 3a08e6d..d423d98 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -162,9 +162,6 @@ final class FragmentState implements Parcelable {
* constructor to instantiate it. If the empty constructor is not available,
* a runtime exception will occur in some cases during state restore.
*
- * <p>For more documentation, also see the <a
- * href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p>
- *
* <p>Topics covered here:
* <ol>
* <li><a href="#OlderPlatforms">Older Platforms</a>
@@ -173,6 +170,12 @@ final class FragmentState implements Parcelable {
* <li><a href="#BackStack">Back Stack</a>
* </ol>
*
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p>
+ * </div>
+ *
* <a name="OlderPlatforms"></a>
* <h3>Older Platforms</h3>
*
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 7a6759f..3da4f29 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -45,6 +45,12 @@ import java.util.Arrays;
/**
* Interface for interacting with {@link Fragment} objects inside of an
* {@link Activity}
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p>
+ * </div>
*/
public abstract class FragmentManager {
/**
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index c1f3cd6..6e99899 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -2,6 +2,12 @@ package android.app;
/**
* API for performing a set of Fragment operations.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> developer guide.</p>
+ * </div>
*/
public abstract class FragmentTransaction {
/**
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/Instrumentation.java b/core/java/android/app/Instrumentation.java
index f3bc495..d7f5c55 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1375,6 +1375,7 @@ public class Instrumentation {
}
}
try {
+ intent.setAllowFds(false);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
@@ -1415,6 +1416,7 @@ public class Instrumentation {
try {
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
+ intents[i].setAllowFds(false);
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
}
int result = ActivityManagerNative.getDefault()
@@ -1471,6 +1473,7 @@ public class Instrumentation {
}
}
try {
+ intent.setAllowFds(false);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 57a2695..96767ae 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -41,6 +41,12 @@ import android.os.Message;
* long as necessary (and will not block the application's main loop), but
* only one request will be processed at a time.
*
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For a detailed discussion about how to create services, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> developer guide.</p>
+ * </div>
+ *
* @see android.os.AsyncTask
*/
public abstract class IntentService extends Service {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 2549c84..522f477 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -661,6 +661,9 @@ public final class LoadedApk {
"Finishing broadcast to unregistered receiver");
IActivityManager mgr = ActivityManagerNative.getDefault();
try {
+ if (extras != null) {
+ extras.setAllowFds(false);
+ }
mgr.finishReceiver(this, resultCode, data, extras, false);
} catch (RemoteException e) {
Slog.w(ActivityThread.TAG, "Couldn't finish broadcast to unregistered receiver");
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 46a008d..89e9ddd 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -47,6 +47,12 @@ import java.lang.reflect.Modifier;
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
* fragment_cursor}
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using loaders, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
+ * </div>
*/
public abstract class LoaderManager {
/**
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index b4827cb..b0637a7 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -192,6 +192,7 @@ public final class PendingIntent implements Parcelable {
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
+ intent.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
IActivityManager.INTENT_SENDER_ACTIVITY, packageName,
@@ -249,6 +250,7 @@ public final class PendingIntent implements Parcelable {
String packageName = context.getPackageName();
String[] resolvedTypes = new String[intents.length];
for (int i=0; i<intents.length; i++) {
+ intents[i].setAllowFds(false);
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver());
}
try {
@@ -287,6 +289,7 @@ public final class PendingIntent implements Parcelable {
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
+ intent.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
IActivityManager.INTENT_SENDER_BROADCAST, packageName,
@@ -324,6 +327,7 @@ public final class PendingIntent implements Parcelable {
String resolvedType = intent != null ? intent.resolveTypeIfNeeded(
context.getContentResolver()) : null;
try {
+ intent.setAllowFds(false);
IIntentSender target =
ActivityManagerNative.getDefault().getIntentSender(
IActivityManager.INTENT_SENDER_SERVICE, packageName,
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index ebde6e0..35bd8c0 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -50,10 +50,6 @@ import java.io.PrintWriter;
* Threads</a>. The {@link IntentService} class is available
* as a standard implementation of Service that has its own thread where it
* schedules its work to be done.</p>
- *
- * <p>You can find a detailed discussion about how to create services in the
- * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a>
- * document.</p>
*
* <p>Topics covered here:
* <ol>
@@ -64,7 +60,13 @@ import java.io.PrintWriter;
* <li><a href="#LocalServiceSample">Local Service Sample</a>
* <li><a href="#RemoteMessengerServiceSample">Remote Messenger Service Sample</a>
* </ol>
- *
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For a detailed discussion about how to create services, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/services.html">Services</a> developer guide.</p>
+ * </div>
+ *
* <a name="WhatIsAService"></a>
* <h3>What is a Service?</h3>
*
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/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index c154296..acdd0b5 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -157,11 +157,15 @@ interface IBackupManager {
* @param allApps If <code>true</code>, the resulting tar stream will include all
* installed applications' data, not just those named in the <code>packageNames</code>
* parameter.
+ * @param allIncludesSystem If {@code true}, then {@code allApps} will be interpreted
+ * as including packages pre-installed as part of the system. If {@code false},
+ * then setting {@code allApps} to {@code true} will mean only that all 3rd-party
+ * applications will be included in the dataset.
* @param packageNames The package names of the apps whose data (and optionally .apk files)
* are to be backed up. The <code>allApps</code> parameter supersedes this.
*/
void fullBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeShared,
- boolean allApps, in String[] packageNames);
+ boolean allApps, boolean allIncludesSystem, in String[] packageNames);
/**
* Restore device content from the data stream passed through the given socket. The
diff --git a/core/java/android/app/backup/WallpaperBackupHelper.java b/core/java/android/app/backup/WallpaperBackupHelper.java
index 0c034cf..170171e 100644
--- a/core/java/android/app/backup/WallpaperBackupHelper.java
+++ b/core/java/android/app/backup/WallpaperBackupHelper.java
@@ -35,7 +35,7 @@ import java.io.File;
*/
public class WallpaperBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "WallpaperBackupHelper";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
// This path must match what the WallpaperManagerService uses
private static final String WALLPAPER_IMAGE = "/data/data/com.android.settings/files/wallpaper";
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 960d7fb..761c7eb 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -16,13 +16,17 @@
package android.appwidget;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -107,6 +111,54 @@ public class AppWidgetHostView extends FrameLayout {
public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
mAppWidgetId = appWidgetId;
mInfo = info;
+
+ // Sometimes the AppWidgetManager returns a null AppWidgetProviderInfo object for
+ // a widget, eg. for some widgets in safe mode.
+ if (info != null) {
+ // We add padding to the AppWidgetHostView if necessary
+ Padding padding = getPaddingForWidget(info.provider);
+ setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ }
+ }
+
+ private static class Padding {
+ int left = 0;
+ int right = 0;
+ int top = 0;
+ int bottom = 0;
+ }
+
+ /**
+ * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting
+ * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend
+ * that widget developers do not add extra padding to their widgets. This will help
+ * achieve consistency among widgets.
+ */
+ private Padding getPaddingForWidget(ComponentName component) {
+ PackageManager packageManager = mContext.getPackageManager();
+ Padding p = new Padding();
+ ApplicationInfo appInfo;
+
+ try {
+ appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
+ } catch (Exception e) {
+ // if we can't find the package, return 0 padding
+ return p;
+ }
+
+ if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ Resources r = getResources();
+ p.left = r.getDimensionPixelSize(com.android.internal.
+ R.dimen.default_app_widget_padding_left);
+ p.right = r.getDimensionPixelSize(com.android.internal.
+ R.dimen.default_app_widget_padding_right);
+ p.top = r.getDimensionPixelSize(com.android.internal.
+ R.dimen.default_app_widget_padding_top);
+ p.bottom = r.getDimensionPixelSize(com.android.internal.
+ R.dimen.default_app_widget_padding_bottom);
+ }
+
+ return p;
}
public int getAppWidgetId() {
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 028149b..d71a8d6 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -90,7 +90,14 @@ import android.util.Slog;
* <li><a href="#Permissions">Permissions</a>
* <li><a href="#ProcessLifecycle">Process Lifecycle</a>
* </ol>
- *
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to use this class to receive and resolve intents, read the
+ * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+ * developer guide.</p>
+ * </div>
+ *
* <a name="ReceiverLifecycle"></a>
* <h3>Receiver Lifecycle</h3>
*
@@ -366,6 +373,9 @@ public abstract class BroadcastReceiver {
mFinished = true;
try {
+ if (mResultExtras != null) {
+ mResultExtras.setAllowFds(false);
+ }
if (mOrderedHint) {
am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
mAbortBroadcast);
@@ -462,6 +472,7 @@ public abstract class BroadcastReceiver {
IActivityManager am = ActivityManagerNative.getDefault();
IBinder binder = null;
try {
+ service.setAllowFds(false);
binder = am.peekService(service, service.resolveTypeIfNeeded(
myContext.getContentResolver()));
} catch (RemoteException e) {
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 abeeb74..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,332 +323,301 @@ 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 {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- int length = 0;
- if (projection != null) {
- length = projection.length;
- }
- data.writeInt(length);
- for (int i = 0; i < length; i++) {
- data.writeString(projection[i]);
- }
- data.writeString(selection);
- if (selectionArgs != null) {
- length = selectionArgs.length;
- } else {
- length = 0;
- }
- data.writeInt(length);
- for (int i = 0; i < length; i++) {
- 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);
-
- mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
+ url.writeToParcel(data, 0);
+ int length = 0;
+ if (projection != null) {
+ length = projection.length;
+ }
+ data.writeInt(length);
+ for (int i = 0; i < length; i++) {
+ data.writeString(projection[i]);
+ }
+ data.writeString(selection);
+ if (selectionArgs != null) {
+ length = selectionArgs.length;
+ } else {
+ length = 0;
+ }
+ data.writeInt(length);
+ for (int i = 0; i < length; i++) {
+ data.writeString(selectionArgs[i]);
+ }
+ data.writeString(sortOrder);
+ data.writeStrongBinder(adaptor.getObserver().asBinder());
- DatabaseUtils.readExceptionFromParcel(reply);
+ mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);
- IBulkCursor bulkCursor = null;
- IBinder bulkCursorBinder = reply.readStrongBinder();
- if (bulkCursorBinder != null) {
- bulkCursor = BulkCursorNative.asInterface(bulkCursorBinder);
+ DatabaseUtils.readExceptionFromParcel(reply);
- if (wantsCursorMetadata) {
+ IBulkCursor bulkCursor = BulkCursorNative.asInterface(reply.readStrongBinder());
+ if (bulkCursor != null) {
int rowCount = reply.readInt();
int idColumnPosition = reply.readInt();
- if (bulkCursor != null) {
- adaptor.set(bulkCursor, rowCount, idColumnPosition);
- }
+ boolean wantsAllOnMoveCalls = reply.readInt() != 0;
+ adaptor.initialize(bulkCursor, rowCount, idColumnPosition, wantsAllOnMoveCalls);
+ } else {
+ adaptor.close();
+ adaptor = null;
}
+ return adaptor;
+ } catch (RemoteException ex) {
+ adaptor.close();
+ throw ex;
+ } catch (RuntimeException ex) {
+ adaptor.close();
+ throw ex;
+ } finally {
+ data.recycle();
+ reply.recycle();
}
-
- data.recycle();
- reply.recycle();
-
- return bulkCursor;
- }
-
- 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();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
-
- mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- String out = reply.readString();
+ url.writeToParcel(data, 0);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.GET_TYPE_TRANSACTION, data, reply, 0);
- return out;
+ DatabaseUtils.readExceptionFromParcel(reply);
+ String out = reply.readString();
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public Uri insert(Uri url, ContentValues values) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- values.writeToParcel(data, 0);
-
- mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- Uri out = Uri.CREATOR.createFromParcel(reply);
+ url.writeToParcel(data, 0);
+ values.writeToParcel(data, 0);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.INSERT_TRANSACTION, data, reply, 0);
- return out;
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Uri out = Uri.CREATOR.createFromParcel(reply);
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public int bulkInsert(Uri url, ContentValues[] values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeTypedArray(values, 0);
-
- mRemote.transact(IContentProvider.BULK_INSERT_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- int count = reply.readInt();
+ url.writeToParcel(data, 0);
+ data.writeTypedArray(values, 0);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.BULK_INSERT_TRANSACTION, data, reply, 0);
- return count;
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int count = reply.readInt();
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
-
- data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInt(operations.size());
- for (ContentProviderOperation operation : operations) {
- operation.writeToParcel(data, 0);
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeInt(operations.size());
+ for (ContentProviderOperation operation : operations) {
+ operation.writeToParcel(data, 0);
+ }
+ mRemote.transact(IContentProvider.APPLY_BATCH_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithOperationApplicationExceptionFromParcel(reply);
+ final ContentProviderResult[] results =
+ reply.createTypedArray(ContentProviderResult.CREATOR);
+ return results;
+ } finally {
+ data.recycle();
+ reply.recycle();
}
- mRemote.transact(IContentProvider.APPLY_BATCH_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionWithOperationApplicationExceptionFromParcel(reply);
- final ContentProviderResult[] results =
- reply.createTypedArray(ContentProviderResult.CREATOR);
-
- data.recycle();
- reply.recycle();
-
- return results;
}
public int delete(Uri url, String selection, String[] selectionArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeString(selection);
- data.writeStringArray(selectionArgs);
-
- mRemote.transact(IContentProvider.DELETE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- int count = reply.readInt();
+ url.writeToParcel(data, 0);
+ data.writeString(selection);
+ data.writeStringArray(selectionArgs);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.DELETE_TRANSACTION, data, reply, 0);
- return count;
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int count = reply.readInt();
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public int update(Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- values.writeToParcel(data, 0);
- data.writeString(selection);
- data.writeStringArray(selectionArgs);
-
- mRemote.transact(IContentProvider.UPDATE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- int count = reply.readInt();
+ url.writeToParcel(data, 0);
+ values.writeToParcel(data, 0);
+ data.writeString(selection);
+ data.writeStringArray(selectionArgs);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.UPDATE_TRANSACTION, data, reply, 0);
- return count;
+ DatabaseUtils.readExceptionFromParcel(reply);
+ int count = reply.readInt();
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeString(mode);
-
- mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
- int has = reply.readInt();
- ParcelFileDescriptor fd = has != 0 ? reply.readFileDescriptor() : null;
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.OPEN_FILE_TRANSACTION, data, reply, 0);
- return fd;
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ ParcelFileDescriptor fd = has != 0 ? reply.readFileDescriptor() : null;
+ return fd;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeString(mode);
-
- mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
- int has = reply.readInt();
- AssetFileDescriptor fd = has != 0
- ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+ url.writeToParcel(data, 0);
+ data.writeString(mode);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.OPEN_ASSET_FILE_TRANSACTION, data, reply, 0);
- return fd;
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+ return fd;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public Bundle call(String method, String request, Bundle args)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- data.writeString(method);
- data.writeString(request);
- data.writeBundle(args);
-
- mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- Bundle bundle = reply.readBundle();
+ data.writeString(method);
+ data.writeString(request);
+ data.writeBundle(args);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.CALL_TRANSACTION, data, reply, 0);
- return bundle;
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Bundle bundle = reply.readBundle();
+ return bundle;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeString(mimeTypeFilter);
-
- mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
- String[] out = reply.createStringArray();
+ url.writeToParcel(data, 0);
+ data.writeString(mimeTypeFilter);
- data.recycle();
- reply.recycle();
+ mRemote.transact(IContentProvider.GET_STREAM_TYPES_TRANSACTION, data, reply, 0);
- return out;
+ DatabaseUtils.readExceptionFromParcel(reply);
+ String[] out = reply.createStringArray();
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
-
- data.writeInterfaceToken(IContentProvider.descriptor);
-
- url.writeToParcel(data, 0);
- data.writeString(mimeType);
- data.writeBundle(opts);
-
- mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
- int has = reply.readInt();
- AssetFileDescriptor fd = has != 0
- ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
-
- data.recycle();
- reply.recycle();
-
- return fd;
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ url.writeToParcel(data, 0);
+ data.writeString(mimeType);
+ data.writeBundle(opts);
+
+ mRemote.transact(IContentProvider.OPEN_TYPED_ASSET_FILE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(reply);
+ int has = reply.readInt();
+ AssetFileDescriptor fd = has != 0
+ ? AssetFileDescriptor.CREATOR.createFromParcel(reply) : null;
+ return fd;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
private IBinder mRemote;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 1e72092..0d25926 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -16,6 +16,8 @@
package android.content;
+import dalvik.system.CloseGuard;
+
import android.accounts.Account;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
@@ -33,6 +35,7 @@ import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.EventLog;
@@ -1562,27 +1565,39 @@ public abstract class ContentResolver {
private final class CursorWrapperInner extends CursorWrapper {
private final IContentProvider mContentProvider;
public static final String TAG="CursorWrapperInner";
- private boolean mCloseFlag = false;
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private boolean mProviderReleased;
CursorWrapperInner(Cursor cursor, IContentProvider icp) {
super(cursor);
mContentProvider = icp;
+ mCloseGuard.open("close");
}
@Override
public void close() {
super.close();
ContentResolver.this.releaseProvider(mContentProvider);
- mCloseFlag = true;
+ mProviderReleased = true;
+
+ if (mCloseGuard != null) {
+ mCloseGuard.close();
+ }
}
@Override
protected void finalize() throws Throwable {
- // TODO: integrate CloseGuard support.
try {
- if(!mCloseFlag) {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+
+ if (!mProviderReleased && mContentProvider != null) {
+ // Even though we are using CloseGuard, log this anyway so that
+ // application developers always see the message in the log.
Log.w(TAG, "Cursor finalized without prior close()");
- close();
+ ContentResolver.this.releaseProvider(mContentProvider);
}
} finally {
super.finalize();
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 6228bd0..7af535b 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -19,6 +19,7 @@ package android.content;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.AsyncTask;
import java.io.FileDescriptor;
import java.io.PrintWriter;
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 f44d038..45a42e4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -55,13 +55,21 @@ import java.util.Set;
* {@link android.content.Context#bindService} to communicate with a
* background {@link android.app.Service}.
*
- * <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. The primary pieces of information in an intent
- * are:</p>
+ * <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>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to create and resolve intents, read the
+ * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <a name="IntentStructure"></a>
+ * <h3>Intent Structure</h3>
+ * <p>The primary pieces of information in an intent are:</p>
*
* <ul>
* <li> <p><b>action</b> -- The general action to be performed, such as
@@ -2558,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).
@@ -3568,6 +3576,13 @@ public class Intent implements Parcelable, Cloneable {
return mExtras != null && mExtras.hasFileDescriptors();
}
+ /** @hide */
+ public void setAllowFds(boolean allowFds) {
+ if (mExtras != null) {
+ mExtras.setAllowFds(allowFds);
+ }
+ }
+
/**
* Retrieve extended data from the intent.
*
@@ -5276,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/IntentFilter.java b/core/java/android/content/IntentFilter.java
index f9b3fd5..3b0d846 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -71,6 +71,14 @@ import java.util.Set;
* To specify a path, you also must specify both one or more authorities and
* one or more schemes it is associated with.
*
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to create and resolve intents, read the
+ * <a href="{@docRoot}guide/topics/intents/intents-filters.html">Intents and Intent Filters</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <h3>Filter Rules</h3>
* <p>A match is based on the following rules. Note that
* for an IntentFilter to match an Intent, three conditions must hold:
* the <strong>action</strong> and <strong>category</strong> must match, and
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 368c33e..b962800 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -40,8 +40,12 @@ import java.io.PrintWriter;
*
* <p>Most implementations should not derive directly from this class, but
* instead inherit from {@link AsyncTaskLoader}.</p>
- * <p>For more information, see the <a
- * href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using loaders, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
+ * </div>
*
* @param <D> The result returned when the load is complete
*/
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 127efa2..4225393 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -1301,7 +1301,7 @@ public class SyncManager implements OnAccountsUpdateListener {
elapsedTime = authoritySyncStats.elapsedTime;
times = authoritySyncStats.times;
- timeStr = String.format("%d/%d%%",
+ timeStr = String.format("%ds/%d%%",
elapsedTime / 1000,
elapsedTime * 100 / totalElapsedTime);
timesStr = String.format("%d/%d%%",
@@ -1309,32 +1309,30 @@ public class SyncManager implements OnAccountsUpdateListener {
times * 100 / totalTimes);
pw.printf(authorityFormat, name, timesStr, timeStr);
- if (authoritySyncStats.accountMap.size() > 1) {
- final List<AccountSyncStats> sortedAccounts =
- new ArrayList<AccountSyncStats>(
- authoritySyncStats.accountMap.values());
- Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() {
- @Override
- public int compare(AccountSyncStats lhs, AccountSyncStats rhs) {
- // reverse order
- int compare = Integer.compare(rhs.times, lhs.times);
- if (compare == 0) {
- compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
- }
- return compare;
+ final List<AccountSyncStats> sortedAccounts =
+ new ArrayList<AccountSyncStats>(
+ authoritySyncStats.accountMap.values());
+ Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() {
+ @Override
+ public int compare(AccountSyncStats lhs, AccountSyncStats rhs) {
+ // reverse order
+ int compare = Integer.compare(rhs.times, lhs.times);
+ if (compare == 0) {
+ compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
}
- });
- for (AccountSyncStats stats: sortedAccounts) {
- elapsedTime = stats.elapsedTime;
- times = stats.times;
- timeStr = String.format("%d/%d%%",
- elapsedTime / 1000,
- elapsedTime * 100 / totalElapsedTime);
- timesStr = String.format("%d/%d%%",
- times,
- times * 100 / totalTimes);
- pw.printf(accountFormat, stats.name, timesStr, timeStr);
+ return compare;
}
+ });
+ for (AccountSyncStats stats: sortedAccounts) {
+ elapsedTime = stats.elapsedTime;
+ times = stats.times;
+ timeStr = String.format("%ds/%d%%",
+ elapsedTime / 1000,
+ elapsedTime * 100 / totalElapsedTime);
+ timesStr = String.format("%d/%d%%",
+ times,
+ times * 100 / totalTimes);
+ pw.printf(accountFormat, stats.name, timesStr, timeStr);
}
pw.println(separator);
}
@@ -1342,9 +1340,8 @@ public class SyncManager implements OnAccountsUpdateListener {
pw.println();
pw.println("Recent Sync History");
final String format = " %-" + maxAccount + "s %s\n";
- String lastAuthorityName = null;
- String lastAccountKey = null;
- long lastEventTime = 0;
+ final Map<String, Long> lastTimeMap = Maps.newHashMap();
+
for (int i = 0; i < N; i++) {
SyncStorageEngine.SyncHistoryItem item = items.get(i);
SyncStorageEngine.AuthorityInfo authority
@@ -1363,21 +1360,32 @@ public class SyncManager implements OnAccountsUpdateListener {
final long eventTime = item.eventTime;
time.set(eventTime);
- pw.printf(" #%-3d: %s %8s %5.1fs",
- i + 1,
- formatTime(eventTime),
- SyncStorageEngine.SOURCES[item.source],
- ((float) elapsedTime) / 1000);
- if (authorityName.equals(lastAuthorityName) && accountKey.equals(lastAccountKey)) {
- final long span = (lastEventTime - eventTime) / 1000;
- pw.printf(" %02d:%02d\n", span / 60, span % 60);
+ final String key = authorityName + "/" + accountKey;
+ final Long lastEventTime = lastTimeMap.get(key);
+ final String diffString;
+ if (lastEventTime == null) {
+ diffString = "";
} else {
- pw.printf(format, accountKey, authorityName);
+ final long diff = (lastEventTime - eventTime) / 1000;
+ if (diff < 60) {
+ diffString = String.valueOf(diff);
+ } else if (diff < 3600) {
+ diffString = String.format("%02d:%02d", diff / 60, diff % 60);
+ } else {
+ final long sec = diff % 3600;
+ diffString = String.format("%02d:%02d:%02d",
+ diff / 3600, sec / 60, sec % 60);
+ }
}
+ lastTimeMap.put(key, eventTime);
- lastAuthorityName = authorityName;
- lastAccountKey = accountKey;
- lastEventTime = eventTime;
+ pw.printf(" #%-3d: %s %8s %5.1fs %8s",
+ i + 1,
+ formatTime(eventTime),
+ SyncStorageEngine.SOURCES[item.source],
+ ((float) elapsedTime) / 1000,
+ diffString);
+ pw.printf(format, accountKey, authorityName);
if (item.event != SyncStorageEngine.EVENT_STOP
|| item.upstreamActivity != 0
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 5fe42db..ee6aec6 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -64,7 +64,10 @@ public abstract class AbstractCursor implements CrossProcessCursor {
/* Methods that may optionally be implemented by subclasses */
/**
- * returns a pre-filled window, return NULL if no such window
+ * If the cursor is backed by a {@link CursorWindow}, returns a pre-filled
+ * window with the contents of the cursor, otherwise null.
+ *
+ * @return The pre-filled window that backs this cursor, or null if none.
*/
public CursorWindow getWindow() {
return null;
@@ -75,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;
@@ -105,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 3d95769..d0aedd2 100644
--- a/core/java/android/database/AbstractWindowedCursor.java
+++ b/core/java/android/database/AbstractWindowedCursor.java
@@ -18,8 +18,27 @@ 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
+ * filled with new data.
+ * </p><p>
+ * If the contents of the cursor change or become invalid, the old window must be closed
+ * (because it is owned by the cursor) and set to null.
+ * </p>
*/
public abstract class AbstractWindowedCursor extends AbstractCursor {
+ /**
+ * The cursor window owned by this cursor.
+ */
+ protected CursorWindow mWindow;
+
@Override
public byte[] getBlob(int columnIndex) {
checkPosition();
@@ -126,25 +145,67 @@ public abstract class AbstractWindowedCursor extends AbstractCursor {
public CursorWindow getWindow() {
return mWindow;
}
-
+
/**
- * Set a new cursor window to cursor, usually set a remote cursor window
- * @param window cursor window
+ * Sets a new cursor window for the cursor to use.
+ * <p>
+ * The cursor takes ownership of the provided cursor window; the cursor window
+ * will be closed when the cursor is closed or when the cursor adopts a new
+ * cursor window.
+ * </p><p>
+ * If the cursor previously had a cursor window, then it is closed when the
+ * new cursor window is assigned.
+ * </p>
+ *
+ * @param window The new cursor window, typically a remote cursor window.
*/
public void setWindow(CursorWindow window) {
- if (mWindow != null) {
- mWindow.close();
+ if (window != mWindow) {
+ closeWindow();
+ mWindow = window;
}
- mWindow = window;
}
-
+
+ /**
+ * Returns true if the cursor has an associated cursor window.
+ *
+ * @return True if the cursor has an associated cursor window.
+ */
public boolean hasWindow() {
return mWindow != null;
}
/**
- * This needs be updated in {@link #onMove} by subclasses, and
- * needs to be set to NULL when the contents of the cursor change.
+ * Closes the cursor window and sets {@link #mWindow} to null.
+ * @hide
*/
- protected CursorWindow mWindow;
+ protected void closeWindow() {
+ if (mWindow != null) {
+ mWindow.close();
+ 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 fa62d69..20a9c67 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -20,12 +20,13 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
/**
* Native implementation of the bulk cursor. This is only for use in implementing
* IPC, application code should use the Cursor interface.
- *
+ *
* {@hide}
*/
public abstract class BulkCursorNative extends Binder implements IBulkCursor
@@ -61,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, 0);
return true;
}
@@ -108,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());
@@ -184,172 +184,171 @@ final class BulkCursorProxy implements IBulkCursor {
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
+ data.writeInt(startPos);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- data.writeInt(startPos);
-
- mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0);
+ mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- CursorWindow window = null;
- if (reply.readInt() == 1) {
- window = CursorWindow.newFromParcel(reply);
+ CursorWindow window = null;
+ if (reply.readInt() == 1) {
+ window = CursorWindow.newFromParcel(reply);
+ }
+ return window;
+ } finally {
+ data.recycle();
+ reply.recycle();
}
-
- data.recycle();
- reply.recycle();
-
- return window;
}
public void onMove(int position) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
+ data.writeInt(position);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- data.writeInt(position);
-
- mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
-
- data.recycle();
- reply.recycle();
+ mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public int count() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);
+ boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- int count;
- if (result == false) {
- count = -1;
- } else {
- count = reply.readInt();
+ int count;
+ if (result == false) {
+ count = -1;
+ } else {
+ count = reply.readInt();
+ }
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
}
- data.recycle();
- reply.recycle();
- return count;
}
public String[] getColumnNames() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0);
+ mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- String[] columnNames = null;
- int numColumns = reply.readInt();
- columnNames = new String[numColumns];
- for (int i = 0; i < numColumns; i++) {
- columnNames[i] = reply.readString();
+ String[] columnNames = null;
+ int numColumns = reply.readInt();
+ columnNames = new String[numColumns];
+ for (int i = 0; i < numColumns; i++) {
+ columnNames[i] = reply.readString();
+ }
+ return columnNames;
+ } finally {
+ data.recycle();
+ reply.recycle();
}
-
- data.recycle();
- reply.recycle();
- return columnNames;
}
public void deactivate() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- mRemote.transact(DEACTIVATE_TRANSACTION, data, reply, 0);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- data.recycle();
- reply.recycle();
+ mRemote.transact(DEACTIVATE_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public void close() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- mRemote.transact(CLOSE_TRANSACTION, data, reply, 0);
- DatabaseUtils.readExceptionFromParcel(reply);
-
- data.recycle();
- reply.recycle();
+ mRemote.transact(CLOSE_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
- 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);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- data.writeStrongInterface(observer);
- window.writeToParcel(data, 0);
-
- boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
+ boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
- int count;
- if (!result) {
- count = -1;
- } else {
- count = reply.readInt();
- mExtras = reply.readBundle();
+ int count;
+ if (!result) {
+ count = -1;
+ } else {
+ count = reply.readInt();
+ mExtras = reply.readBundle();
+ }
+ return count;
+ } finally {
+ data.recycle();
+ reply.recycle();
}
-
- data.recycle();
- reply.recycle();
-
- return count;
}
public boolean getWantsAllOnMoveCalls() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
+ mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
- int result = reply.readInt();
- data.recycle();
- reply.recycle();
- return result != 0;
+ int result = reply.readInt();
+ return result != 0;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
public Bundle getExtras() throws RemoteException {
if (mExtras == null) {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
- data.writeInterfaceToken(IBulkCursor.descriptor);
+ mRemote.transact(GET_EXTRAS_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
- mRemote.transact(GET_EXTRAS_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
-
- mExtras = reply.readBundle();
- data.recycle();
- reply.recycle();
+ mExtras = reply.readBundle();
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
return mExtras;
}
@@ -357,19 +356,19 @@ final class BulkCursorProxy implements IBulkCursor {
public Bundle respond(Bundle extras) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IBulkCursor.descriptor);
+ data.writeBundle(extras);
- data.writeInterfaceToken(IBulkCursor.descriptor);
-
- data.writeBundle(extras);
-
- mRemote.transact(RESPOND_TRANSACTION, data, reply, 0);
-
- DatabaseUtils.readExceptionFromParcel(reply);
+ mRemote.transact(RESPOND_TRANSACTION, data, reply, 0);
+ DatabaseUtils.readExceptionFromParcel(reply);
- Bundle returnExtras = reply.readBundle();
- data.recycle();
- reply.recycle();
- return returnExtras;
+ Bundle returnExtras = reply.readBundle();
+ return returnExtras;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
}
}
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index 16becf5..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,38 +115,39 @@ 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;
- if (mWindow != null) {
- mWindow.close();
- mWindow = null;
- }
+ closeWindow();
// super.requery() will call onChanged. Do it here instead of relying on the
// observer from the far side so that observers can see a correct value for mCount
@@ -177,6 +167,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
@Override
public String[] getColumnNames() {
+ throwIfCursorIsClosed();
+
if (mColumns == null) {
try {
mColumns = mBulkCursor.getColumnNames();
@@ -190,6 +182,8 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
@Override
public Bundle getExtras() {
+ throwIfCursorIsClosed();
+
try {
return mBulkCursor.getExtras();
} catch (RemoteException e) {
@@ -201,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 f7cbf7a..a18a721 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -16,10 +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;
@@ -28,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";
@@ -39,538 +48,648 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
Resources.getSystem().getInteger(
com.android.internal.R.integer.config_cursorWindowSize) * 1024;
- /** The pointer to the native window class. set by the native methods in
- * android_database_CursorWindow.cpp
+ /**
+ * The native CursorWindow object pointer. (FOR INTERNAL USE ONLY)
+ * @hide
*/
- private int nWindow;
+ public int mWindowPtr;
private int mStartPos;
- /**
- * Creates a new empty window.
+ 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 void nativeWriteToParcel(int windowPtr, Parcel parcel);
+
+ private static native void nativeClear(int windowPtr);
+
+ private static native int nativeGetNumRows(int windowPtr);
+ private static native boolean nativeSetNumColumns(int windowPtr, int columnNum);
+ private static native boolean nativeAllocRow(int windowPtr);
+ private static native void nativeFreeLastRow(int windowPtr);
+
+ private static native int nativeGetType(int windowPtr, int row, int column);
+ private static native byte[] nativeGetBlob(int windowPtr, int row, int column);
+ private static native String nativeGetString(int windowPtr, int row, int column);
+ private static native long nativeGetLong(int windowPtr, int row, int column);
+ private static native double nativeGetDouble(int windowPtr, int row, int column);
+ private static native void nativeCopyStringToBuffer(int windowPtr, int row, int column,
+ CharArrayBuffer buffer);
+
+ private static native boolean nativePutBlob(int windowPtr, byte[] value, int row, int column);
+ private static native boolean nativePutString(int windowPtr, String value, int row, int column);
+ private static native boolean nativePutLong(int windowPtr, long value, int row, int column);
+ private static native boolean nativePutDouble(int windowPtr, double value, int row, int column);
+ private static native boolean nativePutNull(int windowPtr, int row, int column);
+
+ /**
+ * 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 localWindow true if this window will be used in this process only
+ * @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;
- int rslt = native_init(sCursorWindowSize, localWindow);
- printDebugMsgIfError(rslt);
- recordNewWindow(Binder.getCallingPid(), nWindow);
- }
-
- private void printDebugMsgIfError(int rslt) {
- if (rslt > 0) {
- // cursor window allocation failed. either low memory or too many cursors being open.
- // print info to help in debugging this.
- throw new CursorWindowAllocationException("Cursor Window allocation of " +
- sCursorWindowSize/1024 + " kb failed. " + printStats());
+ 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);
}
/**
- * Returns the starting position of this window within the entire
- * Cursor's result set.
+ * 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>
*
- * @return the starting position of this window within the entire
- * Cursor's result set.
+ * @param localWindow True if this window will be used in this process only,
+ * false if it might be sent to another processes.
*/
- public int getStartPosition() {
- return mStartPos;
+ public CursorWindow(boolean localWindow) {
+ this(null, localWindow);
}
- /**
- * Set the start position of cursor window
- * @param pos
- */
- public void setStartPosition(int pos) {
- mStartPos = pos;
- }
-
- /**
- * Returns the number of rows in this window.
- *
- * @return the number of rows in this window.
- */
- public int getNumRows() {
- acquireReference();
- try {
- return getNumRows_native();
- } finally {
- releaseReference();
+ private CursorWindow(Parcel source) {
+ mStartPos = source.readInt();
+ mWindowPtr = nativeCreateFromParcel(source);
+ if (mWindowPtr == 0) {
+ throw new CursorWindowAllocationException("Cursor window could not be "
+ + "created from binder.");
}
+ mCloseGuard.open("close");
}
-
- private native int getNumRows_native();
- /**
- * Set number of Columns
- * @param columnNum
- * @return true if success
- */
- public boolean setNumColumns(int columnNum) {
- acquireReference();
+
+ @Override
+ protected void finalize() throws Throwable {
try {
- return setNumColumns_native(columnNum);
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ dispose();
} finally {
- releaseReference();
+ super.finalize();
}
}
-
- private native boolean setNumColumns_native(int columnNum);
-
+
+ private void dispose() {
+ if (mCloseGuard != null) {
+ mCloseGuard.close();
+ }
+ if (mWindowPtr != 0) {
+ recordClosingOfWindow(mWindowPtr);
+ nativeDispose(mWindowPtr);
+ mWindowPtr = 0;
+ }
+ }
+
/**
- * Allocate a row in cursor window
- * @return false if cursor window is out of memory
+ * Closes the cursor window and frees its underlying resources when all other
+ * remaining references have been released.
*/
- public boolean allocRow(){
- acquireReference();
- try {
- return allocRow_native();
- } finally {
- releaseReference();
- }
+ public void close() {
+ releaseReference();
}
-
- private native boolean allocRow_native();
-
+
/**
- * Free the last row
+ * Clears out the existing contents of the window, making it safe to reuse
+ * for new data.
+ * <p>
+ * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}),
+ * and number of columns in the cursor are all reset to zero.
+ * </p>
*/
- public void freeLastRow(){
+ public void clear() {
acquireReference();
try {
- freeLastRow_native();
+ mStartPos = 0;
+ nativeClear(mWindowPtr);
} finally {
releaseReference();
}
}
-
- private native void freeLastRow_native();
/**
- * copy byte array to cursor window
- * @param value
- * @param row
- * @param col
- * @return false if fail to copy
+ * Gets the start position of this cursor window.
+ * <p>
+ * The start position is the zero-based index of the first row that this window contains
+ * relative to the entire result set of the {@link Cursor}.
+ * </p>
+ *
+ * @return The zero-based start position.
*/
- public boolean putBlob(byte[] value, int row, int col) {
- acquireReference();
- try {
- return putBlob_native(value, row - mStartPos, col);
- } finally {
- releaseReference();
- }
+ public int getStartPosition() {
+ return mStartPos;
}
-
- private native boolean putBlob_native(byte[] value, int row, int col);
/**
- * Copy String to cursor window
- * @param value
- * @param row
- * @param col
- * @return false if fail to copy
+ * Sets the start position of this cursor window.
+ * <p>
+ * The start position is the zero-based index of the first row that this window contains
+ * relative to the entire result set of the {@link Cursor}.
+ * </p>
+ *
+ * @param pos The new zero-based start position.
*/
- public boolean putString(String value, int row, int col) {
- acquireReference();
- try {
- return putString_native(value, row - mStartPos, col);
- } finally {
- releaseReference();
- }
+ public void setStartPosition(int pos) {
+ mStartPos = pos;
}
-
- private native boolean putString_native(String value, int row, int col);
-
+
/**
- * Copy integer to cursor window
- * @param value
- * @param row
- * @param col
- * @return false if fail to copy
+ * Gets the number of rows in this window.
+ *
+ * @return The number of rows in this cursor window.
*/
- public boolean putLong(long value, int row, int col) {
+ public int getNumRows() {
acquireReference();
try {
- return putLong_native(value, row - mStartPos, col);
+ return nativeGetNumRows(mWindowPtr);
} finally {
releaseReference();
}
}
-
- private native boolean putLong_native(long value, int row, int col);
-
/**
- * Copy double to cursor window
- * @param value
- * @param row
- * @param col
- * @return false if fail to copy
+ * Sets the number of columns in this window.
+ * <p>
+ * This method must be called before any rows are added to the window, otherwise
+ * it will fail to set the number of columns if it differs from the current number
+ * of columns.
+ * </p>
+ *
+ * @param columnNum The new number of columns.
+ * @return True if successful.
*/
- public boolean putDouble(double value, int row, int col) {
+ public boolean setNumColumns(int columnNum) {
acquireReference();
try {
- return putDouble_native(value, row - mStartPos, col);
+ return nativeSetNumColumns(mWindowPtr, columnNum);
} finally {
releaseReference();
}
}
-
- private native boolean putDouble_native(double value, int row, int col);
/**
- * Set the [row, col] value to NULL
- * @param row
- * @param col
- * @return false if fail to copy
+ * Allocates a new row at the end of this cursor window.
+ *
+ * @return True if successful, false if the cursor window is out of memory.
*/
- public boolean putNull(int row, int col) {
+ public boolean allocRow(){
acquireReference();
try {
- return putNull_native(row - mStartPos, col);
+ return nativeAllocRow(mWindowPtr);
} finally {
releaseReference();
}
}
-
- private native boolean putNull_native(int row, int col);
-
/**
- * Returns {@code true} if given field is {@code NULL}.
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return {@code true} if given field is {@code NULL}
- * @deprecated use {@link #getType(int, int)} instead
- */
- @Deprecated
- public boolean isNull(int row, int col) {
- return getType(row, col) == Cursor.FIELD_TYPE_NULL;
- }
-
- /**
- * Returns a byte array for the given field.
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return a String value for the given field
+ * Frees the last row in this cursor window.
*/
- public byte[] getBlob(int row, int col) {
+ public void freeLastRow(){
acquireReference();
try {
- return getBlob_native(row - mStartPos, col);
+ nativeFreeLastRow(mWindowPtr);
} finally {
releaseReference();
}
}
/**
- * Returns the value at (<code>row</code>, <code>col</code>) as a <code>byte</code> array.
- *
- * <p>If the value is null, then <code>null</code> is returned. If the
- * type of column <code>col</code> is a string type, then the result
- * is the array of bytes that make up the internal representation of the
- * string value. If the type of column <code>col</code> is integral or floating-point,
- * then an {@link SQLiteException} is thrown.
- */
- private native byte[] getBlob_native(int row, int col);
-
- /**
- * Returns data type of the given column's value.
- *<p>
- * Returned column types are
- * <ul>
- * <li>{@link Cursor#FIELD_TYPE_NULL}</li>
- * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
- * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
- * <li>{@link Cursor#FIELD_TYPE_STRING}</li>
- * <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
- *</ul>
- *</p>
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_NULL}.
*
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return the value type
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}.
+ * @deprecated Use {@link #getType(int, int)} instead.
*/
- public int getType(int row, int col) {
- acquireReference();
- try {
- return getType_native(row - mStartPos, col);
- } finally {
- releaseReference();
- }
+ @Deprecated
+ public boolean isNull(int row, int column) {
+ return getType(row, column) == Cursor.FIELD_TYPE_NULL;
}
/**
- * Checks if a field contains either a blob or is null.
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}.
*
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return {@code true} if given field is {@code NULL} or a blob
- * @deprecated use {@link #getType(int, int)} instead
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or
+ * {@link Cursor#FIELD_TYPE_NULL}.
+ * @deprecated Use {@link #getType(int, int)} instead.
*/
@Deprecated
- public boolean isBlob(int row, int col) {
- int type = getType(row, col);
+ public boolean isBlob(int row, int column) {
+ int type = getType(row, column);
return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL;
}
/**
- * Checks if a field contains a long
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_INTEGER}.
*
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return {@code true} if given field is a long
- * @deprecated use {@link #getType(int, int)} instead
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}.
+ * @deprecated Use {@link #getType(int, int)} instead.
*/
@Deprecated
- public boolean isLong(int row, int col) {
- return getType(row, col) == Cursor.FIELD_TYPE_INTEGER;
+ public boolean isLong(int row, int column) {
+ return getType(row, column) == Cursor.FIELD_TYPE_INTEGER;
}
/**
- * Checks if a field contains a float.
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_FLOAT}.
*
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return {@code true} if given field is a float
- * @deprecated use {@link #getType(int, int)} instead
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}.
+ * @deprecated Use {@link #getType(int, int)} instead.
*/
@Deprecated
- public boolean isFloat(int row, int col) {
- return getType(row, col) == Cursor.FIELD_TYPE_FLOAT;
+ public boolean isFloat(int row, int column) {
+ return getType(row, column) == Cursor.FIELD_TYPE_FLOAT;
}
/**
- * Checks if a field contains either a String or is null.
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}.
*
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return {@code true} if given field is {@code NULL} or a String
- * @deprecated use {@link #getType(int, int)} instead
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING}
+ * or {@link Cursor#FIELD_TYPE_NULL}.
+ * @deprecated Use {@link #getType(int, int)} instead.
*/
@Deprecated
- public boolean isString(int row, int col) {
- int type = getType(row, col);
+ public boolean isString(int row, int column) {
+ int type = getType(row, column);
return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL;
}
- private native int getType_native(int row, int col);
+ /**
+ * Returns the type of the field at the specified row and column index.
+ * <p>
+ * The returned field types are:
+ * <ul>
+ * <li>{@link Cursor#FIELD_TYPE_NULL}</li>
+ * <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
+ * <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
+ * <li>{@link Cursor#FIELD_TYPE_STRING}</li>
+ * <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
+ * </ul>
+ * </p>
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The field type.
+ */
+ public int getType(int row, int column) {
+ acquireReference();
+ try {
+ return nativeGetType(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
/**
- * Returns a String for the given field.
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return a String value for the given field
+ * Gets the value of the field at the specified row and column index as a byte array.
+ * <p>
+ * The result is determined as follows:
+ * <ul>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is <code>null</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result
+ * is the blob value.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the array of bytes that make up the internal representation of the
+ * string value.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or
+ * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.</li>
+ * </ul>
+ * </p>
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The value of the field as a byte array.
*/
- public String getString(int row, int col) {
+ public byte[] getBlob(int row, int column) {
acquireReference();
try {
- return getString_native(row - mStartPos, col);
+ return nativeGetBlob(mWindowPtr, row - mStartPos, column);
} finally {
releaseReference();
}
}
-
+
/**
- * Returns the value at (<code>row</code>, <code>col</code>) as a <code>String</code>.
+ * Gets the value of the field at the specified row and column index as a string.
+ * <p>
+ * The result is determined as follows:
+ * <ul>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is <code>null</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the string value.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is a string representation of the integer in decimal, obtained by formatting the
+ * value with the <code>printf</code> family of functions using
+ * format specifier <code>%lld</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is a string representation of the floating-point value in decimal, obtained by
+ * formatting the value with the <code>printf</code> family of functions using
+ * format specifier <code>%g</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.</li>
+ * </ul>
+ * </p>
*
- * <p>If the value is null, then <code>null</code> is returned. If the
- * type of column <code>col</code> is integral, then the result is the string
- * that is obtained by formatting the integer value with the <code>printf</code>
- * family of functions using format specifier <code>%lld</code>. If the
- * type of column <code>col</code> is floating-point, then the result is the string
- * that is obtained by formatting the floating-point value with the
- * <code>printf</code> family of functions using format specifier <code>%g</code>.
- * If the type of column <code>col</code> is a blob type, then an
- * {@link SQLiteException} is thrown.
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The value of the field as a string.
*/
- private native String getString_native(int row, int col);
+ public String getString(int row, int column) {
+ acquireReference();
+ try {
+ return nativeGetString(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
/**
- * copy the text for the given field in the provided char array.
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @param buffer the CharArrayBuffer to copy the text into,
- * If the requested string is larger than the buffer
- * a new char buffer will be created to hold the string. and assigne to
- * CharArrayBuffer.data
+ * Copies the text of the field at the specified row and column index into
+ * a {@link CharArrayBuffer}.
+ * <p>
+ * The buffer is populated as follows:
+ * <ul>
+ * <li>If the buffer is too small for the value to be copied, then it is
+ * automatically resized.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer
+ * is set to an empty string.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer
+ * is set to the contents of the string.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer
+ * is set to a string representation of the integer in decimal, obtained by formatting the
+ * value with the <code>printf</code> family of functions using
+ * format specifier <code>%lld</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is
+ * set to a string representation of the floating-point value in decimal, obtained by
+ * formatting the value with the <code>printf</code> family of functions using
+ * format specifier <code>%g</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.</li>
+ * </ul>
+ * </p>
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically
+ * resized if the requested string is larger than the buffer's current capacity.
*/
- public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) {
+ public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) {
if (buffer == null) {
throw new IllegalArgumentException("CharArrayBuffer should not be null");
}
- if (buffer.data == null) {
- buffer.data = new char[64];
- }
acquireReference();
try {
- char[] newbuf = copyStringToBuffer_native(
- row - mStartPos, col, buffer.data.length, buffer);
- if (newbuf != null) {
- buffer.data = newbuf;
- }
+ nativeCopyStringToBuffer(mWindowPtr, row, column, buffer);
} finally {
releaseReference();
}
}
-
- private native char[] copyStringToBuffer_native(
- int row, int col, int bufferSize, CharArrayBuffer buffer);
-
+
/**
- * Returns a long for the given field.
- * row is 0 based
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return a long value for the given field
+ * Gets the value of the field at the specified row and column index as a <code>long</code>.
+ * <p>
+ * The result is determined as follows:
+ * <ul>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is <code>0L</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the value obtained by parsing the string value with <code>strtoll</code>.
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is the <code>long</code> value.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is the floating-point value converted to a <code>long</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.</li>
+ * </ul>
+ * </p>
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The value of the field as a <code>long</code>.
*/
- public long getLong(int row, int col) {
+ public long getLong(int row, int column) {
acquireReference();
try {
- return getLong_native(row - mStartPos, col);
+ return nativeGetLong(mWindowPtr, row - mStartPos, column);
} finally {
releaseReference();
}
}
-
- /**
- * Returns the value at (<code>row</code>, <code>col</code>) as a <code>long</code>.
- *
- * <p>If the value is null, then <code>0L</code> is returned. If the
- * type of column <code>col</code> is a string type, then the result
- * is the <code>long</code> that is obtained by parsing the string value with
- * <code>strtoll</code>. If the type of column <code>col</code> is
- * floating-point, then the result is the floating-point value casted to a <code>long</code>.
- * If the type of column <code>col</code> is a blob type, then an
- * {@link SQLiteException} is thrown.
- */
- private native long getLong_native(int row, int col);
/**
- * Returns a double for the given field.
- * row is 0 based
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return a double value for the given field
+ * Gets the value of the field at the specified row and column index as a
+ * <code>double</code>.
+ * <p>
+ * The result is determined as follows:
+ * <ul>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is <code>0.0</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the value obtained by parsing the string value with <code>strtod</code>.
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is the integer value converted to a <code>double</code>.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is the <code>double</code> value.</li>
+ * <li>If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.</li>
+ * </ul>
+ * </p>
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The value of the field as a <code>double</code>.
*/
- public double getDouble(int row, int col) {
+ public double getDouble(int row, int column) {
acquireReference();
try {
- return getDouble_native(row - mStartPos, col);
+ return nativeGetDouble(mWindowPtr, row - mStartPos, column);
} finally {
releaseReference();
}
}
-
+
+ /**
+ * Gets the value of the field at the specified row and column index as a
+ * <code>short</code>.
+ * <p>
+ * The result is determined by invoking {@link #getLong} and converting the
+ * result to <code>short</code>.
+ * </p>
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The value of the field as a <code>short</code>.
+ */
+ public short getShort(int row, int column) {
+ return (short) getLong(row, column);
+ }
+
/**
- * Returns the value at (<code>row</code>, <code>col</code>) as a <code>double</code>.
+ * Gets the value of the field at the specified row and column index as an
+ * <code>int</code>.
+ * <p>
+ * The result is determined by invoking {@link #getLong} and converting the
+ * result to <code>int</code>.
+ * </p>
*
- * <p>If the value is null, then <code>0.0</code> is returned. If the
- * type of column <code>col</code> is a string type, then the result
- * is the <code>double</code> that is obtained by parsing the string value with
- * <code>strtod</code>. If the type of column <code>col</code> is
- * integral, then the result is the integer value casted to a <code>double</code>.
- * If the type of column <code>col</code> is a blob type, then an
- * {@link SQLiteException} is thrown.
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The value of the field as an <code>int</code>.
*/
- private native double getDouble_native(int row, int col);
+ public int getInt(int row, int column) {
+ return (int) getLong(row, column);
+ }
/**
- * Returns a short for the given field.
- * row is 0 based
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return a short value for the given field
+ * Gets the value of the field at the specified row and column index as a
+ * <code>float</code>.
+ * <p>
+ * The result is determined by invoking {@link #getDouble} and converting the
+ * result to <code>float</code>.
+ * </p>
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return The value of the field as an <code>float</code>.
*/
- public short getShort(int row, int col) {
+ public float getFloat(int row, int column) {
+ return (float) getDouble(row, column);
+ }
+
+ /**
+ * Copies a byte array into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putBlob(byte[] value, int row, int column) {
acquireReference();
try {
- return (short) getLong_native(row - mStartPos, col);
+ return nativePutBlob(mWindowPtr, value, row - mStartPos, column);
} finally {
releaseReference();
}
}
/**
- * Returns an int for the given field.
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return an int value for the given field
+ * Copies a string into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if successful.
*/
- public int getInt(int row, int col) {
+ public boolean putString(String value, int row, int column) {
acquireReference();
try {
- return (int) getLong_native(row - mStartPos, col);
+ return nativePutString(mWindowPtr, value, row - mStartPos, column);
} finally {
releaseReference();
}
}
-
+
/**
- * Returns a float for the given field.
- * row is 0 based
- *
- * @param row the row to read from, row - getStartPosition() being the actual row in the window
- * @param col the column to read from
- * @return a float value for the given field
+ * Puts a long integer into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if successful.
*/
- public float getFloat(int row, int col) {
+ public boolean putLong(long value, int row, int column) {
acquireReference();
try {
- return (float) getDouble_native(row - mStartPos, col);
+ return nativePutLong(mWindowPtr, value, row - mStartPos, column);
} finally {
releaseReference();
}
- }
-
+ }
+
/**
- * Clears out the existing contents of the window, making it safe to reuse
- * for new data. Note that the number of columns in the window may NOT
- * change across a call to clear().
+ * Puts a double-precision floating point value into the field at the
+ * specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if successful.
*/
- public void clear() {
+ public boolean putDouble(double value, int row, int column) {
acquireReference();
try {
- mStartPos = 0;
- native_clear();
+ return nativePutDouble(mWindowPtr, value, row - mStartPos, column);
} finally {
releaseReference();
}
}
- /** Clears out the native side of things */
- private native void native_clear();
-
/**
- * Cleans up the native resources associated with the window.
+ * Puts a null value into the field at the specified row and column index.
+ *
+ * @param row The zero-based row index, relative to the cursor window's
+ * start position ({@link #getStartPosition()}).
+ * @param column The zero-based column index.
+ * @return True if successful.
*/
- public void close() {
- releaseReference();
- }
-
- private native void close_native();
-
- @Override
- protected void finalize() {
- if (nWindow == 0) {
- return;
+ public boolean putNull(int row, int column) {
+ acquireReference();
+ try {
+ return nativePutNull(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
}
- // due to bugs 3329504, 3502276, cursorwindow sometimes is closed in fialize()
- // don't print any warning saying "don't release cursor in finzlize"
- // because it is a bug in framework code - NOT an app bug.
- recordClosingOfWindow(nWindow);
- close_native();
}
-
+
public static final Parcelable.Creator<CursorWindow> CREATOR
= new Parcelable.Creator<CursorWindow>() {
public CursorWindow createFromParcel(Parcel source) {
@@ -591,30 +710,17 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeStrongBinder(native_getBinder());
dest.writeInt(mStartPos);
- }
+ nativeWriteToParcel(mWindowPtr, dest);
- private CursorWindow(Parcel source) {
- IBinder nativeBinder = source.readStrongBinder();
- mStartPos = source.readInt();
- int rslt = native_init(nativeBinder);
- printDebugMsgIfError(rslt);
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ releaseReference();
+ }
}
- /** Get the binder for the native side of the window */
- private native IBinder native_getBinder();
-
- /** Does the native side initialization for an empty window */
- private native int native_init(int cursorWindowSize, boolean localOnly);
-
- /** Does the native side initialization with an existing binder from another process */
- private native int native_init(IBinder nativeBinder);
-
@Override
protected void onAllReferencesReleased() {
- recordClosingOfWindow(nWindow);
- close_native();
+ dispose();
}
private static final SparseIntArray sWindowToPidMap = new SparseIntArray();
@@ -637,6 +743,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
sWindowToPidMap.delete(window);
}
}
+
private String printStats() {
StringBuilder buff = new StringBuilder();
int myPid = Process.myPid();
diff --git a/core/java/android/database/CursorWindowAllocationException.java b/core/java/android/database/CursorWindowAllocationException.java
index ba7df68..2e3227d 100644
--- a/core/java/android/database/CursorWindowAllocationException.java
+++ b/core/java/android/database/CursorWindowAllocationException.java
@@ -18,17 +18,12 @@ package android.database;
/**
* This exception is thrown when a CursorWindow couldn't be allocated,
- * most probably due to memory not being available
+ * most probably due to memory not being available.
+ *
+ * @hide
*/
-class CursorWindowAllocationException extends java.lang.RuntimeException
-{
- public CursorWindowAllocationException()
- {
- super();
- }
-
- public CursorWindowAllocationException(String description)
- {
+public class CursorWindowAllocationException extends RuntimeException {
+ public CursorWindowAllocationException(String description) {
super(description);
}
}
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 3c3bd43..320733e 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -33,7 +33,9 @@ public class CursorWrapper implements Cursor {
}
/**
- * @return the wrapped cursor
+ * Gets the underlying cursor that is wrapped by this instance.
+ *
+ * @return The wrapped cursor.
*/
public Cursor getWrappedCursor() {
return mCursor;
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 ea9346d..a1c36e2 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -18,16 +18,11 @@ package android.database.sqlite;
import android.database.AbstractWindowedCursor;
import android.database.CursorWindow;
-import android.database.DataSetObserver;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Process;
import android.os.StrictMode;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.locks.ReentrantLock;
/**
* A Cursor implementation that exposes results from a query on a
@@ -60,140 +55,8 @@ public class SQLiteCursor extends AbstractWindowedCursor {
/** Used to find out where a cursor was allocated in case it never got released. */
private final Throwable mStackTrace;
-
- /**
- * mMaxRead is the max items that each cursor window reads
- * default to a very high value
- */
- private int mMaxRead = Integer.MAX_VALUE;
- private int mInitialRead = Integer.MAX_VALUE;
- private int mCursorState = 0;
- private ReentrantLock mLock = null;
- private boolean mPendingData = false;
/**
- * support for a cursor variant that doesn't always read all results
- * initialRead is the initial number of items that cursor window reads
- * if query contains more than this number of items, a thread will be
- * created and handle the left over items so that caller can show
- * results as soon as possible
- * @param initialRead initial number of items that cursor read
- * @param maxRead leftover items read at maxRead items per time
- * @hide
- */
- public void setLoadStyle(int initialRead, int maxRead) {
- mMaxRead = maxRead;
- mInitialRead = initialRead;
- mLock = new ReentrantLock(true);
- }
-
- private void queryThreadLock() {
- if (mLock != null) {
- mLock.lock();
- }
- }
-
- private void queryThreadUnlock() {
- if (mLock != null) {
- mLock.unlock();
- }
- }
-
-
- /**
- * @hide
- */
- final private class QueryThread implements Runnable {
- private final int mThreadState;
- QueryThread(int version) {
- mThreadState = version;
- }
- private void sendMessage() {
- if (mNotificationHandler != null) {
- mNotificationHandler.sendEmptyMessage(1);
- mPendingData = false;
- } else {
- mPendingData = true;
- }
-
- }
- public void run() {
- // use cached mWindow, to avoid get null mWindow
- CursorWindow cw = mWindow;
- Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND);
- // the cursor's state doesn't change
- while (true) {
- mLock.lock();
- try {
- if (mCursorState != mThreadState) {
- break;
- }
-
- int count = getQuery().fillWindow(cw, mMaxRead, mCount);
- // return -1 means there is still more data to be retrieved from the resultset
- if (count != 0) {
- if (count == NO_COUNT){
- mCount += mMaxRead;
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "received -1 from native_fill_window. read " +
- mCount + " rows so far");
- }
- sendMessage();
- } else {
- mCount += count;
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "received all data from native_fill_window. read " +
- mCount + " rows.");
- }
- sendMessage();
- break;
- }
- } else {
- break;
- }
- } catch (Exception e) {
- // end the tread when the cursor is close
- break;
- } finally {
- mLock.unlock();
- }
- }
- }
- }
-
- /**
- * @hide
- */
- protected class MainThreadNotificationHandler extends Handler {
- public void handleMessage(Message msg) {
- notifyDataSetChange();
- }
- }
-
- /**
- * @hide
- */
- protected MainThreadNotificationHandler mNotificationHandler;
-
- public void registerDataSetObserver(DataSetObserver observer) {
- super.registerDataSetObserver(observer);
- if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) &&
- mNotificationHandler == null) {
- queryThreadLock();
- try {
- mNotificationHandler = new MainThreadNotificationHandler();
- if (mPendingData) {
- notifyDataSetChange();
- mPendingData = false;
- }
- } finally {
- queryThreadUnlock();
- }
- }
-
- }
-
- /**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
@@ -226,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");
}
@@ -293,36 +154,18 @@ public class SQLiteCursor extends AbstractWindowedCursor {
return mCount;
}
- 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 {
- mCursorState++;
- queryThreadLock();
- try {
- mWindow.clear();
- } finally {
- queryThreadUnlock();
- }
- }
+ private void fillWindow(int startPos) {
+ clearOrCreateLocalWindow(getDatabase().getPath());
mWindow.setStartPosition(startPos);
- int count = getQuery().fillWindow(mWindow, mInitialRead, 0);
- // return -1 means there is still more data to be retrieved from the resultset
- if (count == NO_COUNT){
- mCount = startPos + mInitialRead;
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "received -1 from native_fill_window. read " + mCount + " rows so far");
- }
- Thread t = new Thread(new QueryThread(mCursorState), "query thread");
- t.start();
- } else if (startPos == 0) { // native_fill_window returns count(*) only for startPos = 0
+ int count = getQuery().fillWindow(mWindow);
+ if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + count);
}
mCount = count;
} else if (mCount <= 0) {
- throw new IllegalStateException("count should never be non-zero negative number");
+ throw new IllegalStateException("Row count should never be zero or negative "
+ + "when the start position is non-zero");
}
}
@@ -364,20 +207,9 @@ public class SQLiteCursor extends AbstractWindowedCursor {
return mColumns;
}
- private void deactivateCommon() {
- if (false) Log.v(TAG, "<<< Releasing cursor " + this);
- mCursorState = 0;
- if (mWindow != null) {
- mWindow.close();
- mWindow = null;
- }
- if (false) Log.v("DatabaseWindow", "closing window in release()");
- }
-
@Override
public void deactivate() {
super.deactivate();
- deactivateCommon();
mDriver.cursorDeactivated();
}
@@ -385,7 +217,6 @@ public class SQLiteCursor extends AbstractWindowedCursor {
public void close() {
super.close();
synchronized (this) {
- deactivateCommon();
mQuery.close();
mDriver.cursorClosed();
}
@@ -439,16 +270,12 @@ public class SQLiteCursor extends AbstractWindowedCursor {
// This one will recreate the temp table, and get its count
mDriver.cursorRequeried(this);
mCount = NO_COUNT;
- mCursorState++;
- queryThreadLock();
try {
mQuery.requery();
} catch (IllegalStateException e) {
// for backwards compatibility, just return false
Log.w(TAG, "requery() failed " + e.getMessage(), e);
return false;
- } finally {
- queryThreadUnlock();
}
}
@@ -472,18 +299,9 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
@Override
- public void setWindow(CursorWindow window) {
- if (mWindow != null) {
- mCursorState++;
- queryThreadLock();
- try {
- mWindow.close();
- } finally {
- queryThreadUnlock();
- }
- mCount = NO_COUNT;
- }
- mWindow = window;
+ public void setWindow(CursorWindow window) {
+ super.setWindow(window);
+ mCount = NO_COUNT;
}
/**
@@ -521,11 +339,4 @@ public class SQLiteCursor extends AbstractWindowedCursor {
super.finalize();
}
}
-
- /**
- * this is only for testing purposes.
- */
- /* package */ int getMCount() {
- return mCount;
- }
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 93a6ad3..00d7ce8 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -66,6 +66,7 @@ import java.util.regex.Pattern;
*/
public class SQLiteDatabase extends SQLiteClosable {
private static final String TAG = "SQLiteDatabase";
+ private static final boolean ENABLE_DB_SAMPLE = false; // true to enable stats in event log
private static final int EVENT_DB_OPERATION = 52000;
private static final int EVENT_DB_CORRUPT = 75004;
@@ -440,7 +441,9 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
if (sql != null) {
- logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX);
+ if (ENABLE_DB_SAMPLE) {
+ logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX);
+ }
}
}
private static class DatabaseReentrantLock extends ReentrantLock {
@@ -726,7 +729,9 @@ public class SQLiteDatabase extends SQLiteClosable {
}
}
// log the transaction time to the Eventlog.
- logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL);
+ if (ENABLE_DB_SAMPLE) {
+ logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL);
+ }
} else {
try {
execSQL("ROLLBACK;");
@@ -1593,32 +1598,6 @@ public class SQLiteDatabase extends SQLiteClosable {
}
/**
- * Runs the provided SQL and returns a cursor over the result set.
- * The cursor will read an initial set of rows and the return to the caller.
- * It will continue to read in batches and send data changed notifications
- * when the later batches are ready.
- * @param sql the SQL query. The SQL string must not be ; terminated
- * @param selectionArgs You may include ?s in where clause in the query,
- * which will be replaced by the values from selectionArgs. The
- * values will be bound as Strings.
- * @param initialRead set the initial count of items to read from the cursor
- * @param maxRead set the count of items to read on each iteration after the first
- * @return A {@link Cursor} object, which is positioned before the first entry. Note that
- * {@link Cursor}s are not synchronized, see the documentation for more details.
- *
- * This work is incomplete and not fully tested or reviewed, so currently
- * hidden.
- * @hide
- */
- public Cursor rawQuery(String sql, String[] selectionArgs,
- int initialRead, int maxRead) {
- SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory(
- null, sql, selectionArgs, null);
- c.setLoadStyle(initialRead, maxRead);
- return c;
- }
-
- /**
* Convenience method for inserting a row into the database.
*
* @param table the table to insert the row into
@@ -2036,7 +2015,9 @@ public class SQLiteDatabase extends SQLiteClosable {
}
/* package */ void logTimeStat(String sql, long beginMillis) {
- logTimeStat(sql, beginMillis, null);
+ if (ENABLE_DB_SAMPLE) {
+ logTimeStat(sql, beginMillis, null);
+ }
}
private void logTimeStat(String sql, long beginMillis, String prefix) {
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index dc882d9..7db0914 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -30,6 +30,11 @@ import android.util.Log;
public class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "SQLiteQuery";
+ private static native int nativeFillWindow(int databasePtr, int statementPtr, int windowPtr,
+ int startPos, int offsetParam);
+ private static native int nativeColumnCount(int statementPtr);
+ private static native String nativeColumnName(int statementPtr, int columnIndex);
+
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex = 0;
@@ -68,19 +73,15 @@ public class SQLiteQuery extends SQLiteProgram {
* @param window The window to fill into
* @return number of total rows in the query
*/
- /* package */ int fillWindow(CursorWindow window,
- int maxRead, int lastPos) {
+ /* package */ int fillWindow(CursorWindow window) {
mDatabase.lock(mSql);
long timeStart = SystemClock.uptimeMillis();
try {
acquireReference();
try {
window.acquireReference();
- // if the start pos is not equal to 0, then most likely window is
- // too small for the data set, loading by another thread
- // is not safe in this situation. the native code will ignore maxRead
- int numRows = native_fill_window(window, window.getStartPosition(),
- mOffsetIndex, maxRead, lastPos);
+ int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
+ window.getStartPosition(), mOffsetIndex);
mDatabase.logTimeStat(mSql, timeStart);
return numRows;
} catch (IllegalStateException e){
@@ -111,7 +112,7 @@ public class SQLiteQuery extends SQLiteProgram {
/* package */ int columnCountLocked() {
acquireReference();
try {
- return native_column_count();
+ return nativeColumnCount(nStatement);
} finally {
releaseReference();
}
@@ -127,17 +128,17 @@ public class SQLiteQuery extends SQLiteProgram {
/* package */ String columnNameLocked(int columnIndex) {
acquireReference();
try {
- return native_column_name(columnIndex);
+ return nativeColumnName(nStatement, columnIndex);
} finally {
releaseReference();
}
}
-
+
@Override
public String toString() {
return "SQLiteQuery: " + mSql;
}
-
+
@Override
public void close() {
super.close();
@@ -153,11 +154,4 @@ public class SQLiteQuery extends SQLiteProgram {
}
compileAndbindAllArgs();
}
-
- private final native int native_fill_window(CursorWindow window,
- int startPos, int offsetParam, int maxRead, int lastPos);
-
- private final native int native_column_count();
-
- private final native String native_column_name(int columnIndex);
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index e40de26..d338764 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -201,7 +201,7 @@ public class Camera {
public static final int CAMERA_FACING_FRONT = 1;
/**
- * The direction that the camera faces to. It should be
+ * The direction that the camera faces. It should be
* CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
*/
public int facing;
@@ -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)
@@ -1007,6 +1013,10 @@ public class Camera {
* camera.setDisplayOrientation(result);
* }
* </pre>
+ *
+ * <p>Starting from API level 14, this method can be called when preview is
+ * active.
+ *
* @param degrees the angle that the picture will be rotated clockwise.
* Valid values are 0, 90, 180, and 270. The starting
* position is 0 (landscape).
@@ -1055,9 +1065,8 @@ public class Camera {
/**
* Notify the listener of the detected faces in the preview frame.
*
- * @param faces the detected faces. The list is sorted by the score.
- * The highest score is the first element.
- * @param camera the Camera service object
+ * @param faces The detected faces in a list
+ * @param camera The {@link Camera} service object
*/
void onFaceDetection(Face[] faces, Camera camera);
}
@@ -1105,7 +1114,7 @@ public class Camera {
/**
* Stops the face detection.
*
- * @see #startFaceDetection(int)
+ * @see #startFaceDetection()
*/
public final void stopFaceDetection() {
_stopFaceDetection();
@@ -1116,8 +1125,12 @@ public class Camera {
private native final void _stopFaceDetection();
/**
- * The information of a face from camera face detection.
+ * 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>
*
+ * @see FaceDetectionListener
*/
public static class Face {
/**
@@ -1132,21 +1145,23 @@ 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
* mirroring of {@link #setDisplayOrientation(int)}.</p>
*
- * @see #startFaceDetection(int)
+ * @see #startFaceDetection()
*/
public Rect rect;
/**
- * The confidence level of the face. The range is 1 to 100. 100 is the
+ * The confidence level for the detection of the face. The range is 1 to 100. 100 is the
* highest confidence.
*
- * @see #startFaceDetection(int)
+ * @see #startFaceDetection()
*/
public int score;
@@ -1456,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";
@@ -1643,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";
@@ -1653,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
*/
@@ -2435,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);
@@ -3051,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
*/
@@ -3144,7 +3175,7 @@ public class Camera {
* supported.
*
* @return the maximum number of detected face supported by the camera.
- * @see #startFaceDetection(int)
+ * @see #startFaceDetection()
*/
public int getMaxNumDetectedFaces() {
return getInt(KEY_MAX_NUM_DETECTED_FACES_HW, 0);
@@ -3200,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/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 1119c1e..5343e2a 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -248,6 +248,8 @@ public class KeyboardView extends View implements View.OnClickListener {
private AccessibilityManager mAccessibilityManager;
/** The audio manager for accessibility support */
private AudioManager mAudioManager;
+ /** Whether the requirement of a headset to hear passwords if accessibility is enabled is announced. */
+ private boolean mHeadsetRequiredToHearPasswordsAnnounced;
Handler mHandler = new Handler() {
@Override
@@ -852,13 +854,15 @@ public class KeyboardView extends View implements View.OnClickListener {
Key oldKey = keys[oldKeyIndex];
oldKey.onReleased(mCurrentKeyIndex == NOT_A_KEY);
invalidateKey(oldKeyIndex);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT, oldKey.codes[0]);
+ sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT,
+ oldKey.codes[0]);
}
if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
Key newKey = keys[mCurrentKeyIndex];
newKey.onPressed();
invalidateKey(mCurrentKeyIndex);
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER, newKey.codes[0]);
+ sendAccessibilityEventForUnicodeCharacter(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+ newKey.codes[0]);
}
}
// If key changed and preview is on ...
@@ -958,13 +962,13 @@ public class KeyboardView extends View implements View.OnClickListener {
mPreviewText.setVisibility(VISIBLE);
}
- private void sendAccessibilityEvent(int eventType, int code) {
+ private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
if (mAccessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
onInitializeAccessibilityEvent(event);
+ String text = null;
// Add text only if headset is used to avoid leaking passwords.
if (mAudioManager.isBluetoothA2dpOn() || mAudioManager.isWiredHeadsetOn()) {
- String text = null;
switch (code) {
case Keyboard.KEYCODE_ALT:
text = mContext.getString(R.string.keyboardview_keycode_alt);
@@ -990,11 +994,17 @@ public class KeyboardView extends View implements View.OnClickListener {
default:
text = String.valueOf((char) code);
}
- event.getText().add(text);
+ } else if (!mHeadsetRequiredToHearPasswordsAnnounced) {
+ // We want the waring for required head set to be send with both the
+ // hover enter and hover exit event, so set the flag after the exit.
+ if (eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
+ mHeadsetRequiredToHearPasswordsAnnounced = true;
+ }
+ text = mContext.getString(R.string.keyboard_headset_required_to_hear_password);
} else {
- event.getText().add(mContext.getString(
- R.string.keyboard_headset_required_to_hear_password));
+ text = mContext.getString(R.string.keyboard_password_character_no_headset);
}
+ event.getText().add(text);
mAccessibilityManager.sendAccessibilityEvent(event);
}
}
@@ -1134,15 +1144,13 @@ public class KeyboardView extends View implements View.OnClickListener {
}
@Override
- protected boolean dispatchHoverEvent(MotionEvent event) {
+ public boolean onHoverEvent(MotionEvent event) {
// If touch exploring is enabled we ignore touch events and transform
// the stream of hover events as touch events. This allows one consistent
// event stream to drive the keyboard since during touch exploring the
// first touch generates only hover events and tapping on the same
// location generates hover and touch events.
- if (mAccessibilityManager.isEnabled()
- && mAccessibilityManager.isTouchExplorationEnabled()
- && event.getPointerCount() == 1) {
+ if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
@@ -1156,9 +1164,9 @@ public class KeyboardView extends View implements View.OnClickListener {
break;
}
onTouchEventInternal(event);
- return true;
+ event.setAction(action);
}
- return super.dispatchHoverEvent(event);
+ return super.onHoverEvent(event);
}
@Override
@@ -1168,8 +1176,7 @@ public class KeyboardView extends View implements View.OnClickListener {
// event stream to drive the keyboard since during touch exploring the
// first touch generates only hover events and tapping on the same
// location generates hover and touch events.
- if (mAccessibilityManager.isEnabled()
- && mAccessibilityManager.isTouchExplorationEnabled()) {
+ if (mAccessibilityManager.isTouchExplorationEnabled()) {
return true;
}
return onTouchEventInternal(event);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 530122c..0052dd0 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -515,7 +515,7 @@ public class ConnectivityManager {
* All applications that have background services that use the network
* should listen to {@link #ACTION_BACKGROUND_DATA_SETTING_CHANGED}.
* <p>
- * As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of
+ * @deprecated As of {@link VERSION_CODES#ICE_CREAM_SANDWICH}, availability of
* background data depends on several combined factors, and this method will
* always return {@code true}. Instead, when background data is unavailable,
* {@link #getActiveNetworkInfo()} will now appear disconnected.
@@ -548,6 +548,8 @@ public class ConnectivityManager {
* Return quota status for the current active network, or {@code null} if no
* network is active. Quota status can change rapidly, so these values
* shouldn't be cached.
+ *
+ * @hide
*/
public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
try {
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index 79c9395..fc6a44a 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -336,17 +336,17 @@ public class DhcpStateMachine extends StateMachine {
DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
if (dhcpAction == DhcpAction.START) {
- Log.d(TAG, "DHCP request on " + mInterfaceName);
+ if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
mDhcpInfo = dhcpInfoInternal;
} else if (dhcpAction == DhcpAction.RENEW) {
- Log.d(TAG, "DHCP renewal on " + mInterfaceName);
+ if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
dhcpInfoInternal.updateFromDhcpRequest(mDhcpInfo);
}
if (success) {
- Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
+ if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
//Sanity check for renewal
@@ -366,7 +366,7 @@ public class DhcpStateMachine extends StateMachine {
mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
.sendToTarget();
} else {
- Log.d(TAG, "DHCP failed on " + mInterfaceName + ": " +
+ Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " +
NetworkUtils.getDhcpError());
NetworkUtils.stopDhcp(mInterfaceName);
mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
diff --git a/core/java/android/net/DnsPinger.java b/core/java/android/net/DnsPinger.java
index 3291e6b..11acabe 100644
--- a/core/java/android/net/DnsPinger.java
+++ b/core/java/android/net/DnsPinger.java
@@ -22,7 +22,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings;
-import android.util.Slog;
+import android.util.Log;
import com.android.internal.util.Protocol;
@@ -51,7 +51,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* @hide
*/
public final class DnsPinger extends Handler {
- private static final boolean V = true;
+ private static final boolean DBG = false;
private static final int RECEIVE_POLL_INTERVAL_MS = 200;
private static final int DNS_PORT = 53;
@@ -154,7 +154,7 @@ public final class DnsPinger extends Handler {
newActivePing.socket.setNetworkInterface(NetworkInterface.getByName(
getCurrentLinkProperties().getInterfaceName()));
} catch (Exception e) {
- Slog.w(TAG,"sendDnsPing::Error binding to socket", e);
+ loge("sendDnsPing::Error binding to socket " + e);
}
newActivePing.packetId = (short) sRandom.nextInt();
@@ -165,8 +165,8 @@ public final class DnsPinger extends Handler {
// Send the DNS query
DatagramPacket packet = new DatagramPacket(buf,
buf.length, dnsAddress, DNS_PORT);
- if (V) {
- Slog.v(TAG, "Sending a ping " + newActivePing.internalId +
+ if (DBG) {
+ log("Sending a ping " + newActivePing.internalId +
" to " + dnsAddress.getHostAddress()
+ " with packetId " + newActivePing.packetId + ".");
}
@@ -196,15 +196,15 @@ public final class DnsPinger extends Handler {
curPing.result =
(int) (SystemClock.elapsedRealtime() - curPing.start);
} else {
- if (V) {
- Slog.v(TAG, "response ID didn't match, ignoring packet");
+ if (DBG) {
+ log("response ID didn't match, ignoring packet");
}
}
} catch (SocketTimeoutException e) {
// A timeout here doesn't mean anything - squelsh this exception
} catch (Exception e) {
- if (V) {
- Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e);
+ if (DBG) {
+ log("DnsPinger.pingDns got socket exception: " + e);
}
curPing.result = SOCKET_EXCEPTION;
}
@@ -244,13 +244,13 @@ public final class DnsPinger extends Handler {
public List<InetAddress> getDnsList() {
LinkProperties curLinkProps = getCurrentLinkProperties();
if (curLinkProps == null) {
- Slog.e(TAG, "getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
+ loge("getCurLinkProperties:: LP for type" + mConnectionType + " is null!");
return mDefaultDns;
}
Collection<InetAddress> dnses = curLinkProps.getDnses();
if (dnses == null || dnses.size() == 0) {
- Slog.v(TAG, "getDns::LinkProps has null dns - returning default");
+ loge("getDns::LinkProps has null dns - returning default");
return mDefaultDns;
}
@@ -277,8 +277,8 @@ public final class DnsPinger extends Handler {
}
private void sendResponse(int internalId, int externalId, int responseVal) {
- if(V) {
- Slog.d(TAG, "Responding to packet " + internalId +
+ if(DBG) {
+ log("Responding to packet " + internalId +
" externalId " + externalId +
" and val " + responseVal);
}
@@ -304,7 +304,7 @@ public final class DnsPinger extends Handler {
try {
return NetworkUtils.numericToInetAddress(dns);
} catch (IllegalArgumentException e) {
- Slog.w(TAG, "getDefaultDns::malformed default dns address");
+ loge("getDefaultDns::malformed default dns address");
return null;
}
}
@@ -323,4 +323,12 @@ public final class DnsPinger extends Handler {
0, 1, // QTYPE, set to 1 = A (host address)
0, 1 // QCLASS, set to 1 = IN (internet)
};
+
+ private void log(String s) {
+ Log.d(TAG, s);
+ }
+
+ private void loge(String s) {
+ Log.e(TAG, s);
+ }
}
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/net/NetworkQuotaInfo.java b/core/java/android/net/NetworkQuotaInfo.java
index b85f925..6535256 100644
--- a/core/java/android/net/NetworkQuotaInfo.java
+++ b/core/java/android/net/NetworkQuotaInfo.java
@@ -21,6 +21,8 @@ import android.os.Parcelable;
/**
* Information about quota status on a specific network.
+ *
+ * @hide
*/
public class NetworkQuotaInfo implements Parcelable {
private final long mEstimatedBytes;
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index f3be39c..69ac1e7 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -229,6 +229,14 @@ public class NetworkStats implements Parcelable {
return elapsedRealtime;
}
+ /**
+ * Return age of this {@link NetworkStats} object with respect to
+ * {@link SystemClock#elapsedRealtime()}.
+ */
+ public long getElapsedRealtimeAge() {
+ return SystemClock.elapsedRealtime() - elapsedRealtime;
+ }
+
public int size() {
return size;
}
@@ -272,6 +280,17 @@ public class NetworkStats implements Parcelable {
}
/**
+ * Combine all values from another {@link NetworkStats} into this object.
+ */
+ public void combineAllValues(NetworkStats another) {
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < another.size; i++) {
+ entry = another.getValues(i, entry);
+ combineValues(entry);
+ }
+ }
+
+ /**
* Find first stats index that matches the requested parameters.
*/
public int findIndex(String iface, int uid, int set, int tag) {
@@ -343,26 +362,59 @@ public class NetworkStats implements Parcelable {
* Return total of all fields represented by this snapshot object.
*/
public Entry getTotal(Entry recycle) {
+ return getTotal(recycle, null, UID_ALL);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #uid}.
+ */
+ public Entry getTotal(Entry recycle, int limitUid) {
+ return getTotal(recycle, null, limitUid);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #iface}.
+ */
+ public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
+ return getTotal(recycle, limitIface, UID_ALL);
+ }
+
+ /**
+ * Return total of all fields represented by this snapshot object matching
+ * the requested {@link #iface} and {@link #uid}.
+ *
+ * @param limitIface Set of {@link #iface} to include in total; or {@code
+ * null} to include all ifaces.
+ */
+ private Entry getTotal(Entry recycle, HashSet<String> limitIface, int limitUid) {
final Entry entry = recycle != null ? recycle : new Entry();
entry.iface = IFACE_ALL;
- entry.uid = UID_ALL;
+ entry.uid = limitUid;
entry.set = SET_ALL;
entry.tag = TAG_NONE;
entry.rxBytes = 0;
entry.rxPackets = 0;
entry.txBytes = 0;
entry.txPackets = 0;
+ entry.operations = 0;
for (int i = 0; i < size; i++) {
- // skip specific tags, since already counted in TAG_NONE
- if (tag[i] != TAG_NONE) continue;
-
- entry.rxBytes += rxBytes[i];
- entry.rxPackets += rxPackets[i];
- entry.txBytes += txBytes[i];
- entry.txPackets += txPackets[i];
- entry.operations += operations[i];
+ final boolean matchesUid = (limitUid == UID_ALL) || (limitUid == uid[i]);
+ final boolean matchesIface = (limitIface == null) || (limitIface.contains(iface[i]));
+
+ if (matchesUid && matchesIface) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE) continue;
+
+ entry.rxBytes += rxBytes[i];
+ entry.rxPackets += rxPackets[i];
+ entry.txBytes += txBytes[i];
+ entry.txPackets += txPackets[i];
+ entry.operations += operations[i];
+ }
}
return entry;
}
@@ -456,6 +508,62 @@ public class NetworkStats implements Parcelable {
return result;
}
+ /**
+ * Return total statistics grouped by {@link #iface}; doesn't mutate the
+ * original structure.
+ */
+ public NetworkStats groupedByIface() {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ final Entry entry = new Entry();
+ entry.uid = UID_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+ entry.operations = 0L;
+
+ for (int i = 0; i < size; i++) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE) continue;
+
+ entry.iface = iface[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ stats.combineValues(entry);
+ }
+
+ return stats;
+ }
+
+ /**
+ * Return total statistics grouped by {@link #uid}; doesn't mutate the
+ * original structure.
+ */
+ public NetworkStats groupedByUid() {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ final Entry entry = new Entry();
+ entry.iface = IFACE_ALL;
+ entry.set = SET_ALL;
+ entry.tag = TAG_NONE;
+
+ for (int i = 0; i < size; i++) {
+ // skip specific tags, since already counted in TAG_NONE
+ if (tag[i] != TAG_NONE) continue;
+
+ entry.uid = uid[i];
+ entry.rxBytes = rxBytes[i];
+ entry.rxPackets = rxPackets[i];
+ entry.txBytes = txBytes[i];
+ entry.txPackets = txPackets[i];
+ entry.operations = operations[i];
+ stats.combineValues(entry);
+ }
+
+ return stats;
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index a6635be..8c01331 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -478,17 +478,34 @@ public class NetworkStatsHistory implements Parcelable {
* @deprecated only for temporary testing
*/
@Deprecated
+ public void generateRandom(long start, long end, long bytes) {
+ final Random r = new Random();
+
+ final float fractionRx = r.nextFloat();
+ final long rxBytes = (long) (bytes * fractionRx);
+ final long txBytes = (long) (bytes * (1 - fractionRx));
+
+ final long rxPackets = rxBytes / 1024;
+ final long txPackets = txBytes / 1024;
+ final long operations = rxBytes / 2048;
+
+ generateRandom(start, end, rxBytes, rxPackets, txBytes, txPackets, operations, r);
+ }
+
+ /**
+ * @deprecated only for temporary testing
+ */
+ @Deprecated
public void generateRandom(long start, long end, long rxBytes, long rxPackets, long txBytes,
- long txPackets, long operations) {
+ long txPackets, long operations, Random r) {
ensureBuckets(start, end);
final NetworkStats.Entry entry = new NetworkStats.Entry(
IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
- final Random r = new Random();
while (rxBytes > 1024 || rxPackets > 128 || txBytes > 1024 || txPackets > 128
|| operations > 32) {
final long curStart = randomLong(r, start, end);
- final long curEnd = randomLong(r, curStart, end);
+ final long curEnd = curStart + randomLong(r, 0, (end - curStart) / 2);
entry.rxBytes = randomLong(r, 0, rxBytes);
entry.rxPackets = randomLong(r, 0, rxPackets);
@@ -506,7 +523,7 @@ public class NetworkStatsHistory implements Parcelable {
}
}
- private static long randomLong(Random r, long start, long end) {
+ public static long randomLong(Random r, long start, long end) {
return (long) (start + (r.nextFloat() * (end - start)));
}
diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java
index 08c6692..863304c 100644
--- a/core/java/android/net/http/SslError.java
+++ b/core/java/android/net/http/SslError.java
@@ -19,7 +19,8 @@ package android.net.http;
import java.security.cert.X509Certificate;
/**
- * One or more individual SSL errors and the associated SSL certificate
+ * This class represents a set of one or more SSL errors and the associated SSL
+ * certificate.
*/
public class SslError {
@@ -48,16 +49,17 @@ public class SslError {
*/
public static final int SSL_DATE_INVALID = 4;
/**
- * The certificate is invalid
+ * A generic error occurred
*/
public static final int SSL_INVALID = 5;
/**
- * The number of different SSL errors (update if you add a new SSL error!!!)
+ * The number of different SSL errors.
* @deprecated This constant is not necessary for using the SslError API and
* can change from release to release.
*/
+ // Update if you add a new SSL error!!!
@Deprecated
public static final int SSL_MAX_ERROR = 6;
@@ -78,56 +80,56 @@ public class SslError {
final String mUrl;
/**
- * Creates a new SSL error set object
+ * Creates a new SslError object using the supplied error and certificate.
+ * The URL will be set to the empty string.
* @param error The SSL error
* @param certificate The associated SSL certificate
* @deprecated Use {@link #SslError(int, SslCertificate, String)}
*/
@Deprecated
public SslError(int error, SslCertificate certificate) {
- addError(error);
- if (certificate == null) {
- throw new NullPointerException("certificate is null.");
- }
- mCertificate = certificate;
- mUrl = "";
+ this(error, certificate, "");
}
/**
- * Creates a new SSL error set object
+ * Creates a new SslError object using the supplied error and certificate.
+ * The URL will be set to the empty string.
* @param error The SSL error
* @param certificate The associated SSL certificate
* @deprecated Use {@link #SslError(int, X509Certificate, String)}
*/
@Deprecated
public SslError(int error, X509Certificate certificate) {
- addError(error);
- if (certificate == null) {
- throw new NullPointerException("certificate is null.");
- }
- mCertificate = new SslCertificate(certificate);
- mUrl = "";
+ this(error, certificate, "");
}
/**
- * Creates a new SSL error set object
+ * Creates a new SslError object using the supplied error, certificate and
+ * URL.
* @param error The SSL error
* @param certificate The associated SSL certificate
- * @param url The associated URL.
+ * @param url The associated URL
*/
public SslError(int error, SslCertificate certificate, String url) {
+ assert certificate != null;
+ assert url != null;
addError(error);
- if (certificate == null) {
- throw new NullPointerException("certificate is null.");
- }
mCertificate = certificate;
- if (url == null) {
- throw new NullPointerException("url is null.");
- }
mUrl = url;
}
/**
+ * Creates a new SslError object using the supplied error, certificate and
+ * URL.
+ * @param error The SSL error
+ * @param certificate The associated SSL certificate
+ * @param url The associated URL
+ */
+ public SslError(int error, X509Certificate certificate, String url) {
+ this(error, new SslCertificate(certificate), url);
+ }
+
+ /**
* Creates an SslError object from a chromium error code.
* @param error The chromium error code
* @param certificate The associated SSL certificate
@@ -138,56 +140,38 @@ public class SslError {
int error, SslCertificate cert, String url) {
// The chromium error codes are in:
// external/chromium/net/base/net_error_list.h
- if (error > -200 || error < -299) {
- throw new NullPointerException("Not a valid chromium SSL error code.");
- }
+ assert (error >= -299 && error <= -200);
if (error == -200)
return new SslError(SSL_IDMISMATCH, cert, url);
if (error == -201)
return new SslError(SSL_DATE_INVALID, cert, url);
if (error == -202)
return new SslError(SSL_UNTRUSTED, cert, url);
- // Map all other errors to SSL_INVALID
+ // Map all other codes to SSL_INVALID.
return new SslError(SSL_INVALID, cert, url);
}
/**
- * Creates a new SSL error set object
- * @param error The SSL error
- * @param certificate The associated SSL certificate
- * @param url The associated URL.
- */
- public SslError(int error, X509Certificate certificate, String url) {
- addError(error);
- if (certificate == null) {
- throw new NullPointerException("certificate is null.");
- }
- mCertificate = new SslCertificate(certificate);
- if (url == null) {
- throw new NullPointerException("url is null.");
- }
- mUrl = url;
- }
-
- /**
- * @return The SSL certificate associated with the error set, non-null.
+ * Gets the SSL certificate associated with this object.
+ * @return The SSL certificate, non-null.
*/
public SslCertificate getCertificate() {
return mCertificate;
}
/**
- * @return The URL associated with the error set, non-null.
- * "" if one of the deprecated constructors is used.
+ * Gets the URL associated with this object.
+ * @return The URL, non-null.
*/
public String getUrl() {
return mUrl;
}
/**
- * Adds the SSL error to the error set
+ * Adds the supplied SSL error to the set.
* @param error The SSL error to add
- * @return True iff the error being added is a known SSL error
+ * @return True if the error being added is a known SSL error, otherwise
+ * false.
*/
public boolean addError(int error) {
boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
@@ -199,8 +183,9 @@ public class SslError {
}
/**
- * @param error The SSL error to check
- * @return True iff the set includes the error
+ * Determines whether this object includes the supplied error.
+ * @param error The SSL error to check for
+ * @return True if this object includes the error, otherwise false.
*/
public boolean hasError(int error) {
boolean rval = (0 <= error && error < SslError.SSL_MAX_ERROR);
@@ -212,7 +197,9 @@ public class SslError {
}
/**
- * @return The primary, most severe, SSL error in the set
+ * Gets the most severe SSL error in this object's set of errors.
+ * Returns -1 if the set is empty.
+ * @return The most severe SSL error, or -1 if the set is empty.
*/
public int getPrimaryError() {
if (mErrors != 0) {
@@ -222,18 +209,20 @@ public class SslError {
return error;
}
}
+ // mErrors should never be set to an invalid value.
+ assert false;
}
- return 0;
+ return -1;
}
/**
- * @return A String representation of this SSL error object
- * (used mostly for debugging).
+ * Returns a string representation of this object.
+ * @return A String representation of this object.
*/
public String toString() {
return "primary error: " + getPrimaryError() +
- " certificate: " + getCertificate() +
- " on URL: " + getUrl();
+ " certificate: " + getCertificate() +
+ " on URL: " + getUrl();
}
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index fe0106d..33310df 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -768,61 +768,6 @@ public final class NfcAdapter {
}
/**
- * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
- * @deprecated use {@link CreateNdefMessageCallback} or {@link OnNdefPushCompleteCallback}
- * @hide
- */
- @Deprecated
- public interface NdefPushCallback {
- /**
- * @deprecated use {@link CreateNdefMessageCallback} instead
- */
- @Deprecated
- NdefMessage createMessage();
- /**
- * @deprecated use{@link OnNdefPushCompleteCallback} instead
- */
- @Deprecated
- void onMessagePushed();
- }
-
- /**
- * TODO: Remove this
- * Converts new callbacks to old callbacks.
- */
- static final class LegacyCallbackWrapper implements CreateNdefMessageCallback,
- OnNdefPushCompleteCallback {
- final NdefPushCallback mLegacyCallback;
- LegacyCallbackWrapper(NdefPushCallback legacyCallback) {
- mLegacyCallback = legacyCallback;
- }
- @Override
- public void onNdefPushComplete(NfcEvent event) {
- mLegacyCallback.onMessagePushed();
- }
- @Override
- public NdefMessage createNdefMessage(NfcEvent event) {
- return mLegacyCallback.createMessage();
- }
- }
-
- /**
- * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated
- * @deprecated use {@link #setNdefPushMessageCallback} instead
- * @hide
- */
- @Deprecated
- public void enableForegroundNdefPush(Activity activity, final NdefPushCallback callback) {
- if (activity == null || callback == null) {
- throw new NullPointerException();
- }
- enforceResumed(activity);
- LegacyCallbackWrapper callbackWrapper = new LegacyCallbackWrapper(callback);
- mNfcActivityManager.setNdefPushMessageCallback(activity, callbackWrapper);
- mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callbackWrapper);
- }
-
- /**
* Enable NDEF Push feature.
* <p>This API is for the Settings application.
* @hide
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 64bba54..9dea4c4 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -42,6 +42,13 @@ import java.util.concurrent.atomic.AtomicInteger;
* and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
* <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
*
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using tasks and threads, read the
+ * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html">Processes and
+ * Threads</a> developer guide.</p>
+ * </div>
+ *
* <h2>Usage</h2>
* <p>AsyncTask must be subclassed to be used. The subclass will override at least
* one method ({@link #doInBackground}), and most often will override a
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 1d8ea12..5faab36 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -282,6 +282,10 @@ public class Build {
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
* <ul>
+ * <li> For devices without a dedicated menu key, the software compatibility
+ * menu key will not be shown even on phones. By targeting Ice Cream Sandwich
+ * or later, your UI must always have its own menu UI affordance if needed,
+ * on both tablets and phones. The ActionBar will take care of this for you.
* <li> 2d drawing hardware acceleration is now turned on by default.
* You can use
* {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated}
@@ -296,6 +300,12 @@ public class Build {
* will not change character within the same platform version. Applications
* that wish to blend in with the device should use a theme from the
* {@link android.R.style#Theme_DeviceDefault} family.
+ * <li> Managed cursors can now throw an exception if you directly close
+ * the cursor yourself without stopping the management of it; previously failures
+ * would be silently ignored.
+ * <li> The fadingEdge attribute on views will be ignored (fading edges is no
+ * longer a standard part of the UI). A new requiresFadingEdge attribute allows
+ * applications to still force fading edges on for special cases.
* </ul>
*/
public static final int ICE_CREAM_SANDWICH = 14;
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c288f8a..28206b7 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -54,6 +54,7 @@ public final class Bundle implements Parcelable, Cloneable {
private boolean mHasFds = false;
private boolean mFdsKnown = true;
+ private boolean mAllowFds = true;
/**
* The ClassLoader used when unparcelling data from mParcelledData.
@@ -186,7 +187,14 @@ public final class Bundle implements Parcelable, Cloneable {
public ClassLoader getClassLoader() {
return mClassLoader;
}
-
+
+ /** @hide */
+ public boolean setAllowFds(boolean allowFds) {
+ boolean orig = mAllowFds;
+ mAllowFds = allowFds;
+ return orig;
+ }
+
/**
* Clones the current Bundle. The internal map is cloned, but the keys and
* values to which it refers are copied by reference.
@@ -1589,24 +1597,29 @@ public final class Bundle implements Parcelable, Cloneable {
* @param parcel The parcel to copy this bundle to.
*/
public void writeToParcel(Parcel parcel, int flags) {
- if (mParcelledData != null) {
- int length = mParcelledData.dataSize();
- parcel.writeInt(length);
- parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
- parcel.appendFrom(mParcelledData, 0, length);
- } else {
- parcel.writeInt(-1); // dummy, will hold length
- parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
-
- int oldPos = parcel.dataPosition();
- parcel.writeMapInternal(mMap);
- int newPos = parcel.dataPosition();
-
- // Backpatch length
- parcel.setDataPosition(oldPos - 8);
- int length = newPos - oldPos;
- parcel.writeInt(length);
- parcel.setDataPosition(newPos);
+ final boolean oldAllowFds = parcel.pushAllowFds(mAllowFds);
+ try {
+ if (mParcelledData != null) {
+ int length = mParcelledData.dataSize();
+ parcel.writeInt(length);
+ parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
+ parcel.appendFrom(mParcelledData, 0, length);
+ } else {
+ parcel.writeInt(-1); // dummy, will hold length
+ parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
+
+ int oldPos = parcel.dataPosition();
+ parcel.writeMapInternal(mMap);
+ int newPos = parcel.dataPosition();
+
+ // Backpatch length
+ parcel.setDataPosition(oldPos - 8);
+ int length = newPos - oldPos;
+ parcel.writeInt(length);
+ parcel.setDataPosition(newPos);
+ }
+ } finally {
+ parcel.restoreAllowFds(oldAllowFds);
}
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index e9ed676..15e3af4 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -323,6 +323,12 @@ public final class Parcel {
*/
public final native void setDataCapacity(int size);
+ /** @hide */
+ public final native boolean pushAllowFds(boolean allowFds);
+
+ /** @hide */
+ public final native void restoreAllowFds(boolean lastValue);
+
/**
* Returns the raw bytes of the parcel.
*
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 68385b4..cc2fa85 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -35,7 +35,6 @@ import dalvik.system.VMDebug;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -902,6 +901,7 @@ public final class StrictMode {
return false;
}
+ // Thread policy controls BlockGuard.
int threadPolicyMask = StrictMode.DETECT_DISK_WRITE |
StrictMode.DETECT_DISK_READ |
StrictMode.DETECT_NETWORK;
@@ -915,10 +915,16 @@ public final class StrictMode {
StrictMode.setThreadPolicyMask(threadPolicyMask);
+ // VM Policy controls CloseGuard, detection of Activity leaks,
+ // and instance counting.
if (IS_USER_BUILD) {
setCloseGuardEnabled(false);
} else {
- setVmPolicy(new VmPolicy.Builder().detectAll().penaltyDropBox().build());
+ VmPolicy.Builder policyBuilder = new VmPolicy.Builder().detectAll().penaltyDropBox();
+ if (IS_ENG_BUILD) {
+ policyBuilder.penaltyLog();
+ }
+ setVmPolicy(policyBuilder.build());
setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
}
return true;
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 4b4d308..d7060c1 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -300,8 +300,25 @@ public final class CalendarContract {
public static final String CALENDAR_COLOR = "calendar_color";
/**
+ * An index for looking up a color from the {@link Colors} table. NULL
+ * or an empty string are reserved for indicating that the calendar does
+ * not use an index for looking up the color. The provider will update
+ * {@link #CALENDAR_COLOR} automatically when a valid index is written
+ * to this column. @see Colors
+ * <P>
+ * Type: TEXT
+ * </P>
+ * TODO UNHIDE
+ *
+ * @hide
+ */
+ public static final String CALENDAR_COLOR_INDEX = "calendar_color_index";
+
+ /**
* The display name of the calendar. Column name.
- * <P>Type: TEXT</P>
+ * <P>
+ * Type: TEXT
+ * </P>
*/
public static final String CALENDAR_DISPLAY_NAME = "calendar_displayName";
@@ -392,6 +409,34 @@ public final class CalendarContract {
* <P>Type: TEXT</P>
*/
public static final String ALLOWED_REMINDERS = "allowedReminders";
+
+ /**
+ * A comma separated list of availability types supported for this
+ * calendar in the format "#,#,#". Valid types are
+ * {@link Events#AVAILABILITY_BUSY}, {@link Events#AVAILABILITY_FREE},
+ * {@link Events#AVAILABILITY_TENTATIVE}. Setting this field to only
+ * {@link Events#AVAILABILITY_BUSY} should be used to indicate that
+ * changing the availability is not supported.
+ *
+ * TODO UNHIDE, Update Calendars doc
+ *
+ * @hide
+ */
+ public static final String ALLOWED_AVAILABILITY = "allowedAvailability";
+
+ /**
+ * A comma separated list of attendee types supported for this calendar
+ * in the format "#,#,#". Valid types are {@link Attendees#TYPE_NONE},
+ * {@link Attendees#TYPE_OPTIONAL}, {@link Attendees#TYPE_REQUIRED},
+ * {@link Attendees#TYPE_RESOURCE}. Setting this field to only
+ * {@link Attendees#TYPE_NONE} should be used to indicate that changing
+ * the attendee type is not supported.
+ *
+ * TODO UNHIDE, Update Calendars doc
+ *
+ * @hide
+ */
+ public static final String ALLOWED_ATTENDEE_TYPES = "allowedAttendeeTypes";
}
/**
@@ -688,13 +733,22 @@ public final class CalendarContract {
/**
* The type of attendee. Column name.
- * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})</P>
+ * <P>
+ * Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL}
+ * </P>
*/
public static final String ATTENDEE_TYPE = "attendeeType";
public static final int TYPE_NONE = 0;
public static final int TYPE_REQUIRED = 1;
public static final int TYPE_OPTIONAL = 2;
+ /**
+ * This specifies that an attendee is a resource, such as a room, and
+ * not an actual person. TODO UNHIDE and add to ATTENDEE_TYPE comment
+ *
+ * @hide
+ */
+ public static final int TYPE_RESOURCE = 3;
/**
* The attendance status of the attendee. Column name.
@@ -787,13 +841,26 @@ public final class CalendarContract {
public static final String EVENT_LOCATION = "eventLocation";
/**
- * A secondary color for the individual event. Reserved for future use.
- * Column name.
+ * A secondary color for the individual event. This should only be
+ * updated by the sync adapter for a given account.
* <P>Type: INTEGER</P>
*/
public static final String EVENT_COLOR = "eventColor";
/**
+ * A secondary color index for the individual event. NULL or an empty
+ * string are reserved for indicating that the event does not use an
+ * index for looking up the color. The provider will update
+ * {@link #EVENT_COLOR} automatically when a valid index is written to
+ * this column. @see Colors
+ * <P>Type: TEXT</P>
+ * TODO UNHIDE
+ *
+ * @hide
+ */
+ public static final String EVENT_COLOR_INDEX = "eventColor_index";
+
+ /**
* The event status. Column name.
* <P>Type: INTEGER (one of {@link #STATUS_TENTATIVE}...)</P>
*/
@@ -964,6 +1031,15 @@ public final class CalendarContract {
* other events.
*/
public static final int AVAILABILITY_FREE = 1;
+ /**
+ * Indicates that the owner's availability may change, but should be
+ * considered busy time that will conflict.
+ *
+ * TODO UNHIDE
+ *
+ * @hide
+ */
+ public static final int AVAILABILITY_TENTATIVE = 2;
/**
* Whether the event has an alarm or not. Column name.
@@ -2224,6 +2300,91 @@ public final class CalendarContract {
}
}
+ /**
+ * @hide
+ * TODO UNHIDE
+ */
+ protected interface ColorsColumns extends SyncStateContract.Columns {
+
+ /**
+ * The type of color, which describes how it should be used. Valid types
+ * are {@link #TYPE_CALENDAR} and {@link #TYPE_EVENT}. Column name.
+ * <P>
+ * Type: INTEGER (NOT NULL)
+ * </P>
+ */
+ public static final String COLOR_TYPE = "color_type";
+
+ /**
+ * This indicateds a color that can be used for calendars.
+ */
+ public static final int TYPE_CALENDAR = 0;
+ /**
+ * This indicates a color that can be used for events.
+ */
+ public static final int TYPE_EVENT = 1;
+
+ /**
+ * The index used to reference this color. This can be any non-empty
+ * string, but must be unique for a given {@link #ACCOUNT_TYPE} and
+ * {@link #ACCOUNT_NAME} . Column name.
+ * <P>
+ * Type: TEXT
+ * </P>
+ */
+ public static final String COLOR_INDEX = "color_index";
+
+ /**
+ * The version of this color that will work with dark text as an 8-bit
+ * ARGB integer value. Colors should specify alpha as fully opaque (eg
+ * 0xFF993322) as the alpha may be ignored or modified for display.
+ * Column name.
+ * <P>
+ * Type: INTEGER (NOT NULL)
+ * </P>
+ */
+ public static final String COLOR_LIGHT = "color_light";
+
+ /**
+ * The version of this color that will work with light text as an 8-bit
+ * ARGB integer value. Colors should specify alpha as fully opaque (eg
+ * 0xFF993322) as the alpha may be ignored or modified for display.
+ * Column name.
+ * <P>
+ * Type: INTEGER (NOT NULL)
+ * </P>
+ */
+ public static final String COLOR_DARK = "color_dark";
+
+ }
+
+ /**
+ * Fields for accessing colors available for a given account. Colors are
+ * referenced by {@link #COLOR_INDEX} which must be unique for a given
+ * account name/type. These values should only be updated by the sync
+ * adapter.
+ * TODO UNHIDE
+ *
+ * @hide
+ */
+ public static final class Colors implements ColorsColumns {
+ /**
+ * @hide
+ */
+ public static final String TABLE_NAME = "Colors";
+ /**
+ * The Uri for querying color information
+ */
+ @SuppressWarnings("hiding")
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/colors");
+
+ /**
+ * This utility class cannot be instantiated
+ */
+ private Colors() {
+ }
+ }
+
protected interface ExtendedPropertiesColumns {
/**
* The event the extended property belongs to. Column name.
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 886edaf..6d14dfc 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -238,6 +238,14 @@ public class CallLog {
public static final String CACHED_PHOTO_ID = "photo_id";
/**
+ * The cached formatted phone number.
+ * This value is not guaranteed to be present.
+ * <P>Type: TEXT</P>
+ * @hide
+ */
+ public static final String CACHED_FORMATTED_NUMBER = "formatted_number";
+
+ /**
* Adds a call to the call log.
*
* @param ci the CallerInfo object to get the target contact from. Can be null
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index fb119b3..8483b4f 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1611,9 +1611,16 @@ public final class ContactsContract {
}
/**
+ * <p>
* A sub-directory of a single contact that contains all of the constituent raw contact
* {@link ContactsContract.StreamItems} rows. This directory can be used either
* with a {@link #CONTENT_URI} or {@link #CONTENT_LOOKUP_URI}.
+ * </p>
+ * <p>
+ * Querying for social stream data requires android.permission.READ_SOCIAL_STREAM
+ * permission.
+ * </p>
+ * @hide
*/
public static final class StreamItems implements StreamItemsColumns {
/**
@@ -2669,6 +2676,14 @@ public final class ContactsContract {
* {@link ContactsContract.StreamItems} for a stand-alone table containing the
* same data.
* </p>
+ * <p>
+ * Access to the social stream through this sub-directory requires additional permissions
+ * beyond the read/write contact permissions required by the provider. Querying for
+ * social stream data requires android.permission.READ_SOCIAL_STREAM permission, and
+ * inserting or updating social stream items requires android.permission.WRITE_SOCIAL_STREAM
+ * permission.
+ * </p>
+ * @hide
*/
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
@@ -2963,6 +2978,12 @@ public final class ContactsContract {
* transaction correspondingly. Insertion of more items beyond the limit will
* automatically lead to deletion of the oldest items, by {@link StreamItems#TIMESTAMP}.
* </p>
+ * <p>
+ * Access to the social stream through these URIs requires additional permissions beyond the
+ * read/write contact permissions required by the provider. Querying for social stream data
+ * requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating social
+ * stream items requires android.permission.WRITE_SOCIAL_STREAM permission.
+ * </p>
* <h3>Operations</h3>
* <dl>
* <dt><b>Insert</b></dt>
@@ -3075,6 +3096,7 @@ public final class ContactsContract {
* </pre>
* </dd>
* </dl>
+ * @hide
*/
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
@@ -3135,6 +3157,12 @@ public final class ContactsContract {
* directory append {@link StreamItems.StreamItemPhotos#CONTENT_DIRECTORY} to
* an individual stream item URI.
* </p>
+ * <p>
+ * Access to social stream photos requires additional permissions beyond the read/write
+ * contact permissions required by the provider. Querying for social stream photos
+ * requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating
+ * social stream photos requires android.permission.WRITE_SOCIAL_STREAM permission.
+ * </p>
*/
public static final class StreamItemPhotos
implements BaseColumns, StreamItemPhotosColumns {
@@ -3166,6 +3194,7 @@ public final class ContactsContract {
* Columns in the StreamItems table.
*
* @see ContactsContract.StreamItems
+ * @hide
*/
protected interface StreamItemsColumns {
/**
@@ -3312,6 +3341,12 @@ public final class ContactsContract {
* Constants for the stream_item_photos table, which contains photos associated with
* social stream updates.
* </p>
+ * <p>
+ * Access to social stream photos requires additional permissions beyond the read/write
+ * contact permissions required by the provider. Querying for social stream photos
+ * requires android.permission.READ_SOCIAL_STREAM permission, and inserting or updating
+ * social stream photos requires android.permission.WRITE_SOCIAL_STREAM permission.
+ * </p>
* <h3>Operations</h3>
* <dl>
* <dt><b>Insert</b></dt>
@@ -3450,6 +3485,7 @@ public final class ContactsContract {
* <pre>
* </dd>
* </dl>
+ * @hide
*/
public static final class StreamItemPhotos implements BaseColumns, StreamItemPhotosColumns {
/**
@@ -3477,6 +3513,7 @@ public final class ContactsContract {
* Columns in the StreamItemPhotos table.
*
* @see ContactsContract.StreamItemPhotos
+ * @hide
*/
protected interface StreamItemPhotosColumns {
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bc05078..3d2a3ce 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4087,7 +4087,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/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 237a892..79995d0 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -666,6 +666,7 @@ public final class Telephony {
public static SmsMessage[] getMessagesFromIntent(
Intent intent) {
Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
+ String format = intent.getStringExtra("format");
byte[][] pduObjs = new byte[messages.length][];
for (int i = 0; i < messages.length; i++) {
@@ -676,7 +677,7 @@ public final class Telephony {
SmsMessage[] msgs = new SmsMessage[pduCount];
for (int i = 0; i < pduCount; i++) {
pdus[i] = pduObjs[i];
- msgs[i] = SmsMessage.createFromPdu(pdus[i]);
+ msgs[i] = SmsMessage.createFromPdu(pdus[i], format);
}
return msgs;
}
diff --git a/core/java/android/server/BluetoothAdapterStateMachine.java b/core/java/android/server/BluetoothAdapterStateMachine.java
index d26364e..da7c489 100644
--- a/core/java/android/server/BluetoothAdapterStateMachine.java
+++ b/core/java/android/server/BluetoothAdapterStateMachine.java
@@ -77,6 +77,12 @@ final class BluetoothAdapterStateMachine extends StateMachine {
static final int PER_PROCESS_TURN_ON = 3;
static final int PER_PROCESS_TURN_OFF = 4;
+ // Turn on Bluetooth Module, Load firmware, and do all the preparation
+ // needed to get the Bluetooth Module ready but keep it not discoverable
+ // and not connectable. This way the Bluetooth Module can be quickly
+ // switched on if needed
+ static final int TURN_HOT = 5;
+
// Message(what) to report a event that the state machine need to respond to
//
// Event indicates sevice records have been loaded
@@ -94,23 +100,18 @@ final class BluetoothAdapterStateMachine extends StateMachine {
// private internal messages
//
- // Turn on Bluetooth Module, Load firmware, and do all the preparation
- // needed to get the Bluetooth Module ready but keep it not discoverable
- // and not connectable. This way the Bluetooth Module can be quickly
- // switched on if needed
- private static final int TURN_HOT = 101;
// USER_TURN_ON is changed to TURN_ON_CONTINUE after we broadcast the
// state change intent so that we will not broadcast the intent again in
// other state
- private static final int TURN_ON_CONTINUE = 102;
+ private static final int TURN_ON_CONTINUE = 101;
// Unload firmware, turning off Bluetooth module power
- private static final int TURN_COLD = 103;
+ private static final int TURN_COLD = 102;
// Device disconnecting timeout happens
- private static final int DEVICES_DISCONNECT_TIMEOUT = 104;
+ private static final int DEVICES_DISCONNECT_TIMEOUT = 103;
// Prepare Bluetooth timeout happens
- private static final int PREPARE_BLUETOOTH_TIMEOUT = 105;
+ private static final int PREPARE_BLUETOOTH_TIMEOUT = 104;
// Bluetooth Powerdown timeout happens
- private static final int POWER_DOWN_TIMEOUT = 106;
+ private static final int POWER_DOWN_TIMEOUT = 105;
private Context mContext;
private BluetoothService mBluetoothService;
@@ -156,11 +157,6 @@ final class BluetoothAdapterStateMachine extends StateMachine {
setInitialState(mPowerOff);
mPublicState = BluetoothAdapter.STATE_OFF;
-
- if (mContext.getResources().getBoolean
- (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
- sendMessage(TURN_HOT);
- }
}
/**
diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java
index 2961fd2..5e93b81 100644
--- a/core/java/android/server/BluetoothHealthProfileHandler.java
+++ b/core/java/android/server/BluetoothHealthProfileHandler.java
@@ -45,8 +45,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
final class BluetoothHealthProfileHandler {
private static final String TAG = "BluetoothHealthProfileHandler";
- /*STOPSHIP*/
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static BluetoothHealthProfileHandler sInstance;
private BluetoothService mBluetoothService;
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 63da926..9ca5847 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -313,6 +313,10 @@ public class BluetoothService extends IBluetooth.Stub {
mAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothState = new BluetoothAdapterStateMachine(mContext, this, mAdapter);
mBluetoothState.start();
+ if (mContext.getResources().getBoolean
+ (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
+ mBluetoothState.sendMessage(BluetoothAdapterStateMachine.TURN_HOT);
+ }
mEventLoop = mBluetoothState.getBluetoothEventLoop();
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 713bb91..ba94ab2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -565,7 +565,7 @@ public abstract class WallpaperService extends Service {
mLayout.windowAnimations =
com.android.internal.R.style.Animation_Wallpaper;
mInputChannel = new InputChannel();
- if (mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets,
+ if (mSession.add(mWindow, mWindow.mSeq, mLayout, View.VISIBLE, mContentInsets,
mInputChannel) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
@@ -580,7 +580,7 @@ public abstract class WallpaperService extends Service {
mDrawingAllowed = true;
final int relayoutResult = mSession.relayout(
- mWindow, mLayout, mWidth, mHeight,
+ mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
View.VISIBLE, false, mWinFrame, mContentInsets,
mVisibleInsets, mConfiguration, mSurfaceHolder.mSurface);
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 5126e48..98ab310 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -440,6 +440,9 @@ public class TextToSpeech {
private final Context mContext;
private Connection mServiceConnection;
private OnInitListener mInitListener;
+ // Written from an unspecified application thread, read from
+ // a binder thread.
+ private volatile OnUtteranceCompletedListener mUtteranceCompletedListener;
private final Object mStartLock = new Object();
private String mRequestedEngine;
@@ -1071,20 +1074,8 @@ public class TextToSpeech {
* @return {@link #ERROR} or {@link #SUCCESS}.
*/
public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
- return runAction(new Action<Integer>() {
- @Override
- public Integer run(ITextToSpeechService service) throws RemoteException {
- ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() {
- public void utteranceCompleted(String utteranceId) {
- if (listener != null) {
- listener.onUtteranceCompleted(utteranceId);
- }
- }
- };
- service.setCallback(getPackageName(), callback);
- return SUCCESS;
- }
- }, ERROR, "setOnUtteranceCompletedListener");
+ mUtteranceCompletedListener = listener;
+ return TextToSpeech.SUCCESS;
}
/**
@@ -1137,6 +1128,15 @@ public class TextToSpeech {
private class Connection implements ServiceConnection {
private ITextToSpeechService mService;
+ private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
+ @Override
+ public void utteranceCompleted(String utteranceId) {
+ OnUtteranceCompletedListener listener = mUtteranceCompletedListener;
+ if (listener != null) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
+ }
+ };
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "Connected to " + name);
@@ -1147,7 +1147,13 @@ public class TextToSpeech {
}
mServiceConnection = this;
mService = ITextToSpeechService.Stub.asInterface(service);
- dispatchOnInit(SUCCESS);
+ try {
+ mService.setCallback(getPackageName(), mCallback);
+ dispatchOnInit(SUCCESS);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error connecting to service, setCallback() failed");
+ dispatchOnInit(ERROR);
+ }
}
}
diff --git a/core/java/android/text/CharSequenceIterator.java b/core/java/android/text/CharSequenceIterator.java
deleted file mode 100644
index 4b8ac10..0000000
--- a/core/java/android/text/CharSequenceIterator.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import java.text.CharacterIterator;
-
-/** {@hide} */
-public class CharSequenceIterator implements CharacterIterator {
- private final CharSequence mValue;
-
- private final int mLength;
- private int mIndex;
-
- public CharSequenceIterator(CharSequence value) {
- mValue = value;
- mLength = value.length();
- mIndex = 0;
- }
-
- @Override
- public Object clone() {
- try {
- return super.clone();
- } catch (CloneNotSupportedException e) {
- throw new AssertionError(e);
- }
- }
-
- /** {@inheritDoc} */
- public char current() {
- if (mIndex == mLength) {
- return DONE;
- }
- return mValue.charAt(mIndex);
- }
-
- /** {@inheritDoc} */
- public int getBeginIndex() {
- return 0;
- }
-
- /** {@inheritDoc} */
- public int getEndIndex() {
- return mLength;
- }
-
- /** {@inheritDoc} */
- public int getIndex() {
- return mIndex;
- }
-
- /** {@inheritDoc} */
- public char first() {
- return setIndex(0);
- }
-
- /** {@inheritDoc} */
- public char last() {
- return setIndex(mLength - 1);
- }
-
- /** {@inheritDoc} */
- public char next() {
- if (mIndex == mLength) {
- return DONE;
- }
- return setIndex(mIndex + 1);
- }
-
- /** {@inheritDoc} */
- public char previous() {
- if (mIndex == 0) {
- return DONE;
- }
- return setIndex(mIndex - 1);
- }
-
- /** {@inheritDoc} */
- public char setIndex(int index) {
- if ((index < 0) || (index > mLength)) {
- throw new IllegalArgumentException("Valid range is [" + 0 + "..." + mLength + "]");
- }
- mIndex = index;
- return current();
- }
-}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index c3a2308..f82c9c4 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -76,7 +76,7 @@ extends Layout
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
- spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
+ spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
}
/**
@@ -93,7 +93,7 @@ extends Layout
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad,
- TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
+ TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
super((ellipsize == null)
? display
: (display instanceof Spanned)
@@ -135,8 +135,6 @@ extends Layout
mEllipsize = true;
}
- mMaxLines = maxLines;
-
// Initial state is a single line with 0 characters (0 to 0),
// with top at 0 and bottom at whatever is natural, and
// undefined ellipsis.
@@ -285,7 +283,7 @@ extends Layout
reflowed.generate(text, where, where + after,
getPaint(), getWidth(), getAlignment(), getTextDirectionHeuristic(),
getSpacingMultiplier(), getSpacingAdd(),
- false, true, mEllipsizedWidth, mEllipsizeAt, mMaxLines);
+ false, true, mEllipsizedWidth, mEllipsizeAt);
int n = reflowed.getLineCount();
// If the new layout has a blank line at the end, but it is not
@@ -490,8 +488,6 @@ extends Layout
private int mTopPadding, mBottomPadding;
- private int mMaxLines;
-
private static StaticLayout sStaticLayout = new StaticLayout(null);
private static final Object[] sLock = new Object[0];
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 7c27396..583cbe6 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -139,7 +139,7 @@ public class StaticLayout extends Layout {
generate(source, bufstart, bufend, paint, outerwidth, align, textDir,
spacingmult, spacingadd, includepad, includepad,
- ellipsizedWidth, ellipsize, mMaximumVisibleLineCount);
+ ellipsizedWidth, ellipsize);
mMeasured = MeasuredText.recycle(mMeasured);
mFontMetricsInt = null;
@@ -160,7 +160,7 @@ public class StaticLayout extends Layout {
Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
boolean includepad, boolean trackpad,
- float ellipsizedWidth, TextUtils.TruncateAt ellipsize, int maxLines) {
+ float ellipsizedWidth, TextUtils.TruncateAt ellipsize) {
mLineCount = 0;
int v = 0;
@@ -477,13 +477,13 @@ public class StaticLayout extends Layout {
width = restWidth;
}
}
- if (mLineCount >= maxLines) {
+ if (mLineCount >= mMaximumVisibleLineCount) {
break;
}
}
}
- if (paraEnd != here && mLineCount < maxLines) {
+ if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
paint.getFontMetricsInt(fm);
@@ -514,7 +514,7 @@ public class StaticLayout extends Layout {
}
if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
- mLineCount < maxLines) {
+ mLineCount < mMaximumVisibleLineCount) {
// Log.e("text", "output last " + bufEnd);
paint.getFontMetricsInt(fm);
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index b8728ee..e93039b 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -35,11 +35,11 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
(MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
}
- private int getCurrentLineTop(Spannable buffer, Layout layout) {
+ private static int getCurrentLineTop(Spannable buffer, Layout layout) {
return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
}
- private int getPageHeight(TextView widget) {
+ private static int getPageHeight(TextView widget) {
// This calculation does not take into account the view transformations that
// may have been applied to the child or its containers. In case of scaling or
// rotation, the calculated page height may be incorrect.
@@ -196,14 +196,16 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
/** {@hide} */
@Override
protected boolean leftWord(TextView widget, Spannable buffer) {
- mWordIterator.setCharSequence(buffer);
+ final int selectionEnd = widget.getSelectionEnd();
+ mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
}
/** {@hide} */
@Override
protected boolean rightWord(TextView widget, Spannable buffer) {
- mWordIterator.setCharSequence(buffer);
+ final int selectionEnd = widget.getSelectionEnd();
+ mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer));
}
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index af524ee..239d9e8 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -17,14 +17,9 @@
package android.text.method;
-import android.text.CharSequenceIterator;
-import android.text.Editable;
import android.text.Selection;
-import android.text.Spanned;
-import android.text.TextWatcher;
import java.text.BreakIterator;
-import java.text.CharacterIterator;
import java.util.Locale;
/**
@@ -36,8 +31,11 @@ import java.util.Locale;
* {@hide}
*/
public class WordIterator implements Selection.PositionIterator {
- private CharSequence mCurrent;
- private boolean mCurrentDirty = false;
+ // Size of the window for the word iterator, should be greater than the longest word's length
+ private static final int WINDOW_WIDTH = 50;
+
+ private String mString;
+ private int mOffsetShift;
private BreakIterator mIterator;
@@ -56,70 +54,40 @@ public class WordIterator implements Selection.PositionIterator {
mIterator = BreakIterator.getWordInstance(locale);
}
- private final TextWatcher mWatcher = new TextWatcher() {
- /** {@inheritDoc} */
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // ignored
- }
-
- /** {@inheritDoc} */
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- mCurrentDirty = true;
- }
-
- /** {@inheritDoc} */
- public void afterTextChanged(Editable s) {
- // ignored
- }
- };
-
- public void setCharSequence(CharSequence incoming) {
- // When incoming is different object, move listeners to new sequence
- // and mark as dirty so we reload contents.
- if (mCurrent != incoming) {
- if (mCurrent instanceof Editable) {
- ((Editable) mCurrent).removeSpan(mWatcher);
- }
+ public void setCharSequence(CharSequence charSequence, int start, int end) {
+ mOffsetShift = Math.max(0, start - WINDOW_WIDTH);
+ final int windowEnd = Math.min(charSequence.length(), end + WINDOW_WIDTH);
- if (incoming instanceof Editable) {
- ((Editable) incoming).setSpan(
- mWatcher, 0, incoming.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
- }
-
- mCurrent = incoming;
- mCurrentDirty = true;
- }
-
- if (mCurrentDirty) {
- final CharacterIterator charIterator = new CharSequenceIterator(mCurrent);
- mIterator.setText(charIterator);
-
- mCurrentDirty = false;
- }
+ mString = charSequence.toString().substring(mOffsetShift, windowEnd);
+ mIterator.setText(mString);
}
/** {@inheritDoc} */
public int preceding(int offset) {
+ int shiftedOffset = offset - mOffsetShift;
do {
- offset = mIterator.preceding(offset);
- if (offset == BreakIterator.DONE || isOnLetterOrDigit(offset)) {
- break;
+ shiftedOffset = mIterator.preceding(shiftedOffset);
+ if (shiftedOffset == BreakIterator.DONE) {
+ return BreakIterator.DONE;
+ }
+ if (isOnLetterOrDigit(shiftedOffset)) {
+ return shiftedOffset + mOffsetShift;
}
} while (true);
-
- return offset;
}
/** {@inheritDoc} */
public int following(int offset) {
+ int shiftedOffset = offset - mOffsetShift;
do {
- offset = mIterator.following(offset);
- if (offset == BreakIterator.DONE || isAfterLetterOrDigit(offset)) {
- break;
+ shiftedOffset = mIterator.following(shiftedOffset);
+ if (shiftedOffset == BreakIterator.DONE) {
+ return BreakIterator.DONE;
+ }
+ if (isAfterLetterOrDigit(shiftedOffset)) {
+ return shiftedOffset + mOffsetShift;
}
} while (true);
-
- return offset;
}
/** If <code>offset</code> is within a word, returns the index of the first character of that
@@ -135,17 +103,18 @@ public class WordIterator implements Selection.PositionIterator {
* @throws IllegalArgumentException is offset is not valid.
*/
public int getBeginning(int offset) {
- checkOffsetIsValid(offset);
+ final int shiftedOffset = offset - mOffsetShift;
+ checkOffsetIsValid(shiftedOffset);
- if (isOnLetterOrDigit(offset)) {
- if (mIterator.isBoundary(offset)) {
- return offset;
+ if (isOnLetterOrDigit(shiftedOffset)) {
+ if (mIterator.isBoundary(shiftedOffset)) {
+ return shiftedOffset + mOffsetShift;
} else {
- return mIterator.preceding(offset);
+ return mIterator.preceding(shiftedOffset) + mOffsetShift;
}
} else {
- if (isAfterLetterOrDigit(offset)) {
- return mIterator.preceding(offset);
+ if (isAfterLetterOrDigit(shiftedOffset)) {
+ return mIterator.preceding(shiftedOffset) + mOffsetShift;
}
}
return BreakIterator.DONE;
@@ -164,58 +133,44 @@ public class WordIterator implements Selection.PositionIterator {
* @throws IllegalArgumentException is offset is not valid.
*/
public int getEnd(int offset) {
- checkOffsetIsValid(offset);
+ final int shiftedOffset = offset - mOffsetShift;
+ checkOffsetIsValid(shiftedOffset);
- if (isAfterLetterOrDigit(offset)) {
- if (mIterator.isBoundary(offset)) {
- return offset;
+ if (isAfterLetterOrDigit(shiftedOffset)) {
+ if (mIterator.isBoundary(shiftedOffset)) {
+ return shiftedOffset + mOffsetShift;
} else {
- return mIterator.following(offset);
+ return mIterator.following(shiftedOffset) + mOffsetShift;
}
} else {
- if (isOnLetterOrDigit(offset)) {
- return mIterator.following(offset);
+ if (isOnLetterOrDigit(shiftedOffset)) {
+ return mIterator.following(shiftedOffset) + mOffsetShift;
}
}
return BreakIterator.DONE;
}
- private boolean isAfterLetterOrDigit(int offset) {
- if (offset - 1 >= 0) {
- final char previousChar = mCurrent.charAt(offset - 1);
- if (Character.isLetterOrDigit(previousChar)) return true;
- if (offset - 2 >= 0) {
- final char previousPreviousChar = mCurrent.charAt(offset - 2);
- if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
- final int codePoint = Character.toCodePoint(previousPreviousChar, previousChar);
- return Character.isLetterOrDigit(codePoint);
- }
- }
+ private boolean isAfterLetterOrDigit(int shiftedOffset) {
+ if (shiftedOffset >= 1 && shiftedOffset <= mString.length()) {
+ final int codePoint = mString.codePointBefore(shiftedOffset);
+ if (Character.isLetterOrDigit(codePoint)) return true;
}
return false;
}
- private boolean isOnLetterOrDigit(int offset) {
- final int length = mCurrent.length();
- if (offset < length) {
- final char currentChar = mCurrent.charAt(offset);
- if (Character.isLetterOrDigit(currentChar)) return true;
- if (offset + 1 < length) {
- final char nextChar = mCurrent.charAt(offset + 1);
- if (Character.isSurrogatePair(currentChar, nextChar)) {
- final int codePoint = Character.toCodePoint(currentChar, nextChar);
- return Character.isLetterOrDigit(codePoint);
- }
- }
+ private boolean isOnLetterOrDigit(int shiftedOffset) {
+ if (shiftedOffset >= 0 && shiftedOffset < mString.length()) {
+ final int codePoint = mString.codePointAt(shiftedOffset);
+ if (Character.isLetterOrDigit(codePoint)) return true;
}
return false;
}
- private void checkOffsetIsValid(int offset) {
- if (offset < 0 || offset > mCurrent.length()) {
- final String message = "Invalid offset: " + offset +
- ". Valid range is [0, " + mCurrent.length() + "]";
- throw new IllegalArgumentException(message);
+ private void checkOffsetIsValid(int shiftedOffset) {
+ if (shiftedOffset < 0 || shiftedOffset > mString.length()) {
+ throw new IllegalArgumentException("Invalid offset: " + (shiftedOffset + mOffsetShift) +
+ ". Valid range is [" + mOffsetShift + ", " + (mString.length() + mOffsetShift) +
+ "]");
}
}
}
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 1379dd2..6e95016 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -56,6 +56,14 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
*/
public static final int FLAG_MISSPELLED = 0x0002;
+ /**
+ * Sets this flag if the auto correction is about to be applied to a word/text
+ * that the user is typing/composing. This type of suggestion is rendered differently
+ * to indicate the auto correction is happening.
+ * @hide
+ */
+ public static final int FLAG_AUTO_CORRECTION = 0x0004;
+
public static final String ACTION_SUGGESTION_PICKED = "android.text.style.SUGGESTION_PICKED";
public static final String SUGGESTION_SPAN_PICKED_AFTER = "after";
public static final String SUGGESTION_SPAN_PICKED_BEFORE = "before";
@@ -82,6 +90,9 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
private float mMisspelledUnderlineThickness;
private int mMisspelledUnderlineColor;
+ private float mAutoCorrectionUnderlineThickness;
+ private int mAutoCorrectionUnderlineColor;
+
/*
* TODO: If switching IME is required, needs to add parameters for ids of InputMethodInfo
* and InputMethodSubtype.
@@ -145,14 +156,21 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
defStyle = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion;
-
typedArray = context.obtainStyledAttributes(
null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0);
-
mEasyCorrectUnderlineThickness = typedArray.getDimension(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
mEasyCorrectUnderlineColor = typedArray.getColor(
com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+
+ defStyle = com.android.internal.R.attr.textAppearanceAutoCorrectionSuggestion;
+ typedArray = context.obtainStyledAttributes(
+ null, com.android.internal.R.styleable.SuggestionSpan, defStyle, 0);
+ mAutoCorrectionUnderlineThickness = typedArray.getDimension(
+ com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0);
+ mAutoCorrectionUnderlineColor = typedArray.getColor(
+ com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK);
+
}
public SuggestionSpan(Parcel src) {
@@ -165,6 +183,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mEasyCorrectUnderlineThickness = src.readFloat();
mMisspelledUnderlineColor = src.readInt();
mMisspelledUnderlineThickness = src.readFloat();
+ mAutoCorrectionUnderlineColor = src.readInt();
+ mAutoCorrectionUnderlineThickness = src.readFloat();
}
/**
@@ -218,6 +238,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
dest.writeFloat(mEasyCorrectUnderlineThickness);
dest.writeInt(mMisspelledUnderlineColor);
dest.writeFloat(mMisspelledUnderlineThickness);
+ dest.writeInt(mAutoCorrectionUnderlineColor);
+ dest.writeFloat(mAutoCorrectionUnderlineThickness);
}
@Override
@@ -261,6 +283,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
public void updateDrawState(TextPaint tp) {
final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0;
final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0;
+ final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0;
if (easy) {
if (!misspelled) {
tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness);
@@ -269,6 +292,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
// than just easy, do not apply misspelled if an easy (or a mispelled) has been set
tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness);
}
+ } else if (autoCorrection) {
+ tp.setUnderlineText(mAutoCorrectionUnderlineColor, mAutoCorrectionUnderlineThickness);
}
}
@@ -281,12 +306,15 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
// The order here should match what is used in updateDrawState
final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0;
final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0;
+ final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0;
if (easy) {
if (!misspelled) {
return mEasyCorrectUnderlineColor;
} else {
return mMisspelledUnderlineColor;
}
+ } else if (autoCorrection) {
+ return mAutoCorrectionUnderlineColor;
}
return 0;
}
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
index e954983..34e7d4d 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -153,6 +153,18 @@ public abstract class ActionMode {
public abstract MenuInflater getMenuInflater();
/**
+ * Returns whether the UI presenting this action mode can take focus or not.
+ * This is used by internal components within the framework that would otherwise
+ * present an action mode UI that requires focus, such as an EditText as a custom view.
+ *
+ * @return true if the UI used to show this action mode can take focus
+ * @hide Internal use only
+ */
+ public boolean isUiFocusable() {
+ return true;
+ }
+
+ /**
* Callback interface for action modes. Supplied to
* {@link View#startActionMode(Callback)}, a Callback
* configures and handles events raised by a user's interaction with an action mode.
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/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 23d1b0f..b86d21d 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -184,6 +184,22 @@ public abstract class HardwareRenderer {
abstract void setup(int width, int height);
/**
+ * Gets the current width of the surface. This is the width that the surface
+ * was last set to in a call to {@link #setup(int, int)}.
+ *
+ * @return the current width of the surface
+ */
+ abstract int getWidth();
+
+ /**
+ * Gets the current height of the surface. This is the height that the surface
+ * was last set to in a call to {@link #setup(int, int)}.
+ *
+ * @return the current width of the surface
+ */
+ abstract int getHeight();
+
+ /**
* Interface used to receive callbacks whenever a view is drawn by
* a hardware renderer instance.
*/
@@ -362,6 +378,7 @@ public abstract class HardwareRenderer {
static EGLDisplay sEglDisplay;
static EGLConfig sEglConfig;
static final Object[] sEglLock = new Object[0];
+ int mWidth = -1, mHeight = -1;
static final ThreadLocal<EGLContext> sEglContextStorage = new ThreadLocal<EGLContext>();
@@ -714,9 +731,21 @@ public abstract class HardwareRenderer {
void setup(int width, int height) {
if (validate()) {
mCanvas.setViewport(width, height);
+ mWidth = width;
+ mHeight = height;
}
}
+ @Override
+ int getWidth() {
+ return mWidth;
+ }
+
+ @Override
+ int getHeight() {
+ return mHeight;
+ }
+
boolean canDraw() {
return mGl != null && mCanvas != null;
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 0e482d6..715fa7b 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -74,5 +74,6 @@ oneway interface IWindow {
/**
* System chrome visibility changes
*/
- void dispatchSystemUiVisibilityChanged(int visibility);
+ void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+ int localValue, int localChanges);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 45f9da2..fe32a5f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -192,11 +192,12 @@ interface IWindowManager
int getPreferredOptionsPanelGravity();
/**
- * Lock the device orientation to the current rotation. Sensor input will
- * be ignored until thawRotation() is called.
+ * Lock the device orientation to the specified rotation, or to the
+ * current rotation if -1. Sensor input will be ignored until
+ * thawRotation() is called.
* @hide
*/
- void freezeRotation();
+ void freezeRotation(int rotation);
/**
* Release the orientation lock imposed by freezeRotation().
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 990af08..282d7be 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -34,10 +34,10 @@ import android.view.Surface;
* {@hide}
*/
interface IWindowSession {
- int add(IWindow window, in WindowManager.LayoutParams attrs,
+ int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets,
out InputChannel outInputChannel);
- int addWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
+ int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets);
void remove(IWindow window);
@@ -49,6 +49,7 @@ interface IWindowSession {
* to draw the window's contents.
*
* @param window The window being modified.
+ * @param seq Ordering sequence number.
* @param attrs If non-null, new attributes to apply to the window.
* @param requestedWidth The width the window wants to be.
* @param requestedHeight The height the window wants to be.
@@ -77,7 +78,7 @@ interface IWindowSession {
* @return int Result flags: {@link WindowManagerImpl#RELAYOUT_SHOW_FOCUS},
* {@link WindowManagerImpl#RELAYOUT_FIRST_TIME}.
*/
- int relayout(IWindow window, in WindowManager.LayoutParams attrs,
+ int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
boolean insetsPending, out Rect outFrame, out Rect outContentInsets,
out Rect outVisibleInsets, out Configuration outConfig,
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index cbdb38e..9a57ea0 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -266,7 +266,7 @@ public class SurfaceView extends View {
try {
DisplayMetrics metrics = getResources().getDisplayMetrics();
mLayout.x = metrics.widthPixels * 3;
- mSession.relayout(mWindow, mLayout, mWidth, mHeight, VISIBLE, false,
+ mSession.relayout(mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, VISIBLE, false,
mWinFrame, mContentInsets, mVisibleInsets, mConfiguration, mSurface);
} catch (RemoteException e) {
// Ignore
@@ -492,7 +492,7 @@ public class SurfaceView extends View {
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
mLayout.gravity = Gravity.LEFT|Gravity.TOP;
- mSession.addWithoutInputChannel(mWindow, mLayout,
+ mSession.addWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
mVisible ? VISIBLE : GONE, mContentInsets);
}
@@ -513,7 +513,7 @@ public class SurfaceView extends View {
mDrawingStopped = !visible;
final int relayoutResult = mSession.relayout(
- mWindow, mLayout, mWidth, mHeight,
+ mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
mVisibleInsets, mConfiguration, mSurface);
if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
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 d193d6e..86be28a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -94,20 +94,10 @@ import java.util.concurrent.CopyOnWriteArrayList;
* their layout properties.
* </p>
*
- * <div class="special">
- * <p>For an introduction to using this class to develop your
- * application's user interface, read the Developer Guide documentation on
- * <strong><a href="{@docRoot}guide/topics/ui/index.html">User Interface</a></strong>. Special topics
- * include:
- * <br/><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Declaring Layout</a>
- * <br/><a href="{@docRoot}guide/topics/ui/menus.html">Creating Menus</a>
- * <br/><a href="{@docRoot}guide/topics/ui/layout-objects.html">Common Layout Objects</a>
- * <br/><a href="{@docRoot}guide/topics/ui/binding.html">Binding to Data with AdapterView</a>
- * <br/><a href="{@docRoot}guide/topics/ui/ui-events.html">Handling UI Events</a>
- * <br/><a href="{@docRoot}guide/topics/ui/themes.html">Applying Styles and Themes</a>
- * <br/><a href="{@docRoot}guide/topics/ui/custom-components.html">Building Custom Components</a>
- * <br/><a href="{@docRoot}guide/topics/ui/how-android-draws.html">How Android Draws Views</a>.
- * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about using this class to develop your application's user interface,
+ * read the <a href="{@docRoot}guide/topics/ui/index.html">User Interface</a> developer guide.
* </div>
*
* <a name="Using"></a>
@@ -1487,6 +1477,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Accessibility event types that are dispatched for text population.
+ */
+ private static final int POPULATING_ACCESSIBILITY_EVENT_TYPES =
+ AccessibilityEvent.TYPE_VIEW_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_SELECTED
+ | AccessibilityEvent.TYPE_VIEW_FOCUSED
+ | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+ | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+ | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED;
+
+ /**
* Temporary Rect currently for use in setBackground(). This will probably
* be extended in the future to hold our own class with more than just
* a Rect. :)
@@ -1923,6 +1926,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF;
/**
+ * These are the system UI flags that can be cleared by events outside
+ * of an application. Currently this is just the ability to tap on the
+ * screen while hiding the navigation bar to have it return.
+ * @hide
+ */
+ public static final int SYSTEM_UI_CLEARABLE_FLAGS =
+ SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+
+ /**
* Find views that render the specified text.
*
* @see #findViewsWithText(ArrayList, CharSequence, int)
@@ -3333,7 +3345,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (mOnLayoutChangeListeners == null) {
mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>();
}
- mOnLayoutChangeListeners.add(listener);
+ if (!mOnLayoutChangeListeners.contains(listener)) {
+ mOnLayoutChangeListeners.add(listener);
+ }
}
/**
@@ -3855,7 +3869,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return;
}
onInitializeAccessibilityEvent(event);
- dispatchPopulateAccessibilityEvent(event);
+ // Only a subset of accessibility events populates text content.
+ if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
+ dispatchPopulateAccessibilityEvent(event);
+ }
// In the beginning we called #isShown(), so we know that getParent() is not null.
getParent().requestSendAccessibilityEvent(this, event);
}
@@ -3876,6 +3893,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link AccessibilityDelegate#dispatchPopulateAccessibilityEvent(View, AccessibilityEvent)}
* is responsible for handling this call.
* </p>
+ * <p>
+ * <em>Note:</em> Accessibility events of certain types are not dispatched for
+ * populating the event text via this method. For details refer to {@link AccessibilityEvent}.
+ * </p>
*
* @param event The event.
*
@@ -3895,12 +3916,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* Note: Called from the default {@link AccessibilityDelegate}.
*/
boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- // Do not populate text to scroll events. They describe position change
- // and usually come from container with a lot of text which is not very
- // informative for accessibility purposes. Also they are fired frequently.
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- return true;
- }
onPopulateAccessibilityEvent(event);
return false;
}
@@ -3998,23 +4013,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
event.setEnabled(isEnabled());
event.setContentDescription(mContentDescription);
- final int eventType = event.getEventType();
- switch (eventType) {
- case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
- if (mAttachInfo != null) {
- ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList;
- getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
- FOCUSABLES_ALL);
- event.setItemCount(focusablesTempList.size());
- event.setCurrentItemIndex(focusablesTempList.indexOf(this));
- focusablesTempList.clear();
- }
- } break;
- case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
- event.setScrollX(mScrollX);
- event.setScrollY(mScrollY);
- event.setItemCount(getHeight());
- } break;
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
+ ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList;
+ getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
+ FOCUSABLES_ALL);
+ event.setItemCount(focusablesTempList.size());
+ event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+ focusablesTempList.clear();
}
}
@@ -10121,6 +10126,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();
@@ -12093,6 +12106,39 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Finds a view by its unuque and stable accessibility id.
+ *
+ * @param accessibilityId The searched accessibility id.
+ * @return The found view.
+ */
+ final View findViewByAccessibilityId(int accessibilityId) {
+ if (accessibilityId < 0) {
+ return null;
+ }
+ return findViewByAccessibilityIdTraversal(accessibilityId);
+ }
+
+ /**
+ * Performs the traversal to find a view by its unuque and stable accessibility id.
+ *
+ * <strong>Note:</strong>This method does not stop at the root namespace
+ * boundary since the user can touch the screen at an arbitrary location
+ * potentially crossing the root namespace bounday which will send an
+ * accessibility event to accessibility services and they should be able
+ * to obtain the event source. Also accessibility ids are guaranteed to be
+ * unique in the window.
+ *
+ * @param accessibilityId The accessibility id.
+ * @return The found view.
+ */
+ View findViewByAccessibilityIdTraversal(int accessibilityId) {
+ if (getAccessibilityViewId() == accessibilityId) {
+ return this;
+ }
+ return null;
+ }
+
+ /**
* Look for a child view with the given tag. If this view has the given
* tag, return this view.
*
@@ -12991,6 +13037,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Dispatch callbacks to {@link #setOnSystemUiVisibilityChangeListener} down
+ * the view hierarchy.
*/
public void dispatchSystemUiVisibilityChanged(int visibility) {
if (mOnSystemUiVisibilityChangeListener != null) {
@@ -12999,6 +13047,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
}
+ void updateLocalSystemUiVisibility(int localValue, int localChanges) {
+ int val = (mSystemUiVisibility&~localChanges) | (localValue&localChanges);
+ if (val != mSystemUiVisibility) {
+ setSystemUiVisibility(val);
+ }
+ }
+
/**
* Creates an image that the system displays during the drag and drop
* operation. This is called a &quot;drag shadow&quot;. The default implementation
@@ -14072,7 +14127,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
/**
* Interface definition for a callback to be invoked when the status bar changes
- * visibility.
+ * visibility. This reports <strong>global</strong> changes to the system UI
+ * state, not just what the application is requesting.
*
* @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener)
*/
@@ -14082,7 +14138,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link View#setSystemUiVisibility(int)}.
*
* @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or
- * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}.
+ * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. This tells you the
+ * <strong>global</strong> state of the UI visibility flags, not what your
+ * app is currently applying.
*/
public void onSystemUiVisibilityChange(int visibility);
}
@@ -14340,6 +14398,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
boolean mRecomputeGlobalAttributes;
/**
+ * Always report new attributes at next traversal.
+ */
+ boolean mForceReportNewAttributes;
+
+ /**
* Set during a traveral if any views want to keep the screen on.
*/
boolean mKeepScreenOn;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index fb0d80a..9266ae2 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -821,6 +821,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ @Override
+ View findViewByAccessibilityIdTraversal(int accessibilityId) {
+ View foundView = super.findViewByAccessibilityIdTraversal(accessibilityId);
+ if (foundView != null) {
+ return foundView;
+ }
+ final int childrenCount = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ foundView = child.findViewByAccessibilityIdTraversal(accessibilityId);
+ if (foundView != null) {
+ return foundView;
+ }
+ }
+ return null;
+ }
+
/**
* {@inheritDoc}
*/
@@ -1182,6 +1200,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ @Override
+ void updateLocalSystemUiVisibility(int localValue, int localChanges) {
+ super.updateLocalSystemUiVisibility(localValue, localChanges);
+
+ final int count = mChildrenCount;
+ final View[] children = mChildren;
+ for (int i=0; i <count; i++) {
+ final View child = children[i];
+ child.updateLocalSystemUiVisibility(localValue, localChanges);
+ }
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9cb4e5e..7eae739 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -74,7 +74,6 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import com.android.internal.policy.PolicyManager;
-import com.android.internal.util.Predicate;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
@@ -166,6 +165,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
final int mTargetSdkVersion;
+ int mSeq;
+
View mView;
View mFocusedView;
View mRealFocusedView; // this is not set to null in touch mode
@@ -309,6 +310,13 @@ public final class ViewRootImpl extends Handler implements ViewParent,
return sWindowSession;
}
}
+
+ static final class SystemUiVisibilityInfo {
+ int seq;
+ int globalVisibility;
+ int localValue;
+ int localChanges;
+ }
public ViewRootImpl(Context context) {
super();
@@ -466,7 +474,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
try {
mOrigWindowType = mWindowAttributes.type;
- res = sWindowSession.add(mWindow, mWindowAttributes,
+ res = sWindowSession.add(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets,
mInputChannel);
} catch (RemoteException e) {
@@ -861,7 +869,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
params = lp;
- windowAttributesChanges |= WindowManager.LayoutParams.BUFFER_CHANGED;
fullRedrawNeeded = true;
mLayoutRequested = true;
if (mLastInCompatMode) {
@@ -1046,16 +1053,21 @@ public final class ViewRootImpl extends Handler implements ViewParent,
attachInfo.mRecomputeGlobalAttributes = false;
boolean oldScreenOn = attachInfo.mKeepScreenOn;
int oldVis = attachInfo.mSystemUiVisibility;
+ boolean oldHasSystemUiListeners = attachInfo.mHasSystemUiListeners;
attachInfo.mKeepScreenOn = false;
attachInfo.mSystemUiVisibility = 0;
attachInfo.mHasSystemUiListeners = false;
host.dispatchCollectViewAttributes(0);
if (attachInfo.mKeepScreenOn != oldScreenOn
|| attachInfo.mSystemUiVisibility != oldVis
- || attachInfo.mHasSystemUiListeners) {
+ || attachInfo.mHasSystemUiListeners != oldHasSystemUiListeners) {
params = lp;
}
}
+ if (attachInfo.mForceReportNewAttributes) {
+ attachInfo.mForceReportNewAttributes = false;
+ params = lp;
+ }
if (mFirst || attachInfo.mViewVisibilityChanged) {
attachInfo.mViewVisibilityChanged = false;
@@ -1079,7 +1091,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
resizeMode;
params = lp;
- windowAttributesChanges |= WindowManager.LayoutParams.BUFFER_CHANGED;
}
}
}
@@ -1137,9 +1148,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
}
params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility;
- params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners
- || params.subtreeSystemUiVisibility != 0
- || params.systemUiVisibility != 0;
+ params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners;
}
if (DEBUG_LAYOUT) {
Log.i(TAG, "host=w:" + host.getMeasuredWidth() + ", h:" +
@@ -1376,13 +1385,15 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
- if (hwInitialized || ((windowShouldResize || (params != null &&
- (windowAttributesChanges & WindowManager.LayoutParams.BUFFER_CHANGED) != 0)) &&
- mAttachInfo.mHardwareRenderer != null &&
- mAttachInfo.mHardwareRenderer.isEnabled())) {
- mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight);
- if (!hwInitialized && mAttachInfo.mHardwareRenderer.isEnabled()) {
- mAttachInfo.mHardwareRenderer.invalidate(mHolder);
+ if (mAttachInfo.mHardwareRenderer != null &&
+ mAttachInfo.mHardwareRenderer.isEnabled()) {
+ if (hwInitialized || windowShouldResize ||
+ mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
+ mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
+ mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight);
+ if (!hwInitialized) {
+ mAttachInfo.mHardwareRenderer.invalidate(mHolder);
+ }
}
}
@@ -2546,7 +2557,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
handleDragEvent(event);
} break;
case DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged(msg.arg1);
+ handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo)msg.obj);
} break;
case UPDATE_CONFIGURATION: {
Configuration config = (Configuration)msg.obj;
@@ -3430,12 +3441,27 @@ public final class ViewRootImpl extends Handler implements ViewParent,
event.recycle();
}
- public void handleDispatchSystemUiVisibilityChanged(int visibility) {
+ public void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
+ if (mSeq != args.seq) {
+ // The sequence has changed, so we need to update our value and make
+ // sure to do a traversal afterward so the window manager is given our
+ // most recent data.
+ mSeq = args.seq;
+ mAttachInfo.mForceReportNewAttributes = true;
+ scheduleTraversals();
+ }
if (mView == null) return;
- if (mAttachInfo != null) {
- mAttachInfo.mSystemUiVisibility = visibility;
+ if (args.localChanges != 0) {
+ if (mAttachInfo != null) {
+ mAttachInfo.mSystemUiVisibility =
+ (mAttachInfo.mSystemUiVisibility&~args.localChanges)
+ | (args.localValue&args.localChanges);
+ }
+ mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
+ mAttachInfo.mRecomputeGlobalAttributes = true;
+ scheduleTraversals();
}
- mView.dispatchSystemUiVisibilityChanged(visibility);
+ mView.dispatchSystemUiVisibilityChanged(args.globalVisibility);
}
public void getLastTouchPoint(Point outLocation) {
@@ -3494,7 +3520,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
int relayoutResult = sWindowSession.relayout(
- mWindow, params,
+ mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending, mWinFrame,
@@ -3797,8 +3823,14 @@ public final class ViewRootImpl extends Handler implements ViewParent,
sendMessage(msg);
}
- public void dispatchSystemUiVisibilityChanged(int visibility) {
- sendMessage(obtainMessage(DISPATCH_SYSTEM_UI_VISIBILITY, visibility, 0));
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+ int localValue, int localChanges) {
+ SystemUiVisibilityInfo args = new SystemUiVisibilityInfo();
+ args.seq = seq;
+ args.globalVisibility = globalVisibility;
+ args.localValue = localValue;
+ args.localChanges = localChanges;
+ sendMessage(obtainMessage(DISPATCH_SYSTEM_UI_VISIBILITY, args));
}
/**
@@ -4053,10 +4085,12 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
- public void dispatchSystemUiVisibilityChanged(int visibility) {
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalVisibility,
+ int localValue, int localChanges) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchSystemUiVisibilityChanged(visibility);
+ viewAncestor.dispatchSystemUiVisibilityChanged(seq, globalVisibility,
+ localValue, localChanges);
}
}
}
@@ -4462,9 +4496,6 @@ public final class ViewRootImpl extends Handler implements ViewParent,
final class AccessibilityInteractionController {
private static final int POOL_SIZE = 5;
- private FindByAccessibilitytIdPredicate mFindByAccessibilityIdPredicate =
- new FindByAccessibilitytIdPredicate();
-
private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
@@ -4551,11 +4582,8 @@ public final class ViewRootImpl extends Handler implements ViewParent,
AccessibilityNodeInfo info = null;
try {
- FindByAccessibilitytIdPredicate predicate = mFindByAccessibilityIdPredicate;
- predicate.init(accessibilityId);
- View root = ViewRootImpl.this.mView;
- View target = root.findViewByPredicate(predicate);
- if (target != null && target.getVisibility() == View.VISIBLE) {
+ View target = findViewByAccessibilityId(accessibilityId);
+ if (target != null) {
info = target.createAccessibilityNodeInfo();
}
} finally {
@@ -4794,25 +4822,12 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (root == null) {
return null;
}
- mFindByAccessibilityIdPredicate.init(accessibilityId);
- View foundView = root.findViewByPredicate(mFindByAccessibilityIdPredicate);
- if (foundView == null || foundView.getVisibility() != View.VISIBLE) {
+ View foundView = root.findViewByAccessibilityId(accessibilityId);
+ if (foundView != null && foundView.getVisibility() != View.VISIBLE) {
return null;
}
return foundView;
}
-
- private final class FindByAccessibilitytIdPredicate implements Predicate<View> {
- public int mSearchedId;
-
- public void init(int searchedId) {
- mSearchedId = searchedId;
- }
-
- public boolean apply(View view) {
- return (view.getAccessibilityViewId() == mSearchedId);
- }
- }
}
private class SendWindowContentChangedAccessibilityEvent implements Runnable {
@@ -4820,18 +4835,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void run() {
if (mView != null) {
- // Check again for accessibility state since this is executed delayed.
- AccessibilityManager accessibilityManager =
- AccessibilityManager.getInstance(mView.mContext);
- if (accessibilityManager.isEnabled()) {
- // Send the event directly since we do not want to append the
- // source text because this is the text for the entire window
- // and we just want to notify that the content has changed.
- AccessibilityEvent event = AccessibilityEvent.obtain(
- AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- mView.onInitializeAccessibilityEvent(event);
- accessibilityManager.sendAccessibilityEvent(event);
- }
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
mIsPending = false;
}
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 17a516c..e8ab227 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -416,6 +416,13 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
/**
+ * Window type: Fake window to consume touch events when the navigation
+ * bar is hidden.
+ * @hide
+ */
+ public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
@@ -1260,8 +1267,6 @@ public interface WindowManager extends ViewManager {
/** {@hide} */
public static final int PRIVATE_FLAGS_CHANGED = 1<<16;
/** {@hide} */
- public static final int BUFFER_CHANGED = 1<<17;
- /** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
// internal buffer to backup/restore parameters under compatibility mode.
@@ -1272,11 +1277,11 @@ public interface WindowManager extends ViewManager {
if (width != o.width) {
width = o.width;
- changes |= LAYOUT_CHANGED | BUFFER_CHANGED;
+ changes |= LAYOUT_CHANGED;
}
if (height != o.height) {
height = o.height;
- changes |= LAYOUT_CHANGED | BUFFER_CHANGED;
+ changes |= LAYOUT_CHANGED;
}
if (x != o.x) {
x = o.x;
@@ -1288,19 +1293,19 @@ public interface WindowManager extends ViewManager {
}
if (horizontalWeight != o.horizontalWeight) {
horizontalWeight = o.horizontalWeight;
- changes |= LAYOUT_CHANGED | BUFFER_CHANGED;
+ changes |= LAYOUT_CHANGED;
}
if (verticalWeight != o.verticalWeight) {
verticalWeight = o.verticalWeight;
- changes |= LAYOUT_CHANGED | BUFFER_CHANGED;
+ changes |= LAYOUT_CHANGED;
}
if (horizontalMargin != o.horizontalMargin) {
horizontalMargin = o.horizontalMargin;
- changes |= LAYOUT_CHANGED | BUFFER_CHANGED;
+ changes |= LAYOUT_CHANGED;
}
if (verticalMargin != o.verticalMargin) {
verticalMargin = o.verticalMargin;
- changes |= LAYOUT_CHANGED | BUFFER_CHANGED;
+ changes |= LAYOUT_CHANGED;
}
if (type != o.type) {
type = o.type;
@@ -1308,7 +1313,7 @@ public interface WindowManager extends ViewManager {
}
if (flags != o.flags) {
flags = o.flags;
- changes |= FLAGS_CHANGED | BUFFER_CHANGED;
+ changes |= FLAGS_CHANGED;
}
if (privateFlags != o.privateFlags) {
privateFlags = o.privateFlags;
@@ -1320,11 +1325,11 @@ public interface WindowManager extends ViewManager {
}
if (gravity != o.gravity) {
gravity = o.gravity;
- changes |= LAYOUT_CHANGED | BUFFER_CHANGED;
+ changes |= LAYOUT_CHANGED;
}
if (format != o.format) {
format = o.format;
- changes |= FORMAT_CHANGED | BUFFER_CHANGED;
+ changes |= FORMAT_CHANGED;
}
if (windowAnimations != o.windowAnimations) {
windowAnimations = o.windowAnimations;
@@ -1363,7 +1368,7 @@ public interface WindowManager extends ViewManager {
if (screenOrientation != o.screenOrientation) {
screenOrientation = o.screenOrientation;
- changes |= SCREEN_ORIENTATION_CHANGED | BUFFER_CHANGED;
+ changes |= SCREEN_ORIENTATION_CHANGED;
}
if (systemUiVisibility != o.systemUiVisibility
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 4f67675..17bdff2 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -23,6 +23,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.os.IBinder;
import android.os.LocalPowerManager;
+import android.os.Looper;
import android.view.animation.Animation;
import java.io.FileDescriptor;
@@ -103,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;
@@ -237,6 +238,12 @@ public interface WindowManagerPolicy {
public WindowManager.LayoutParams getAttrs();
/**
+ * Retrieve the current system UI visibility flags associated with
+ * this window.
+ */
+ public int getSystemUiVisibility();
+
+ /**
* Get the layer at which this window's surface will be Z-ordered.
*/
public int getSurfaceLayer();
@@ -309,6 +316,36 @@ public interface WindowManagerPolicy {
}
/**
+ * Representation of a "fake window" that the policy has added to the
+ * window manager to consume events.
+ */
+ public interface FakeWindow {
+ /**
+ * Remove the fake window from the window manager.
+ */
+ void dismiss();
+ }
+
+ /**
+ * Interface for calling back in to the window manager that is private
+ * between it and the policy.
+ */
+ public interface WindowManagerFuncs {
+ /**
+ * Ask the window manager to re-evaluate the system UI flags.
+ */
+ public void reevaluateStatusBarVisibility();
+
+ /**
+ * Add a fake window to the window manager. This window sits
+ * at the top of the other windows and consumes events.
+ */
+ public FakeWindow addFakeWindow(Looper looper, InputHandler inputHandler,
+ String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys,
+ boolean hasFocus, boolean touchFullscreen);
+ }
+
+ /**
* Bit mask that is set for all enter transition.
*/
public final int TRANSIT_ENTER_MASK = 0x1000;
@@ -389,6 +426,7 @@ public interface WindowManagerPolicy {
* @param powerManager
*/
public void init(Context context, IWindowManager windowManager,
+ WindowManagerFuncs windowManagerFuncs,
LocalPowerManager powerManager);
/**
@@ -639,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
@@ -756,7 +796,7 @@ public interface WindowManagerPolicy {
/**
* A new window has been focused.
*/
- public void focusChanged(WindowState lastFocus, WindowState newFocus);
+ public int focusChangedLw(WindowState lastFocus, WindowState newFocus);
/**
* Called after the screen turns off.
@@ -962,6 +1002,14 @@ public interface WindowManagerPolicy {
public void setUserRotationMode(int mode, int rotation);
/**
+ * Called when a new system UI visibility is being reported, allowing
+ * the policy to adjust what is actually reported.
+ * @param visibility The raw visiblity reported by the status bar.
+ * @return The new desired visibility.
+ */
+ public int adjustSystemUiVisibilityLw(int visibility);
+
+ /**
* Print the WindowManagerPolicy's state into the given stream.
*
* @param prefix Text to print at the front of each line.
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c93b564..86dd9df 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -69,14 +69,26 @@ import java.util.List;
* <em>Type:</em>{@link #TYPE_VIEW_CLICKED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
* <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* <li>{@link #isPassword()} - Whether the source is password.</li>
* <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -85,14 +97,26 @@ import java.util.List;
* <em>Type:</em>{@link #TYPE_VIEW_LONG_CLICKED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
* <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* <li>{@link #isPassword()} - Whether the source is password.</li>
* <li>{@link #isChecked()} - Whether the source is checked.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -101,16 +125,28 @@ import java.util.List;
* <em>Type:</em> {@link #TYPE_VIEW_SELECTED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
* <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* <li>{@link #isPassword()} - Whether the source is password.</li>
* <li>{@link #isChecked()} - Whether the source is checked.</li>
* <li>{@link #getItemCount()} - The number of selectable items of the source.</li>
* <li>{@link #getCurrentItemIndex()} - The currently selected item index.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -119,16 +155,28 @@ import java.util.List;
* <em>Type:</em> {@link #TYPE_VIEW_FOCUSED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
* <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* <li>{@link #isPassword()} - Whether the source is password.</li>
* <li>{@link #isChecked()} - Whether the source is checked.</li>
* <li>{@link #getItemCount()} - The number of focusable items on the screen.</li>
* <li>{@link #getCurrentItemIndex()} - The currently focused item index.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
* </ul>
* </p>
* <p>
@@ -137,6 +185,7 @@ import java.util.List;
* <em>Type:</em> {@link #TYPE_VIEW_TEXT_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
@@ -149,6 +198,7 @@ import java.util.List;
* <li>{@link #getAddedCount()} - The number of added characters.</li>
* <li>{@link #getRemovedCount()} - The number of removed characters.</li>
* <li>{@link #getBeforeText()} - The text of the source before the change.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
* </ul>
* </p>
* <p>
@@ -157,96 +207,236 @@ import java.util.List;
* <em>Type:</em> {@link #TYPE_VIEW_TEXT_SELECTION_CHANGED} </br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
* <li>{@link #getText()} - The text of the source.</li>
- * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* <li>{@link #isPassword()} - Whether the source is password.</li>
* <li>{@link #getFromIndex()} - The selection start index.</li>
* <li>{@link #getToIndex()} - The selection end index.</li>
* <li>{@link #getItemCount()} - The length of the source text.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</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 scrolled</b> - represents the event of scrolling a view. If
* the source is a descendant of {@link android.widget.AdapterView} the
* scroll is reported in terms of visible items - the first visible item,
* the last visible item, and the total items - because the the source
- * is unaware if its pixel size since its adapter is responsible for
+ * is unaware of its pixel size since its adapter is responsible for
* creating views. In all other cases the scroll is reported as the current
* scroll on the X and Y axis respectively plus the height of the source in
* pixels.</br>
* <em>Type:</em> {@link #TYPE_VIEW_SCROLLED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
* <li>{@link #isEnabled()} - Whether the source is enabled.</li>
- * <li>{@link #getScrollX()} - The horizontal offset of the source
- * (without descendants of AdapterView)).</li>
- * <li>{@link #getScrollY()} - The vertical offset of the source
- * (without descendants of AdapterView)).</li>
- * <li>{@link #getFromIndex()} - The index of the first visible item of the source
- * (for descendants of AdapterView).</li>
- * <li>{@link #getToIndex()} - The index of the last visible item of the source
- * (for descendants of AdapterView).</li>
- * <li>{@link #getItemCount()} - The total items of the source (for descendants of AdapterView)
- * or the height of the source in pixels (all other cases).</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</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>TRANSITION TYPES</b></br>
* </p>
+ * <p>
* <b>Window state changed</b> - represents the event of opening a
* {@link android.widget.PopupWindow}, {@link android.view.Menu},
* {@link android.app.Dialog}, etc.</br>
* <em>Type:</em> {@link #TYPE_WINDOW_STATE_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
* </ul>
* </p>
* <p>
* <b>Window content changed</b> - represents the event of change in the
* content of a window. This change can be adding/removing view, changing
* a view size, etc.</br>
+ * </p>
* <p>
* <strong>Note:</strong> This event is fired only for the window source of the
- * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED})
+ * last accessibility event different from {@link #TYPE_NOTIFICATION_STATE_CHANGED}
* and its purpose is to notify clients that the content of the user interaction
- * window has changed.
- * </p>
+ * window has changed.</br>
* <em>Type:</em> {@link #TYPE_WINDOW_CONTENT_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getSource()} - The source info (for registered clients).</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</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>NOTIFICATION TYPES</b></br>
+ * </p>
* <p>
* <b>Notification state changed</b> - represents the event showing
- * {@link android.app.Notification}.
+ * {@link android.app.Notification}.</br>
* <em>Type:</em> {@link #TYPE_NOTIFICATION_STATE_CHANGED}</br>
* <em>Properties:</em></br>
* <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
* <li>{@link #getClassName()} - The class name of the source.</li>
* <li>{@link #getPackageName()} - The package name of the source.</li>
* <li>{@link #getEventTime()} - The event time.</li>
- * <li>{@link #getText()} - The text of the source.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
* <li>{@link #getParcelableData()} - The posted {@link android.app.Notification}.</li>
+ * <li>{@link #getText()} - Text for providing more context.</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>EXPLORATION TYPES</b></br>
+ * </p>
+ * <p>
+ * <b>View hover enter</b> - represents the event of beginning to hover
+ * over a {@link android.view.View}. The hover may be generated via
+ * exploring the screen by touch or via a pointing device.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_HOVER_ENTER}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <b>View hover exit</b> - represents the event of stopping to hover
+ * over a {@link android.view.View}. The hover may be generated via
+ * exploring the screen by touch or via a pointing device.</br>
+ * <em>Type:</em> {@link #TYPE_VIEW_HOVER_EXIT}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</li>
+ * <li>{@link #getSource()} - The source info (for registered clients).</li>
+ * <li>{@link #getClassName()} - The class name of the source.</li>
+ * <li>{@link #getPackageName()} - The package name of the source.</li>
+ * <li>{@link #getEventTime()} - The event time.</li>
+ * <li>{@link #getText()} - The text of the source's sub-tree.</li>
+ * <li>{@link #isEnabled()} - Whether the source is enabled.</li>
+ * <li>{@link #getContentDescription()} - The content description of the source.</li>
+ * <li>{@link #getScrollX()} - The offset of the source left edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getScrollY()} - The offset of the source top edge in pixels
+ * (without descendants of AdapterView).</li>
+ * <li>{@link #getFromIndex()} - The zero based index of the first visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getToIndex()} - The zero based index of the last visible item of the source,
+ * inclusive (for descendants of AdapterView).</li>
+ * <li>{@link #getItemCount()} - The total items of the source
+ * (for descendants of AdapterView).</li>
+ * </ul>
+ * </p>
+ * <p>
+ * <b>Touch exploration gesture start</b> - represents the event of starting a touch
+ * exploring gesture.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_START}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</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>Touch exploration gesture end</b> - represents the event of ending a touch
+ * exploring gesture.</br>
+ * <em>Type:</em> {@link #TYPE_TOUCH_EXPLORATION_GESTURE_END}</br>
+ * <em>Properties:</em></br>
+ * <ul>
+ * <li>{@link #getEventType()} - The type of the event.</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>Security note</b>
@@ -254,6 +444,7 @@ import java.util.List;
* Since an event contains the text of its source privacy can be compromised by leaking
* sensitive information such as passwords. To address this issue any event fired in response
* to manipulation of a PASSWORD field does NOT CONTAIN the text of the password.
+ * </p>
*
* @see android.view.accessibility.AccessibilityManager
* @see android.accessibilityservice.AccessibilityService
@@ -675,6 +866,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
record.mToIndex = parcel.readInt();
record.mScrollX = parcel.readInt();
record.mScrollY = parcel.readInt();
+ record.mMaxScrollX = parcel.readInt();
+ record.mMaxScrollY = parcel.readInt();
record.mAddedCount = parcel.readInt();
record.mRemovedCount = parcel.readInt();
record.mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
@@ -727,6 +920,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
parcel.writeInt(record.mToIndex);
parcel.writeInt(record.mScrollX);
parcel.writeInt(record.mScrollY);
+ parcel.writeInt(record.mMaxScrollX);
+ parcel.writeInt(record.mMaxScrollY);
parcel.writeInt(record.mAddedCount);
parcel.writeInt(record.mRemovedCount);
TextUtils.writeToParcel(record.mClassName, parcel, flags);
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index afd7473..a4e0688 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -73,6 +73,8 @@ public class AccessibilityRecord {
int mToIndex = UNDEFINED;
int mScrollX = UNDEFINED;
int mScrollY = UNDEFINED;
+ int mMaxScrollX = UNDEFINED;
+ int mMaxScrollY = UNDEFINED;
int mAddedCount= UNDEFINED;
int mRemovedCount = UNDEFINED;
@@ -348,18 +350,18 @@ public class AccessibilityRecord {
}
/**
- * Gets the scroll position of the source along the X axis.
+ * Gets the scroll offset of the source left edge in pixels.
*
- * @return The scroll along the X axis.
+ * @return The scroll.
*/
public int getScrollX() {
return mScrollX;
}
/**
- * Sets the scroll position of the source along the X axis.
+ * Sets the scroll offset of the source left edge in pixels.
*
- * @param scrollX The scroll along the X axis.
+ * @param scrollX The scroll.
*/
public void setScrollX(int scrollX) {
enforceNotSealed();
@@ -367,18 +369,18 @@ public class AccessibilityRecord {
}
/**
- * Gets the scroll position of the source along the Y axis.
+ * Gets the scroll offset of the source top edge in pixels.
*
- * @return The scroll along the Y axis.
+ * @return The scroll.
*/
public int getScrollY() {
return mScrollY;
}
/**
- * Sets the scroll position of the source along the Y axis.
+ * Sets the scroll offset of the source top edge in pixels.
*
- * @param scrollY The scroll along the Y axis.
+ * @param scrollY The scroll.
*/
public void setScrollY(int scrollY) {
enforceNotSealed();
@@ -386,6 +388,43 @@ public class AccessibilityRecord {
}
/**
+ * Gets the max scroll offset of the source left edge in pixels.
+ *
+ * @return The max scroll.
+ */
+ public int getMaxScrollX() {
+ return mMaxScrollX;
+ }
+ /**
+ * Sets the max scroll offset of the source left edge in pixels.
+ *
+ * @param maxScrollX The max scroll.
+ */
+ public void setMaxScrollX(int maxScrollX) {
+ enforceNotSealed();
+ mMaxScrollX = maxScrollX;
+ }
+
+ /**
+ * Gets the max scroll offset of the source top edge in pixels.
+ *
+ * @return The max scroll.
+ */
+ public int getMaxScrollY() {
+ return mMaxScrollY;
+ }
+
+ /**
+ * Sets the max scroll offset of the source top edge in pixels.
+ *
+ * @param maxScrollY The max scroll.
+ */
+ public void setMaxScrollY(int maxScrollY) {
+ enforceNotSealed();
+ mMaxScrollY = maxScrollY;
+ }
+
+ /**
* Gets the number of added characters.
*
* @return The number of added characters.
@@ -658,6 +697,8 @@ public class AccessibilityRecord {
mToIndex = record.mToIndex;
mScrollX = record.mScrollX;
mScrollY = record.mScrollY;
+ mMaxScrollX = record.mMaxScrollX;
+ mMaxScrollY = record.mMaxScrollY;
mAddedCount = record.mAddedCount;
mRemovedCount = record.mRemovedCount;
mClassName = record.mClassName;
@@ -682,6 +723,8 @@ public class AccessibilityRecord {
mToIndex = UNDEFINED;
mScrollX = UNDEFINED;
mScrollY = UNDEFINED;
+ mMaxScrollX = UNDEFINED;
+ mMaxScrollY = UNDEFINED;
mAddedCount = UNDEFINED;
mRemovedCount = UNDEFINED;
mClassName = null;
@@ -711,6 +754,8 @@ public class AccessibilityRecord {
builder.append("; ToIndex: " + mToIndex);
builder.append("; ScrollX: " + mScrollX);
builder.append("; ScrollY: " + mScrollY);
+ builder.append("; MaxScrollX: " + mMaxScrollX);
+ builder.append("; MaxScrollY: " + mMaxScrollY);
builder.append("; AddedCount: " + mAddedCount);
builder.append("; RemovedCount: " + mRemovedCount);
builder.append("; ParcelableData: " + mParcelableData);
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 28f54aa..2cc928f 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -43,7 +43,6 @@ import junit.framework.Assert;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
-import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charsets;
import java.security.PrivateKey;
@@ -472,8 +471,7 @@ class BrowserFrame extends Handler {
/**
* We have received an SSL certificate for the main top-level page.
- *
- * !!!Called from the network thread!!!
+ * Used by the Android HTTP stack only.
*/
void certificate(SslCertificate certificate) {
if (mIsMainFrame) {
@@ -1171,12 +1169,7 @@ class BrowserFrame extends Handler {
try {
X509Certificate cert = new X509CertImpl(certDER);
SslCertificate sslCert = new SslCertificate(cert);
- if (JniUtil.useChromiumHttpStack()) {
- sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert,
- new URL(url).getHost());
- } else {
- sslError = new SslError(certError, cert, url);
- }
+ sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, url);
} catch (IOException e) {
// Can't get the certificate, not much to do.
Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
@@ -1186,18 +1179,18 @@ class BrowserFrame extends Handler {
if (SslCertLookupTable.getInstance().isAllowed(sslError)) {
nativeSslCertErrorProceed(handle);
+ mCallbackProxy.onProceededAfterSslError(sslError);
return;
}
SslErrorHandler handler = new SslErrorHandler() {
@Override
public void proceed() {
- SslCertLookupTable.getInstance().setIsAllowed(sslError, true);
+ SslCertLookupTable.getInstance().setIsAllowed(sslError);
nativeSslCertErrorProceed(handle);
}
@Override
public void cancel() {
- SslCertLookupTable.getInstance().setIsAllowed(sslError, false);
nativeSslCertErrorCancel(handle, certError);
}
};
@@ -1211,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);
}
}
@@ -1276,7 +1267,8 @@ class BrowserFrame extends Handler {
}
/**
- * Called by JNI when we load a page over SSL.
+ * Called by JNI when we recieve a certificate for the page's main resource.
+ * Used by the Chromium HTTP stack only.
*/
private void setCertificate(byte cert_der[]) {
try {
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 5ee90a4..75ee338 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -77,53 +77,54 @@ class CallbackProxy extends Handler {
// Used to call startActivity during url override.
private final Context mContext;
- // Message Ids
- private static final int PAGE_STARTED = 100;
- private static final int RECEIVED_ICON = 101;
- private static final int RECEIVED_TITLE = 102;
- private static final int OVERRIDE_URL = 103;
- private static final int AUTH_REQUEST = 104;
- private static final int SSL_ERROR = 105;
- private static final int PROGRESS = 106;
- private static final int UPDATE_VISITED = 107;
- private static final int LOAD_RESOURCE = 108;
- private static final int CREATE_WINDOW = 109;
- private static final int CLOSE_WINDOW = 110;
- private static final int SAVE_PASSWORD = 111;
- private static final int JS_ALERT = 112;
- private static final int JS_CONFIRM = 113;
- private static final int JS_PROMPT = 114;
- private static final int JS_UNLOAD = 115;
- private static final int ASYNC_KEYEVENTS = 116;
- private static final int DOWNLOAD_FILE = 118;
- private static final int REPORT_ERROR = 119;
- private static final int RESEND_POST_DATA = 120;
- private static final int PAGE_FINISHED = 121;
- private static final int REQUEST_FOCUS = 122;
- private static final int SCALE_CHANGED = 123;
- private static final int RECEIVED_CERTIFICATE = 124;
- private static final int SWITCH_OUT_HISTORY = 125;
- private static final int EXCEEDED_DATABASE_QUOTA = 126;
- private static final int REACHED_APPCACHE_MAXSIZE = 127;
- private static final int JS_TIMEOUT = 128;
- private static final int ADD_MESSAGE_TO_CONSOLE = 129;
- private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130;
- private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
- private static final int RECEIVED_TOUCH_ICON_URL = 132;
- private static final int GET_VISITED_HISTORY = 133;
- private static final int OPEN_FILE_CHOOSER = 134;
- private static final int ADD_HISTORY_ITEM = 135;
- private static final int HISTORY_INDEX_CHANGED = 136;
- private static final int AUTH_CREDENTIALS = 137;
- private static final int SET_INSTALLABLE_WEBAPP = 138;
- private static final int NOTIFY_SEARCHBOX_LISTENERS = 139;
- private static final int AUTO_LOGIN = 140;
- private static final int CLIENT_CERT_REQUEST = 141;
- private static final int SEARCHBOX_IS_SUPPORTED_CALLBACK = 142;
- private static final int SEARCHBOX_DISPATCH_COMPLETE_CALLBACK= 143;
+ // Message IDs
+ private static final int PAGE_STARTED = 100;
+ private static final int RECEIVED_ICON = 101;
+ private static final int RECEIVED_TITLE = 102;
+ private static final int OVERRIDE_URL = 103;
+ private static final int AUTH_REQUEST = 104;
+ private static final int SSL_ERROR = 105;
+ private static final int PROGRESS = 106;
+ private static final int UPDATE_VISITED = 107;
+ private static final int LOAD_RESOURCE = 108;
+ private static final int CREATE_WINDOW = 109;
+ private static final int CLOSE_WINDOW = 110;
+ private static final int SAVE_PASSWORD = 111;
+ private static final int JS_ALERT = 112;
+ private static final int JS_CONFIRM = 113;
+ private static final int JS_PROMPT = 114;
+ private static final int JS_UNLOAD = 115;
+ private static final int ASYNC_KEYEVENTS = 116;
+ private static final int DOWNLOAD_FILE = 118;
+ private static final int REPORT_ERROR = 119;
+ private static final int RESEND_POST_DATA = 120;
+ private static final int PAGE_FINISHED = 121;
+ private static final int REQUEST_FOCUS = 122;
+ private static final int SCALE_CHANGED = 123;
+ private static final int RECEIVED_CERTIFICATE = 124;
+ private static final int SWITCH_OUT_HISTORY = 125;
+ private static final int EXCEEDED_DATABASE_QUOTA = 126;
+ private static final int REACHED_APPCACHE_MAXSIZE = 127;
+ private static final int JS_TIMEOUT = 128;
+ private static final int ADD_MESSAGE_TO_CONSOLE = 129;
+ private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130;
+ private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
+ private static final int RECEIVED_TOUCH_ICON_URL = 132;
+ private static final int GET_VISITED_HISTORY = 133;
+ private static final int OPEN_FILE_CHOOSER = 134;
+ private static final int ADD_HISTORY_ITEM = 135;
+ private static final int HISTORY_INDEX_CHANGED = 136;
+ private static final int AUTH_CREDENTIALS = 137;
+ private static final int SET_INSTALLABLE_WEBAPP = 138;
+ private static final int NOTIFY_SEARCHBOX_LISTENERS = 139;
+ private static final int AUTO_LOGIN = 140;
+ private static final int CLIENT_CERT_REQUEST = 141;
+ private static final int SEARCHBOX_IS_SUPPORTED_CALLBACK = 142;
+ private static final int SEARCHBOX_DISPATCH_COMPLETE_CALLBACK = 143;
+ private static final int PROCEEDED_AFTER_SSL_ERROR = 144;
// Message triggered by the client to resume execution
- private static final int NOTIFY = 200;
+ private static final int NOTIFY = 200;
// Result transportation object for returning results across thread
// boundaries.
@@ -349,6 +350,13 @@ class CallbackProxy extends Handler {
}
break;
+ case PROCEEDED_AFTER_SSL_ERROR:
+ if (mWebViewClient != null) {
+ mWebViewClient.onProceededAfterSslError(mWebView,
+ (SslError) msg.obj);
+ }
+ break;
+
case CLIENT_CERT_REQUEST:
if (mWebViewClient != null) {
HashMap<String, Object> map =
@@ -1024,6 +1032,15 @@ class CallbackProxy extends Handler {
sendMessage(msg);
}
+ public void onProceededAfterSslError(SslError error) {
+ if (mWebViewClient == null) {
+ return;
+ }
+ Message msg = obtainMessage(PROCEEDED_AFTER_SSL_ERROR);
+ msg.obj = error;
+ sendMessage(msg);
+ }
+
public void onReceivedClientCertRequest(ClientCertRequestHandler handler, String host_and_port) {
// Do an unsynchronized quick check to avoid posting if no callback has
// been set.
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index a322fa3..b85fd17 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -22,16 +22,14 @@ import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
import android.text.TextWatcher;
-import android.webkit.WebView;
-import android.widget.EditText;
-import android.widget.TextView;
import android.view.ActionMode;
import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
View.OnLongClickListener, View.OnClickListener {
@@ -180,6 +178,14 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ if (!mode.isUiFocusable()) {
+ // If the action mode we're running in is not focusable the user
+ // will not be able to type into the find on page field. This
+ // should only come up when we're running in a dialog which is
+ // already less than ideal; disable the option for now.
+ return false;
+ }
+
mode.setCustomView(mCustomView);
mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_find,
menu);
@@ -195,6 +201,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
@Override
public void onDestroyActionMode(ActionMode mode) {
+ mActionMode = null;
mWebView.notifyFindDialogDismissed();
mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
}
@@ -247,4 +254,13 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
// Does nothing. Needed to implement TextWatcher.
}
+ public int getActionModeHeight() {
+ if (mActionMode == null) {
+ return 0;
+ }
+ View parent = (View) mCustomView.getParent();
+ return parent != null ? parent.getMeasuredHeight()
+ : mCustomView.getMeasuredHeight();
+ }
+
}
diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java
index 3600d09..9fc48a1 100644
--- a/core/java/android/webkit/HTML5Audio.java
+++ b/core/java/android/webkit/HTML5Audio.java
@@ -16,12 +16,9 @@
package android.webkit;
+import android.content.Context;
+import android.media.AudioManager;
import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnBufferingUpdateListener;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
-import android.media.MediaPlayer.OnSeekCompleteListener;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -45,7 +42,8 @@ class HTML5Audio extends Handler
MediaPlayer.OnCompletionListener,
MediaPlayer.OnErrorListener,
MediaPlayer.OnPreparedListener,
- MediaPlayer.OnSeekCompleteListener {
+ MediaPlayer.OnSeekCompleteListener,
+ AudioManager.OnAudioFocusChangeListener {
// Logging tag.
private static final String LOGTAG = "HTML5Audio";
@@ -69,6 +67,7 @@ class HTML5Audio extends Handler
private String mUrl;
private boolean mAskToPlay = false;
+ private Context mContext;
// Timer thread -> UI thread
private static final int TIMEUPDATE = 100;
@@ -183,6 +182,7 @@ class HTML5Audio extends Handler
// Save the native ptr
mNativePointer = nativePtr;
resetMediaPlayer();
+ mContext = webViewCore.getContext();
mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter(
webViewCore.getContext().getMainLooper(), webViewCore.getWebView());
}
@@ -233,6 +233,34 @@ class HTML5Audio extends Handler
}
}
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ // resume playback
+ if (mMediaPlayer == null) resetMediaPlayer();
+ else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
+ mState = STARTED;
+ break;
+
+ case AudioManager.AUDIOFOCUS_LOSS:
+ // Lost focus for an unbounded amount of time: stop playback and release media player
+ if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ break;
+
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ // Lost focus for a short time, but we have to stop
+ // playback. We don't release the media player because playback
+ // is likely to resume
+ if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
+ break;
+ }
+ }
+
+
private void play() {
if ((mState >= ERROR && mState < PREPARED) && mUrl != null) {
resetMediaPlayer();
@@ -241,8 +269,17 @@ class HTML5Audio extends Handler
}
if (mState >= PREPARED) {
- mMediaPlayer.start();
- mState = STARTED;
+ AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+
+ if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ // could not get audio focus.
+ teardown();
+ } else {
+ mMediaPlayer.start();
+ mState = STARTED;
+ }
}
}
@@ -276,4 +313,5 @@ class HTML5Audio extends Handler
private native void nativeOnEnded(int nativePointer);
private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
private native void nativeOnTimeupdate(int position, int nativePointer);
+
}
diff --git a/core/java/android/webkit/SelectActionModeCallback.java b/core/java/android/webkit/SelectActionModeCallback.java
index 104deb1..8c174aa 100644
--- a/core/java/android/webkit/SelectActionModeCallback.java
+++ b/core/java/android/webkit/SelectActionModeCallback.java
@@ -17,13 +17,12 @@
package android.webkit;
import android.app.SearchManager;
+import android.content.Context;
import android.content.Intent;
import android.provider.Browser;
-import android.webkit.WebView;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
class SelectActionModeCallback implements ActionMode.Callback {
private WebView mWebView;
@@ -45,9 +44,25 @@ class SelectActionModeCallback implements ActionMode.Callback {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy,
- menu);
- mode.setTitle(com.android.internal.R.string.textSelectionCABTitle);
+ mode.getMenuInflater().inflate(com.android.internal.R.menu.webview_copy, menu);
+
+ final Context context = mWebView.getContext();
+ boolean allowText = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
+ mode.setTitle(allowText ?
+ context.getString(com.android.internal.R.string.textSelectionCABTitle) : null);
+
+ if (!mode.isUiFocusable()) {
+ // If the action mode UI we're running in isn't capable of taking window focus
+ // the user won't be able to type into the find on page UI. Disable this functionality.
+ // (Note that this should only happen in floating dialog windows.)
+ // This can be removed once we can handle multiple focusable windows at a time
+ // in a better way.
+ final MenuItem findOnPageItem = menu.findItem(com.android.internal.R.id.find);
+ if (findOnPageItem != null) {
+ findOnPageItem.setVisible(false);
+ }
+ }
mActionMode = mode;
return true;
}
diff --git a/core/java/android/webkit/SslCertLookupTable.java b/core/java/android/webkit/SslCertLookupTable.java
index 052244f..98ace4f 100644
--- a/core/java/android/webkit/SslCertLookupTable.java
+++ b/core/java/android/webkit/SslCertLookupTable.java
@@ -19,13 +19,18 @@ package android.webkit;
import android.os.Bundle;
import android.net.http.SslError;
+import java.net.MalformedURLException;
+import java.net.URL;
+
/**
* Stores the user's decision of whether to allow or deny an invalid certificate.
*
- * This class is not threadsafe. It is used only on the WebCore thread.
+ * This class is not threadsafe. It is used only on the WebCore thread. Also, it
+ * is used only by the Chromium HTTP stack.
*/
final class SslCertLookupTable {
private static SslCertLookupTable sTable;
+ // We store the most severe error we're willing to allow for each host.
private final Bundle table;
public static SslCertLookupTable getInstance() {
@@ -39,12 +44,26 @@ final class SslCertLookupTable {
table = new Bundle();
}
- public void setIsAllowed(SslError sslError, boolean allow) {
- table.putBoolean(sslError.toString(), allow);
+ public void setIsAllowed(SslError sslError) {
+ String host;
+ try {
+ host = new URL(sslError.getUrl()).getHost();
+ } catch(MalformedURLException e) {
+ return;
+ }
+ table.putInt(host, sslError.getPrimaryError());
}
+ // We allow the decision to be re-used if it's for the same host and is for
+ // an error of equal or greater severity than this error.
public boolean isAllowed(SslError sslError) {
- return table.getBoolean(sslError.toString());
+ String host;
+ try {
+ host = new URL(sslError.getUrl()).getHost();
+ } catch(MalformedURLException e) {
+ return false;
+ }
+ return table.containsKey(host) && sslError.getPrimaryError() <= table.getInt(host);
}
public void clear() {
diff --git a/core/java/android/webkit/SslErrorHandlerImpl.java b/core/java/android/webkit/SslErrorHandlerImpl.java
index e029e37..b2e4b13 100644
--- a/core/java/android/webkit/SslErrorHandlerImpl.java
+++ b/core/java/android/webkit/SslErrorHandlerImpl.java
@@ -16,8 +16,6 @@
package android.webkit;
-import junit.framework.Assert;
-
import android.net.http.SslError;
import android.os.Bundle;
import android.os.Handler;
@@ -54,7 +52,7 @@ class SslErrorHandlerImpl extends SslErrorHandler {
private final SslErrorHandler mOriginHandler;
private final LoadListener mLoadListener;
- // Message id for handling the response
+ // Message id for handling the response from the client.
private static final int HANDLE_RESPONSE = 100;
@Override
@@ -130,7 +128,9 @@ class SslErrorHandlerImpl extends SslErrorHandler {
}
/**
- * Handles SSL error(s) on the way up to the user.
+ * Handles requests from the network stack about whether to proceed with a
+ * load given an SSL error(s). We may ask the client what to do, or use a
+ * cached response.
*/
/* package */ synchronized void handleSslErrorRequest(LoadListener loader) {
if (DebugFlags.SSL_ERROR_HANDLER) {
@@ -147,8 +147,10 @@ class SslErrorHandlerImpl extends SslErrorHandler {
}
/**
- * Check the preference table for a ssl error that has already been shown
- * to the user.
+ * Check the preference table to see if we already have a 'proceed' decision
+ * from the client for this host and for an error of equal or greater
+ * severity than the supplied error. If so, instruct the loader to proceed
+ * and return true. Otherwise return false.
*/
/* package */ synchronized boolean checkSslPrefTable(LoadListener loader,
SslError error) {
@@ -156,21 +158,22 @@ class SslErrorHandlerImpl extends SslErrorHandler {
final int primary = error.getPrimaryError();
if (DebugFlags.SSL_ERROR_HANDLER) {
- Assert.assertTrue(host != null && primary != 0);
+ assert host != null;
+ assert primary != -1;
}
- if (mSslPrefTable.containsKey(host)) {
- if (primary <= mSslPrefTable.getInt(host)) {
- handleSslErrorResponse(loader, error, true);
- return true;
+ if (mSslPrefTable.containsKey(host) && primary <= mSslPrefTable.getInt(host)) {
+ if (!loader.cancelled()) {
+ loader.handleSslErrorResponse(true);
}
+ return true;
}
return false;
}
/**
* Processes queued SSL-error confirmation requests in
- * a tight loop while there is no need to ask the user.
+ * a tight loop while there is no need to ask the client.
*/
/* package */void fastProcessQueuedSslErrors() {
while (processNextLoader());
@@ -195,19 +198,18 @@ class SslErrorHandlerImpl extends SslErrorHandler {
SslError error = loader.sslError();
if (DebugFlags.SSL_ERROR_HANDLER) {
- Assert.assertNotNull(error);
+ assert error != null;
}
- // checkSslPrefTable will handle the ssl error response if the
- // answer is available. It does not remove the loader from the
- // queue.
+ // checkSslPrefTable() will instruct the loader to proceed if we
+ // have a cached 'proceed' decision. It does not remove the loader
+ // from the queue.
if (checkSslPrefTable(loader, error)) {
mLoaderQueue.remove(loader);
return true;
}
- // if we do not have information on record, ask
- // the user (display a dialog)
+ // If we can not proceed based on a cached decision, ask the client.
CallbackProxy proxy = loader.getFrame().getCallbackProxy();
proxy.onReceivedSslError(new SslErrorHandlerImpl(this, loader), error);
}
@@ -217,32 +219,31 @@ class SslErrorHandlerImpl extends SslErrorHandler {
}
/**
- * Proceed with the SSL certificate.
+ * Proceed with this load.
*/
public void proceed() {
- mOriginHandler.sendMessage(
- mOriginHandler.obtainMessage(
- HANDLE_RESPONSE, 1, 0, mLoadListener));
+ mOriginHandler.sendMessage(mOriginHandler.obtainMessage(
+ HANDLE_RESPONSE, 1, 0, mLoadListener));
}
/**
- * Cancel this request and all pending requests for the WebView that had
- * the error.
+ * Cancel this load and all pending loads for the WebView that had the
+ * error.
*/
public void cancel() {
- mOriginHandler.sendMessage(
- mOriginHandler.obtainMessage(
- HANDLE_RESPONSE, 0, 0, mLoadListener));
+ mOriginHandler.sendMessage(mOriginHandler.obtainMessage(
+ HANDLE_RESPONSE, 0, 0, mLoadListener));
}
/**
- * Handles SSL error(s) on the way down from the user.
+ * Handles the response from the client about whether to proceed with this
+ * load. We save the response to be re-used in the future.
*/
/* package */ synchronized void handleSslErrorResponse(LoadListener loader,
SslError error, boolean proceed) {
if (DebugFlags.SSL_ERROR_HANDLER) {
- Assert.assertNotNull(loader);
- Assert.assertNotNull(error);
+ assert loader != null;
+ assert error != null;
}
if (DebugFlags.SSL_ERROR_HANDLER) {
@@ -253,16 +254,16 @@ class SslErrorHandlerImpl extends SslErrorHandler {
if (!loader.cancelled()) {
if (proceed) {
- // update the user's SSL error preference table
+ // Update the SSL error preference table
int primary = error.getPrimaryError();
String host = loader.host();
if (DebugFlags.SSL_ERROR_HANDLER) {
- Assert.assertTrue(host != null && primary != 0);
+ assert host != null;
+ assert primary != -1;
}
boolean hasKey = mSslPrefTable.containsKey(host);
- if (!hasKey ||
- primary > mSslPrefTable.getInt(host)) {
+ if (!hasKey || primary > mSslPrefTable.getInt(host)) {
mSslPrefTable.putInt(host, primary);
}
}
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 217ad7c..b0ecf09 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -768,9 +768,13 @@ import junit.framework.Assert;
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
mInsideRemove = true;
+ boolean isFocused = hasFocus();
mWebView.removeView(this);
- mWebView.requestFocus();
+ if (isFocused) {
+ mWebView.requestFocus();
+ }
mInsideRemove = false;
+ mHandler.removeCallbacksAndMessages(null);
}
@Override
@@ -828,7 +832,7 @@ import junit.framework.Assert;
TextView tv =
(TextView) super.getView(position, convertView, parent);
if (tv != null && mTextView != null) {
- tv.setTextSize(mTextView.getTextSize());
+ tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextView.getTextSize());
}
return tv;
}
@@ -892,7 +896,10 @@ import junit.framework.Assert;
* WebTextView represents.
*/
/* package */ void setNodePointer(int ptr) {
- mNodePointer = ptr;
+ if (ptr != mNodePointer) {
+ mNodePointer = ptr;
+ setAdapterCustom(null);
+ }
}
/**
@@ -995,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;
@@ -1047,11 +1057,12 @@ import junit.framework.Assert;
}
setHint(null);
setThreshold(1);
+ boolean autoComplete = false;
if (single) {
mWebView.requestLabel(mWebView.nativeFocusCandidateFramePointer(),
mNodePointer);
maxLength = mWebView.nativeFocusCandidateMaxLength();
- boolean autoComplete = mWebView.nativeFocusCandidateIsAutoComplete();
+ autoComplete = mWebView.nativeFocusCandidateIsAutoComplete();
if (type != PASSWORD && (mAutoFillable || autoComplete)) {
String name = mWebView.nativeFocusCandidateName();
if (name != null && name.length() > 0) {
@@ -1066,8 +1077,9 @@ import junit.framework.Assert;
setInputType(inputType);
setImeOptions(imeOptions);
setVisibility(VISIBLE);
- AutoCompleteAdapter adapter = null;
- setAdapterCustom(adapter);
+ if (!autoComplete) {
+ setAdapterCustom(null);
+ }
}
/**
@@ -1092,4 +1104,11 @@ import junit.framework.Assert;
return url != null ? url.getProtocol() + "://" + url.getHost() + url.getPath() : null;
}
+
+ public void setGravityForRtl(boolean rtl) {
+ int gravity = rtl ? Gravity.RIGHT : Gravity.LEFT;
+ gravity |= mSingle ? Gravity.CENTER_VERTICAL : Gravity.TOP;
+ setGravity(gravity);
+ }
+
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 370cce4..8da1820 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -77,7 +77,9 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
@@ -1303,6 +1305,31 @@ public class WebView extends AbsoluteLayout
}
@Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setScrollable(isScrollableForAccessibility());
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ event.setScrollable(isScrollableForAccessibility());
+ event.setScrollX(mScrollX);
+ event.setScrollY(mScrollY);
+ final int convertedContentWidth = contentToViewX(getContentWidth());
+ final int adjustedViewWidth = getWidth() - mPaddingLeft - mPaddingRight;
+ event.setMaxScrollX(Math.max(convertedContentWidth - adjustedViewWidth, 0));
+ final int convertedContentHeight = contentToViewY(getContentHeight());
+ final int adjustedViewHeight = getHeight() - mPaddingTop - mPaddingBottom;
+ event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
+ }
+
+ private boolean isScrollableForAccessibility() {
+ return (contentToViewX(getContentWidth()) > getWidth() - mPaddingLeft - mPaddingRight
+ || contentToViewY(getContentHeight()) > getHeight() - mPaddingTop - mPaddingBottom);
+ }
+
+ @Override
public void setOverScrollMode(int mode) {
super.setOverScrollMode(mode);
if (mode != OVER_SCROLL_NEVER) {
@@ -1456,7 +1483,8 @@ public class WebView extends AbsoluteLayout
private int getVisibleTitleHeightImpl() {
// need to restrict mScrollY due to over scroll
- return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
+ return Math.max(getTitleHeight() - Math.max(0, mScrollY),
+ mFindCallback != null ? mFindCallback.getActionModeHeight() : 0);
}
/*
@@ -1581,6 +1609,11 @@ public class WebView extends AbsoluteLayout
mListBoxDialog.dismiss();
mListBoxDialog = null;
}
+ // remove so that it doesn't cause events
+ if (mWebTextView != null) {
+ mWebTextView.remove();
+ mWebTextView = null;
+ }
if (mNativeClass != 0) nativeStopGL();
if (mWebViewCore != null) {
// Set the handlers to null before destroying WebViewCore so no
@@ -2041,7 +2074,13 @@ 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.
* @param data A String of data in the given encoding.
* @param mimeType The MIMEType of the data, e.g. 'text/html'.
* @param encoding The encoding of the data.
@@ -2947,6 +2986,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;
@@ -3241,8 +3287,7 @@ public class WebView extends AbsoluteLayout
public void clearFormData() {
checkThread();
if (inEditingMode()) {
- AutoCompleteAdapter adapter = null;
- mWebTextView.setAdapterCustom(adapter);
+ mWebTextView.setAdapterCustom(null);
}
}
@@ -4078,8 +4123,8 @@ public class WebView extends AbsoluteLayout
boolean pressed = (mTouchMode == TOUCH_SHORTPRESS_START_MODE
|| mTouchMode == TOUCH_INIT_MODE
|| mTouchMode == TOUCH_SHORTPRESS_MODE);
- nativeRecordButtons(hasFocus() && hasWindowFocus(),
- (pressed && !USE_WEBKIT_RINGS)
+ recordButtons(canvas,
+ hasFocus() && hasWindowFocus(), (pressed && !USE_WEBKIT_RINGS)
|| mTrackballDown || mGotCenterDown, false);
drawCoreAndCursorRing(canvas, mBackgroundColor,
mDrawCursorRing && drawRings);
@@ -4760,12 +4805,12 @@ public class WebView extends AbsoluteLayout
}
String text = nativeFocusCandidateText();
int nodePointer = nativeFocusCandidatePointer();
- mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ?
- Gravity.RIGHT : Gravity.NO_GRAVITY);
// This needs to be called before setType, which may call
// requestFormData, and it needs to have the correct nodePointer.
mWebTextView.setNodePointer(nodePointer);
mWebTextView.setType(nativeFocusCandidateType());
+ // Gravity needs to be set after setType
+ mWebTextView.setGravityForRtl(nativeFocusCandidateIsRtlText());
updateWebTextViewPadding();
if (null == text) {
if (DebugFlags.WEB_VIEW) {
@@ -5133,7 +5178,7 @@ public class WebView extends AbsoluteLayout
.obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
// Already checked mNativeClass, so we do not need to check it
// again.
- nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
+ recordButtons(null, hasFocus() && hasWindowFocus(), true, true);
if (!wantsKeyEvents) return true;
}
// Bubble up the key event as WebView doesn't handle it
@@ -5561,7 +5606,7 @@ public class WebView extends AbsoluteLayout
mDrawCursorRing = true;
setFocusControllerActive(true);
if (mNativeClass != 0) {
- nativeRecordButtons(true, false, true);
+ recordButtons(null, true, false, true);
}
} else {
if (!inEditingMode()) {
@@ -5570,7 +5615,7 @@ public class WebView extends AbsoluteLayout
mDrawCursorRing = false;
setFocusControllerActive(false);
}
- // We do not call nativeRecordButtons here because we assume
+ // We do not call recordButtons here because we assume
// that when we lost focus, or window focus, it got called with
// false for the first parameter
}
@@ -5589,7 +5634,7 @@ public class WebView extends AbsoluteLayout
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
mTouchMode = TOUCH_DONE_MODE;
if (mNativeClass != 0) {
- nativeRecordButtons(false, false, true);
+ recordButtons(null, false, false, true);
}
setFocusControllerActive(false);
}
@@ -5647,13 +5692,13 @@ public class WebView extends AbsoluteLayout
if (hasWindowFocus()) {
mDrawCursorRing = true;
if (mNativeClass != 0) {
- nativeRecordButtons(true, false, true);
+ recordButtons(null, true, false, true);
}
setFocusControllerActive(true);
//} else {
// The WebView has gained focus while we do not have
// windowfocus. When our window lost focus, we should have
- // called nativeRecordButtons(false...)
+ // called recordButtons(false...)
}
} else {
// When we lost focus, unless focus went to the TextView (which is
@@ -5661,7 +5706,7 @@ public class WebView extends AbsoluteLayout
if (!inEditingMode()) {
mDrawCursorRing = false;
if (mNativeClass != 0) {
- nativeRecordButtons(false, false, true);
+ recordButtons(null, false, false, true);
}
setFocusControllerActive(false);
}
@@ -6762,7 +6807,7 @@ public class WebView extends AbsoluteLayout
if (mNativeClass == 0) {
return false;
}
- nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true);
+ recordButtons(null, hasFocus() && hasWindowFocus(), true, true);
if (time - mLastCursorTime <= TRACKBALL_TIMEOUT
&& !mLastCursorBounds.equals(nativeGetCursorRingBounds())) {
nativeSelectBestAt(mLastCursorBounds);
@@ -7378,6 +7423,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.
@@ -7636,6 +7685,10 @@ public class WebView extends AbsoluteLayout
}
/* package */ void passToJavaScript(String currentText, KeyEvent event) {
+ // check if mWebViewCore has been destroyed
+ if (mWebViewCore == null) {
+ return;
+ }
WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData();
arg.mEvent = event;
arg.mCurrentText = currentText;
@@ -9349,6 +9402,24 @@ public class WebView extends AbsoluteLayout
return nativeTileProfilingGetFloat(frame, tile, key);
}
+ /**
+ * Helper method to deal with differences between hardware and software rendering
+ */
+ private void recordButtons(Canvas canvas, boolean focus, boolean pressed,
+ boolean inval) {
+ boolean isHardwareAccel = canvas != null
+ ? canvas.isHardwareAccelerated()
+ : isHardwareAccelerated();
+ if (isHardwareAccel) {
+ // We never want to change button state if we are hardware accelerated,
+ // but we DO want to invalidate as necessary so that the GL ring
+ // can be drawn
+ nativeRecordButtons(false, false, inval);
+ } else {
+ nativeRecordButtons(focus, pressed, inval);
+ }
+ }
+
private native int nativeCacheHitFramePointer();
private native boolean nativeCacheHitIsPlugin();
private native Rect nativeCacheHitNodeBounds();
@@ -9395,6 +9466,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/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index d3be2bf..81de356 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -186,11 +186,11 @@ public class WebViewClient {
}
/**
- * Notify the host application to handle a SSL certificate error request
- * (display the error to the user and ask whether to proceed or not). The
- * host application has to call either handler.cancel() or handler.proceed()
- * as the connection is suspended and waiting for the response. The default
- * behavior is to cancel the load.
+ * Notify the host application that an SSL error occurred while loading a
+ * resource. The host application must call either handler.cancel() or
+ * handler.proceed(). Note that the decision may be retained for use in
+ * response to future SSL errors. The default behavior is to cancel the
+ * load.
*
* @param view The WebView that is initiating the callback.
* @param handler An SslErrorHandler object that will handle the user's
@@ -203,6 +203,15 @@ public class WebViewClient {
}
/**
+ * Notify the host application that an SSL error occurred while loading a
+ * resource, but the WebView but chose to proceed anyway based on a
+ * decision retained from a previous response to onReceivedSslError().
+ * @hide
+ */
+ public void onProceededAfterSslError(WebView view, SslError error) {
+ }
+
+ /**
* Notify the host application to handle a SSL client certificate
* request (display the request to the user and ask whether to
* proceed with a client certificate or not). The host application
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 63c4d03..1294a28 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -518,7 +518,7 @@ public final class WebViewCore {
/**
* Update the layers' content
*/
- private native int nativeUpdateLayers(Region invalRegion);
+ private native boolean nativeUpdateLayers(int baseLayer);
private native boolean nativeFocusBoundsChanged();
@@ -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 " +
@@ -1662,24 +1668,15 @@ public final class WebViewCore {
mDrawIsScheduled = false;
}
if (mMessages != null) {
- Log.w(LOGTAG, "Not supported in this case.");
+ Throwable throwable = new Throwable(
+ "EventHub.removeMessages(int what = " + what + ") is not supported " +
+ "before the WebViewCore is set up.");
+ Log.w(LOGTAG, Log.getStackTraceString(throwable));
} else {
mHandler.removeMessages(what);
}
}
- private synchronized boolean hasMessages(int what) {
- if (mBlockMessages) {
- return false;
- }
- if (mMessages != null) {
- Log.w(LOGTAG, "hasMessages() is not supported in this case.");
- return false;
- } else {
- return mHandler.hasMessages(what);
- }
- }
-
private synchronized void sendMessageDelayed(Message msg, long delay) {
if (mBlockMessages) {
return;
@@ -2004,18 +2001,25 @@ public final class WebViewCore {
boolean mFocusSizeChanged;
}
+ DrawData mLastDrawData = null;
+
// Only update the layers' content, not the base surface
// PictureSet.
private void webkitDrawLayers() {
mDrawLayersIsScheduled = false;
- if (mDrawIsScheduled) {
+ if (mDrawIsScheduled || mLastDrawData == null) {
removeMessages(EventHub.WEBKIT_DRAW);
webkitDraw();
return;
}
- DrawData draw = new DrawData();
- draw.mBaseLayer = nativeUpdateLayers(draw.mInvalRegion);
- webkitDraw(draw);
+ // Directly update the layers we last passed to the UI side
+ if (nativeUpdateLayers(mLastDrawData.mBaseLayer)) {
+ // If anything more complex than position has been touched, let's do a full draw
+ webkitDraw();
+ }
+ mWebView.mPrivateHandler.removeMessages(WebView.INVAL_RECT_MSG_ID);
+ mWebView.mPrivateHandler.sendMessageAtFrontOfQueue(mWebView.mPrivateHandler
+ .obtainMessage(WebView.INVAL_RECT_MSG_ID));
}
private void webkitDraw() {
@@ -2032,6 +2036,7 @@ public final class WebViewCore {
}
return;
}
+ mLastDrawData = draw;
webkitDraw(draw);
}
@@ -2827,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/AbsListView.java b/core/java/android/widget/AbsListView.java
index 7b8c7f2..38bb2e1 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -237,9 +237,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
SparseBooleanArray mCheckStates;
/**
- * Running state of which IDs are currently checked
+ * Running state of which IDs are currently checked.
+ * If there is a value for a given key, the checked state for that ID is true
+ * and the value holds the last known position in the adapter for that id.
*/
- LongSparseArray<Boolean> mCheckedIdStates;
+ LongSparseArray<Integer> mCheckedIdStates;
/**
* Controls how the next layout will happen
@@ -472,6 +474,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
static final int OVERSCROLL_LIMIT_DIVISOR = 3;
/**
+ * How many positions in either direction we will search to try to
+ * find a checked item with a stable ID that moved position across
+ * a data set change. If the item isn't found it will be unselected.
+ */
+ private static final int CHECK_POSITION_SEARCH_DISTANCE = 20;
+
+ /**
* Used to request a layout when we changed touch mode
*/
private static final int TOUCH_MODE_UNKNOWN = -1;
@@ -806,7 +815,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (adapter != null) {
if (mChoiceMode != CHOICE_MODE_NONE && mAdapter.hasStableIds() &&
mCheckedIdStates == null) {
- mCheckedIdStates = new LongSparseArray<Boolean>();
+ mCheckedIdStates = new LongSparseArray<Integer>();
}
}
@@ -901,7 +910,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return new long[0];
}
- final LongSparseArray<Boolean> idStates = mCheckedIdStates;
+ final LongSparseArray<Integer> idStates = mCheckedIdStates;
final int count = idStates.size();
final long[] ids = new long[count];
@@ -948,7 +957,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mCheckStates.put(position, value);
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
if (value) {
- mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ mCheckedIdStates.put(mAdapter.getItemId(position), position);
} else {
mCheckedIdStates.delete(mAdapter.getItemId(position));
}
@@ -980,7 +989,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (value) {
mCheckStates.put(position, true);
if (updateIds) {
- mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ mCheckedIdStates.put(mAdapter.getItemId(position), position);
}
mCheckedItemCount = 1;
} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
@@ -1010,7 +1019,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mCheckStates.put(position, newValue);
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
if (newValue) {
- mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ mCheckedIdStates.put(mAdapter.getItemId(position), position);
} else {
mCheckedIdStates.delete(mAdapter.getItemId(position));
}
@@ -1032,7 +1041,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mCheckStates.put(position, true);
if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
mCheckedIdStates.clear();
- mCheckedIdStates.put(mAdapter.getItemId(position), Boolean.TRUE);
+ mCheckedIdStates.put(mAdapter.getItemId(position), position);
}
mCheckedItemCount = 1;
} else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
@@ -1081,7 +1090,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mCheckStates = new SparseBooleanArray();
}
if (mCheckedIdStates == null && mAdapter != null && mAdapter.hasStableIds()) {
- mCheckedIdStates = new LongSparseArray<Boolean>();
+ mCheckedIdStates = new LongSparseArray<Integer>();
}
// Modal multi-choice mode only has choices when the mode is active. Clear them.
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
@@ -1270,40 +1279,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setScrollable(true);
- }
-
- @Override
public void sendAccessibilityEvent(int eventType) {
// Since this class calls onScrollChanged even if the mFirstPosition and the
// child count have not changed we will avoid sending duplicate accessibility
// events.
if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- final int lastPosition = mFirstPosition + getChildCount();
- if (mLastAccessibilityScrollEventFromIndex == mFirstPosition
- && mLastAccessibilityScrollEventToIndex == lastPosition) {
+ final int firstVisiblePosition = getFirstVisiblePosition();
+ final int lastVisiblePosition = getLastVisiblePosition();
+ if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
+ && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
return;
} else {
- mLastAccessibilityScrollEventFromIndex = mFirstPosition;
- mLastAccessibilityScrollEventToIndex = lastPosition;
+ mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
+ mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
}
}
super.sendAccessibilityEvent(eventType);
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setScrollable(true);
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- event.setFromIndex(mFirstPosition);
- event.setToIndex(mFirstPosition + getChildCount());
- event.setItemCount(mItemCount);
- }
- }
-
/**
* Indicates whether the children's drawing cache is used during a scroll.
* By default, the drawing cache is enabled but this will consume more memory.
@@ -1427,7 +1420,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
boolean inActionMode;
int checkedItemCount;
SparseBooleanArray checkState;
- LongSparseArray<Boolean> checkIdState;
+ LongSparseArray<Integer> checkIdState;
/**
* Constructor called from {@link AbsListView#onSaveInstanceState()}
@@ -1450,11 +1443,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
inActionMode = in.readByte() != 0;
checkedItemCount = in.readInt();
checkState = in.readSparseBooleanArray();
- long[] idState = in.createLongArray();
-
- if (idState.length > 0) {
- checkIdState = new LongSparseArray<Boolean>();
- checkIdState.setValues(idState, Boolean.TRUE);
+ final int N = in.readInt();
+ if (N > 0) {
+ checkIdState = new LongSparseArray<Integer>();
+ for (int i=0; i<N; i++) {
+ final long key = in.readLong();
+ final int value = in.readInt();
+ checkIdState.put(key, value);
+ }
}
}
@@ -1470,7 +1466,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
out.writeByte((byte) (inActionMode ? 1 : 0));
out.writeInt(checkedItemCount);
out.writeSparseBooleanArray(checkState);
- out.writeLongArray(checkIdState != null ? checkIdState.getKeys() : new long[0]);
+ final int N = checkIdState != null ? checkIdState.size() : 0;
+ out.writeInt(N);
+ for (int i=0; i<N; i++) {
+ out.writeLong(checkIdState.keyAt(i));
+ out.writeInt(checkIdState.valueAt(i));
+ }
}
@Override
@@ -1565,7 +1566,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
ss.checkState = mCheckStates.clone();
}
if (mCheckedIdStates != null) {
- final LongSparseArray<Boolean> idState = new LongSparseArray<Boolean>();
+ final LongSparseArray<Integer> idState = new LongSparseArray<Integer>();
final int count = mCheckedIdStates.size();
for (int i = 0; i < count; i++) {
idState.put(mCheckedIdStates.keyAt(i), mCheckedIdStates.valueAt(i));
@@ -4786,15 +4787,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return selectedPos >= 0;
}
+ void confirmCheckedPositionsById() {
+ // Clear out the positional check states, we'll rebuild it below from IDs.
+ mCheckStates.clear();
+
+ boolean checkedCountChanged = false;
+ for (int checkedIndex = 0; checkedIndex < mCheckedIdStates.size(); checkedIndex++) {
+ final long id = mCheckedIdStates.keyAt(checkedIndex);
+ final int lastPos = mCheckedIdStates.valueAt(checkedIndex);
+
+ final long lastPosId = mAdapter.getItemId(lastPos);
+ if (id != lastPosId) {
+ // Look around to see if the ID is nearby. If not, uncheck it.
+ final int start = Math.max(0, lastPos - CHECK_POSITION_SEARCH_DISTANCE);
+ final int end = Math.min(lastPos + CHECK_POSITION_SEARCH_DISTANCE, mItemCount);
+ boolean found = false;
+ for (int searchPos = start; searchPos < end; searchPos++) {
+ final long searchId = mAdapter.getItemId(searchPos);
+ if (id == searchId) {
+ found = true;
+ mCheckStates.put(searchPos, true);
+ mCheckedIdStates.setValueAt(checkedIndex, searchPos);
+ break;
+ }
+ }
+
+ if (!found) {
+ mCheckedIdStates.delete(id);
+ checkedIndex--;
+ mCheckedItemCount--;
+ checkedCountChanged = true;
+ if (mChoiceActionMode != null && mMultiChoiceModeCallback != null) {
+ mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode,
+ lastPos, id, false);
+ }
+ }
+ } else {
+ mCheckStates.put(lastPos, true);
+ }
+ }
+
+ if (checkedCountChanged && mChoiceActionMode != null) {
+ mChoiceActionMode.invalidate();
+ }
+ }
+
@Override
protected void handleDataChanged() {
int count = mItemCount;
int lastHandledItemCount = mLastHandledItemCount;
mLastHandledItemCount = mItemCount;
- if (count > 0) {
- int newPos;
+ if (mChoiceMode != CHOICE_MODE_NONE && mAdapter != null && mAdapter.hasStableIds()) {
+ confirmCheckedPositionsById();
+ }
+ if (count > 0) {
+ int newPos;
int selectablePos;
// Find the row we are supposed to sync to
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index df8eb05..475b8ee 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -335,6 +335,7 @@ public abstract class AbsSeekBar extends ProgressBar {
mTouchDownX = event.getX();
} else {
setPressed(true);
+ invalidate(mThumb.getBounds()); // This may be within the padding region
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
@@ -348,6 +349,7 @@ public abstract class AbsSeekBar extends ProgressBar {
final float x = event.getX();
if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) {
setPressed(true);
+ invalidate(mThumb.getBounds()); // This may be within the padding region
onStartTrackingTouch();
trackTouchEvent(event);
attemptClaimDrag();
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index c37cc52..60b24bc 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -33,6 +33,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.ActivityChooserModel.ActivityChooserModelClient;
/**
@@ -169,6 +171,11 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
private boolean mIsAttachedToWindow;
/**
+ * String resource for formatting content description of the default target.
+ */
+ private int mDefaultActionButtonContentDescription;
+
+ /**
* Create a new instance.
*
* @param context The application environment.
@@ -259,7 +266,7 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
*
* <strong>Note:</strong> Clients would like to set this drawable
* as a clue about the action the chosen activity will perform. For
- * example, if share activity is to be chosen the drawable should
+ * example, if a share activity is to be chosen the drawable should
* give a clue that sharing is to be performed.
*
* @param drawable The drawable.
@@ -269,6 +276,21 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
}
/**
+ * Sets the content description for the button that expands the activity
+ * overflow list.
+ *
+ * description as a clue about the action performed by the button.
+ * For example, if a share activity is to be chosen the content
+ * description should be something like "Share with".
+ *
+ * @param resourceId The content description resource id.
+ */
+ public void setExpandActivityOverflowButtonContentDescription(int resourceId) {
+ CharSequence contentDescription = mContext.getString(resourceId);
+ mExpandActivityOverflowButtonImage.setContentDescription(contentDescription);
+ }
+
+ /**
* Set the provider hosting this view, if applicable.
* @hide Internal use only
*/
@@ -329,6 +351,8 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
if (mProvider != null) {
mProvider.subUiVisibilityChanged(true);
}
+ popupWindow.getListView().setContentDescription(mContext.getString(
+ R.string.activitychooserview_choose_application));
}
}
@@ -431,6 +455,20 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
}
/**
+ * Sets a content description of the default action button. This
+ * resource should be a string taking one formatting argument and
+ * will be used for formatting the content description of the button
+ * dynamically as the default target changes. For example, a resource
+ * pointing to the string "share with %1$s" will result in a content
+ * description "share with Bluetooth" for the Bluetooth activity.
+ *
+ * @param resourceId The resource id.
+ */
+ public void setDefaultActionButtonContentDescription(int resourceId) {
+ mDefaultActionButtonContentDescription = resourceId;
+ }
+
+ /**
* Gets the list popup window which is lazily initialized.
*
* @return The popup.
@@ -465,6 +503,12 @@ public class ActivityChooserView extends ViewGroup implements ActivityChooserMod
ResolveInfo activity = mAdapter.getDefaultActivity();
PackageManager packageManager = mContext.getPackageManager();
mDefaultActivityButtonImage.setImageDrawable(activity.loadIcon(packageManager));
+ if (mDefaultActionButtonContentDescription != 0) {
+ CharSequence label = activity.loadLabel(packageManager);
+ String contentDescription = mContext.getString(
+ mDefaultActionButtonContentDescription, label);
+ mDefaultActivityButton.setContentDescription(contentDescription);
+ }
} else {
mDefaultActivityButton.setVisibility(View.GONE);
}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index a4b4e78..e16a8bd 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -29,6 +29,7 @@ import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
/**
@@ -881,25 +882,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- final int eventType = event.getEventType();
- switch (eventType) {
- case AccessibilityEvent.TYPE_VIEW_SCROLLED:
- // Do not populate the text of scroll events.
- return true;
- case AccessibilityEvent.TYPE_VIEW_FOCUSED:
- // This is an exceptional case which occurs when a window gets the
- // focus and sends a focus event via its focused child to announce
- // current focus/selection. AdapterView fires selection but not focus
- // events so we change the event type here.
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
- event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
- break;
- }
-
View selectedView = getSelectedView();
- if (selectedView != null && selectedView.getVisibility() == VISIBLE) {
- getSelectedView().dispatchPopulateAccessibilityEvent(event);
+ if (selectedView != null && selectedView.getVisibility() == VISIBLE
+ && selectedView.dispatchPopulateAccessibilityEvent(event)) {
+ return true;
}
return false;
}
@@ -919,19 +905,37 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
@Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setScrollable(isScrollableForAccessibility());
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ info.setEnabled(selectedView.isEnabled());
+ }
+ }
+
+ @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
-
+ event.setScrollable(isScrollableForAccessibility());
View selectedView = getSelectedView();
if (selectedView != null) {
event.setEnabled(selectedView.isEnabled());
}
- event.setItemCount(getCount());
event.setCurrentItemIndex(getSelectedItemPosition());
- if (getChildCount() > 0) {
- event.setFromIndex(getFirstVisiblePosition());
- event.setToIndex(getLastVisiblePosition());
+ event.setFromIndex(getFirstVisiblePosition());
+ event.setToIndex(getLastVisiblePosition());
+ event.setItemCount(getAdapter().getCount());
+ }
+
+ private boolean isScrollableForAccessibility() {
+ 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/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 84ebec3..63a0870 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -25,6 +25,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.AttributeSet;
import android.view.View;
@@ -228,6 +229,8 @@ public class AnalogClock extends View {
mMinutes = minute + second / 60.0f;
mHour = hour + mMinutes / 60.0f;
mChanged = true;
+
+ updateContentDescription(mCalendar);
}
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -243,4 +246,11 @@ public class AnalogClock extends View {
invalidate();
}
};
+
+ private void updateContentDescription(Time time) {
+ final int flags = DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR;
+ String contentDescription = DateUtils.formatDateTime(mContext,
+ time.toMillis(false), flags);
+ setContentDescription(contentDescription);
+ }
}
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/Gallery.java b/core/java/android/widget/Gallery.java
index 3f5b571..5e37fa8 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -32,8 +32,6 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Transformation;
import com.android.internal.R;
@@ -354,33 +352,6 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
return child.getMeasuredHeight();
}
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setScrollable(true);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setScrollable(true);
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- event.setFromIndex(mFirstPosition);
- event.setToIndex(mFirstPosition + getChildCount());
- event.setItemCount(mItemCount);
- }
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- // Do not append text content to scroll events they are fired frequently
- // and the client has already received another event type with the text.
- if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- super.dispatchPopulateAccessibilityEvent(event);
- }
- return false;
- }
-
/**
* Tracks a motion scroll. In reality, this is used to do just about any
* movement to items (touch scroll, arrow-key scroll, set an item as selected).
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1bbc501..57701ae 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -716,23 +716,17 @@ public class HorizontalScrollView extends FrameLayout {
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setScrollable(true);
+ info.setScrollable(getScrollRange() > 0);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- event.setScrollable(true);
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- // Do not append text content to scroll events they are fired frequently
- // and the client has already received another event type with the text.
- if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- super.dispatchPopulateAccessibilityEvent(event);
- }
- return false;
+ event.setScrollable(getScrollRange() > 0);
+ event.setScrollX(mScrollX);
+ event.setScrollY(mScrollY);
+ event.setMaxScrollX(getScrollRange());
+ event.setMaxScrollY(mScrollY);
}
private int getScrollRange() {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 42e27b1..9ef1aa1 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1997,31 +1997,6 @@ public class ListView extends AbsListView {
}
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
-
- // If the item count is less than 15 then subtract disabled items from the count and
- // position. Otherwise ignore disabled items.
- int itemCount = 0;
- int currentItemIndex = getSelectedItemPosition();
-
- ListAdapter adapter = getAdapter();
- if (adapter != null) {
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- if (adapter.isEnabled(i)) {
- itemCount++;
- } else if (i <= currentItemIndex) {
- currentItemIndex--;
- }
- }
- }
-
- event.setItemCount(itemCount);
- event.setCurrentItemIndex(currentItemIndex);
- }
-
/**
* setSelectionAfterHeaderView set the selection to be the first list item
* after the header views.
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 61ea5c9..767eaee 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -721,23 +721,18 @@ public class ScrollView extends FrameLayout {
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
- info.setScrollable(true);
+ info.setScrollable(getScrollRange() > 0);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
- event.setScrollable(true);
- }
-
- @Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- // Do not append text content to scroll events they are fired frequently
- // and the client has already received another event type with the text.
- if (event.getEventType() != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
- super.dispatchPopulateAccessibilityEvent(event);
- }
- return false;
+ final boolean scrollable = getScrollRange() > 0;
+ event.setScrollable(scrollable);
+ event.setScrollX(mScrollX);
+ event.setScrollY(mScrollY);
+ event.setMaxScrollX(mScrollX);
+ event.setMaxScrollY(getScrollRange());
}
private int getScrollRange() {
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index adf2b7b..6df80e4 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -92,6 +92,11 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
private static final boolean DBG = false;
private static final String LOG_TAG = "SearchView";
+ /**
+ * Private constant for removing the microphone in the keyboard.
+ */
+ private static final String IME_OPTION_NO_MICROPHONE = "nm";
+
private OnQueryTextListener mOnQueryChangeListener;
private OnCloseListener mOnCloseListener;
private OnFocusChangeListener mOnQueryTextFocusChangeListener;
@@ -256,7 +261,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
mQueryTextView.setOnItemClickListener(mOnItemClickListener);
mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
mQueryTextView.setOnKeyListener(mTextKeyListener);
- // Inform any listener of focus changes
+ // Inform any listener of focus changes
mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
@@ -335,6 +340,12 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
// Cache the voice search capability
mVoiceButtonEnabled = hasVoiceSearch();
+
+ if (mVoiceButtonEnabled) {
+ // Disable the microphone on the keyboard, as a mic is displayed near the text box
+ // TODO: use imeOptions to disable voice input when the new API will be available
+ mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
+ }
updateViewsVisibility(isIconified());
}
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index 3627890..bb27b73 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -171,6 +171,12 @@ public class ShareActionProvider extends ActionProvider {
activityChooserView.setExpandActivityOverflowButtonDrawable(drawable);
activityChooserView.setProvider(this);
+ // Set content description.
+ activityChooserView.setDefaultActionButtonContentDescription(
+ R.string.shareactionprovider_share_with_application);
+ activityChooserView.setExpandActivityOverflowButtonContentDescription(
+ R.string.shareactionprovider_share_with);
+
return activityChooserView;
}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index ac9535a..1da18aa 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.text.Editable;
import android.text.Selection;
import android.text.Spanned;
+import android.text.method.WordIterator;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
import android.view.textservice.SpellCheckerSession;
@@ -30,7 +31,7 @@ import android.view.textservice.TextServicesManager;
import com.android.internal.util.ArrayUtils;
-import java.util.Locale;
+import java.text.BreakIterator;
/**
@@ -40,6 +41,8 @@ import java.util.Locale;
*/
public class SpellChecker implements SpellCheckerSessionListener {
+ private final static int MAX_SPELL_BATCH_SIZE = 50;
+
private final TextView mTextView;
final SpellCheckerSession mSpellCheckerSession;
@@ -47,12 +50,15 @@ public class SpellChecker implements SpellCheckerSessionListener {
// Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
// SpellCheckSpan has been recycled and can be-reused.
- // May contain null SpellCheckSpans after a given index.
+ // Contains null SpellCheckSpans after index mLength.
private int[] mIds;
private SpellCheckSpan[] mSpellCheckSpans;
// The mLength first elements of the above arrays have been initialized
private int mLength;
+ // Parsers on chunck of text, cutting text into words that will be checked
+ private SpellParser[] mSpellParsers = new SpellParser[0];
+
private int mSpanSequenceCounter = 0;
public SpellChecker(TextView textView) {
@@ -64,7 +70,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
null /* not currently used by the textServicesManager */,
null /* null locale means use the languages defined in Settings
if referToSpellCheckerLanguageSettings is true */,
- this, true /* means use the languages defined in Settings */);
+ this, true /* means use the languages defined in Settings */);
mCookie = hashCode();
// Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand
@@ -78,7 +84,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
* @return true if a spell checker session has successfully been created. Returns false if not,
* for instance when spell checking has been disabled in settings.
*/
- public boolean isSessionActive() {
+ private boolean isSessionActive() {
return mSpellCheckerSession != null;
}
@@ -86,6 +92,11 @@ public class SpellChecker implements SpellCheckerSessionListener {
if (mSpellCheckerSession != null) {
mSpellCheckerSession.close();
}
+
+ final int length = mSpellParsers.length;
+ for (int i = 0; i < length; i++) {
+ mSpellParsers[i].close();
+ }
}
private int nextSpellCheckSpanIndex() {
@@ -108,10 +119,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
return mLength - 1;
}
- public void addSpellCheckSpan(int wordStart, int wordEnd) {
+ private void addSpellCheckSpan(Editable editable, int start, int end) {
final int index = nextSpellCheckSpanIndex();
- ((Editable) mTextView.getText()).setSpan(mSpellCheckSpans[index], wordStart, wordEnd,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ editable.setSpan(mSpellCheckSpans[index], start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mIds[index] = mSpanSequenceCounter++;
}
@@ -129,10 +139,34 @@ public class SpellChecker implements SpellCheckerSessionListener {
spellCheck();
}
- public void spellCheck() {
+ public void spellCheck(int start, int end) {
+ if (!isSessionActive()) return;
+
+ final int length = mSpellParsers.length;
+ for (int i = 0; i < length; i++) {
+ final SpellParser spellParser = mSpellParsers[i];
+ if (spellParser.isDone()) {
+ spellParser.init(start, end);
+ spellParser.parse();
+ return;
+ }
+ }
+
+ // No available parser found in pool, create a new one
+ SpellParser[] newSpellParsers = new SpellParser[length + 1];
+ System.arraycopy(mSpellParsers, 0, newSpellParsers, 0, length);
+ mSpellParsers = newSpellParsers;
+
+ SpellParser spellParser = new SpellParser();
+ mSpellParsers[length] = spellParser;
+ spellParser.init(start, end);
+ spellParser.parse();
+ }
+
+ private void spellCheck() {
if (mSpellCheckerSession == null) return;
- final Editable editable = (Editable) mTextView.getText();
+ Editable editable = (Editable) mTextView.getText();
final int selectionStart = Selection.getSelectionStart(editable);
final int selectionEnd = Selection.getSelectionEnd(editable);
@@ -155,7 +189,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
if (textInfosCount > 0) {
- if (textInfosCount < mLength) {
+ if (textInfosCount < textInfos.length) {
TextInfo[] textInfosCopy = new TextInfo[textInfosCount];
System.arraycopy(textInfos, 0, textInfosCopy, 0, textInfosCount);
textInfos = textInfosCopy;
@@ -167,15 +201,14 @@ public class SpellChecker implements SpellCheckerSessionListener {
@Override
public void onGetSuggestions(SuggestionsInfo[] results) {
- final Editable editable = (Editable) mTextView.getText();
+ Editable editable = (Editable) mTextView.getText();
+
for (int i = 0; i < results.length; i++) {
SuggestionsInfo suggestionsInfo = results[i];
if (suggestionsInfo.getCookie() != mCookie) continue;
final int sequenceNumber = suggestionsInfo.getSequence();
for (int j = 0; j < mLength; j++) {
- final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
-
if (sequenceNumber == mIds[j]) {
final int attributes = suggestionsInfo.getSuggestionsAttributes();
boolean isInDictionary =
@@ -183,32 +216,214 @@ public class SpellChecker implements SpellCheckerSessionListener {
boolean looksLikeTypo =
((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);
+ SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];
if (!isInDictionary && looksLikeTypo) {
- String[] suggestions = getSuggestions(suggestionsInfo);
- SuggestionSpan suggestionSpan = new SuggestionSpan(
- mTextView.getContext(), suggestions,
- SuggestionSpan.FLAG_EASY_CORRECT |
- SuggestionSpan.FLAG_MISSPELLED);
- final int start = editable.getSpanStart(spellCheckSpan);
- final int end = editable.getSpanEnd(spellCheckSpan);
- editable.setSpan(suggestionSpan, start, end,
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- // TODO limit to the word rectangle region
- mTextView.invalidate();
+ createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan);
}
editable.removeSpan(spellCheckSpan);
+ break;
+ }
+ }
+ }
+
+ final int length = mSpellParsers.length;
+ for (int i = 0; i < length; i++) {
+ final SpellParser spellParser = mSpellParsers[i];
+ if (!spellParser.isDone()) {
+ spellParser.parse();
+ }
+ }
+ }
+
+ private void createMisspelledSuggestionSpan(Editable editable,
+ SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) {
+ final int start = editable.getSpanStart(spellCheckSpan);
+ final int end = editable.getSpanEnd(spellCheckSpan);
+ if (start < 0 || end < 0) return; // span was removed in the meantime
+
+ // 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 = editable.getSpans(start, end, SuggestionSpan.class);
+ final int length = suggestionSpans.length;
+ for (int i = 0; i < length; i++) {
+ final int spanStart = editable.getSpanStart(suggestionSpans[i]);
+ final int spanEnd = editable.getSpanEnd(suggestionSpans[i]);
+ if (spanStart != start || spanEnd != end) {
+ suggestionSpans[i] = null;
+ }
+ }
+
+ final int suggestionsCount = suggestionsInfo.getSuggestionsCount();
+ String[] suggestions;
+ if (suggestionsCount <= 0) {
+ // A negative suggestion count is possible
+ suggestions = ArrayUtils.emptyArray(String.class);
+ } else {
+ int numberOfSuggestions = 0;
+ suggestions = new String[suggestionsCount];
+
+ for (int i = 0; i < suggestionsCount; i++) {
+ final String spellSuggestion = suggestionsInfo.getSuggestionAt(i);
+ if (spellSuggestion == null) break;
+ boolean suggestionFound = false;
+
+ for (int j = 0; j < length && !suggestionFound; j++) {
+ if (suggestionSpans[j] == null) break;
+
+ String[] suggests = suggestionSpans[j].getSuggestions();
+ for (int k = 0; k < suggests.length; k++) {
+ if (spellSuggestion.equals(suggests[k])) {
+ // The suggestion is already provided by an other SuggestionSpan
+ suggestionFound = true;
+ break;
+ }
+ }
+ }
+
+ if (!suggestionFound) {
+ suggestions[numberOfSuggestions++] = spellSuggestion;
}
}
+
+ if (numberOfSuggestions != suggestionsCount) {
+ String[] newSuggestions = new String[numberOfSuggestions];
+ System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions);
+ suggestions = newSuggestions;
+ }
}
+
+ SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
+ SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
+ editable.setSpan(suggestionSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ // TODO limit to the word rectangle region
+ mTextView.invalidate();
}
- private static String[] getSuggestions(SuggestionsInfo suggestionsInfo) {
- // A negative suggestion count is possible
- final int len = Math.max(0, suggestionsInfo.getSuggestionsCount());
- String[] suggestions = new String[len];
- for (int j = 0; j < len; j++) {
- suggestions[j] = suggestionsInfo.getSuggestionAt(j);
+ private class SpellParser {
+ private WordIterator mWordIterator = new WordIterator(/*TODO Locale*/);
+ private Object mRange = new Object();
+
+ public void init(int start, int end) {
+ ((Editable) mTextView.getText()).setSpan(mRange, start, end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
+ public void close() {
+ ((Editable) mTextView.getText()).removeSpan(mRange);
+ }
+
+ public boolean isDone() {
+ 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 = 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);
+ int wordEnd;
+ if (wordStart == BreakIterator.DONE) {
+ wordEnd = mWordIterator.following(start);
+ if (wordEnd != BreakIterator.DONE) {
+ wordStart = mWordIterator.getBeginning(wordEnd);
+ }
+ } else {
+ wordEnd = mWordIterator.getEnd(wordStart);
+ }
+ if (wordEnd == BreakIterator.DONE) {
+ 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 = editable.getSpans(start - 1, end + 1,
+ SpellCheckSpan.class);
+ SuggestionSpan[] suggestionSpans = editable.getSpans(start - 1, end + 1,
+ SuggestionSpan.class);
+
+ int nbWordsChecked = 0;
+ boolean scheduleOtherSpellCheck = false;
+
+ while (wordStart <= end) {
+ if (wordEnd >= start && wordEnd > wordStart) {
+ // 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(editable, start, spellCheckSpans);
+ removeSpansAt(editable, start, suggestionSpans);
+ }
+
+ if (wordStart < end && wordEnd > end) {
+ 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 = editable.getSpanEnd(spellCheckSpans[i]);
+ if (spanEnd == start) {
+ createSpellCheckSpan = false;
+ break;
+ }
+ }
+ }
+
+ if (wordStart == end) {
+ for (int i = 0; i < spellCheckSpans.length; i++) {
+ final int spanStart = editable.getSpanStart(spellCheckSpans[i]);
+ if (spanStart == end) {
+ createSpellCheckSpan = false;
+ break;
+ }
+ }
+ }
+
+ if (createSpellCheckSpan) {
+ if (nbWordsChecked == MAX_SPELL_BATCH_SIZE) {
+ scheduleOtherSpellCheck = true;
+ break;
+ }
+ addSpellCheckSpan(editable, wordStart, wordEnd);
+ nbWordsChecked++;
+ }
+ }
+
+ // iterate word by word
+ wordEnd = mWordIterator.following(wordEnd);
+ if (wordEnd == BreakIterator.DONE) break;
+ wordStart = mWordIterator.getBeginning(wordEnd);
+ if (wordStart == BreakIterator.DONE) {
+ break;
+ }
+ }
+
+ if (scheduleOtherSpellCheck) {
+ editable.setSpan(mRange, wordStart, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ } else {
+ editable.removeSpan(mRange);
+ }
+
+ spellCheck();
+ }
+
+ 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 = editable.getSpanStart(span);
+ if (start > offset) continue;
+ final int end = editable.getSpanEnd(span);
+ if (end < offset) continue;
+ editable.removeSpan(span);
+ }
}
- return suggestions;
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5cd7902..324198f 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;
@@ -2937,11 +2938,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Spannable sp = new SpannableString(mText);
- for (ChangeWatcher cw :
- sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
+ for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
sp.removeSpan(cw);
}
+ SuggestionSpan[] suggestionSpans = sp.getSpans(0, sp.length(), SuggestionSpan.class);
+ for (int i = 0; i < suggestionSpans.length; i++) {
+ int flags = suggestionSpans[i].getFlags();
+ if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
+ && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
+ sp.removeSpan(suggestionSpans[i]);
+ }
+ }
+
sp.removeSpan(mSuggestionRangeSpan);
ss.text = sp;
@@ -4449,7 +4458,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mSpellChecker != null) {
mSpellChecker.closeSession();
- removeMisspelledSpans();
// Forces the creation of a new SpellChecker next time this window is created.
// Will handle the cases where the settings has been changed in the meantime.
mSpellChecker = null;
@@ -6115,7 +6123,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* not the full view width with padding.
* {@hide}
*/
- protected void makeNewLayout(int w, int hintWidth,
+ protected void makeNewLayout(int wantWidth, int hintWidth,
BoringLayout.Metrics boring,
BoringLayout.Metrics hintBoring,
int ellipsisWidth, boolean bringIntoView) {
@@ -6127,8 +6135,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mHighlightPathBogus = true;
- if (w < 0) {
- w = 0;
+ if (wantWidth < 0) {
+ wantWidth = 0;
}
if (hintWidth < 0) {
hintWidth = 0;
@@ -6148,12 +6156,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
resolveTextDirection();
}
- mLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment, shouldEllipsize,
+ mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
effectiveEllipsize, effectiveEllipsize == mEllipsize);
if (switchEllipsize) {
TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
TruncateAt.END : TruncateAt.MARQUEE;
- mSavedMarqueeModeLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment,
+ mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
}
@@ -6161,7 +6169,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mHintLayout = null;
if (mHint != null) {
- if (shouldEllipsize) hintWidth = w;
+ if (shouldEllipsize) hintWidth = wantWidth;
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
@@ -6245,15 +6253,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
prepareCursorControllers();
}
- private Layout makeSingleLayout(int w, BoringLayout.Metrics boring, int ellipsisWidth,
+ private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
boolean useSaved) {
Layout result = null;
if (mText instanceof Spannable) {
- result = new DynamicLayout(mText, mTransformed, mTextPaint, w,
+ result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
- ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
+ ellipsisWidth);
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
@@ -6263,53 +6271,53 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (boring != null) {
- if (boring.width <= w &&
+ if (boring.width <= wantWidth &&
(effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
if (useSaved && mSavedLayout != null) {
result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
+ wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad);
} else {
result = BoringLayout.make(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
+ wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad);
}
if (useSaved) {
mSavedLayout = (BoringLayout) result;
}
- } else if (shouldEllipsize && boring.width <= w) {
+ } else if (shouldEllipsize && boring.width <= wantWidth) {
if (useSaved && mSavedLayout != null) {
result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
+ wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth);
} else {
result = BoringLayout.make(mTransformed, mTextPaint,
- w, alignment, mSpacingMult, mSpacingAdd,
+ wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth);
}
} else if (shouldEllipsize) {
result = new StaticLayout(mTransformed,
0, mTransformed.length(),
- mTextPaint, w, alignment, mTextDir, mSpacingMult,
+ mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, effectiveEllipsize,
ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
} else {
result = new StaticLayout(mTransformed, mTextPaint,
- w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
+ wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad);
}
} else if (shouldEllipsize) {
result = new StaticLayout(mTransformed,
0, mTransformed.length(),
- mTextPaint, w, alignment, mTextDir, mSpacingMult,
+ mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
mSpacingAdd, mIncludePad, effectiveEllipsize,
ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
} else {
result = new StaticLayout(mTransformed, mTextPaint,
- w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
+ wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad);
}
}
@@ -7740,98 +7748,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Create new SpellCheckSpans on the modified region.
*/
private void updateSpellCheckSpans(int start, int end) {
- if (!isTextEditable() || !isSuggestionsEnabled() || !getSpellChecker().isSessionActive())
- return;
- Editable text = (Editable) mText;
-
- final int shift = prepareWordIterator(start, end);
- final int shiftedStart = start - shift;
- final int shiftedEnd = end - shift;
-
- // Move back to the beginning of the current word, if any
- int wordStart = mWordIterator.preceding(shiftedStart);
- int wordEnd;
- if (wordStart == BreakIterator.DONE) {
- wordEnd = mWordIterator.following(shiftedStart);
- if (wordEnd != BreakIterator.DONE) {
- wordStart = mWordIterator.getBeginning(wordEnd);
- }
- } else {
- wordEnd = mWordIterator.getEnd(wordStart);
- }
- if (wordEnd == BreakIterator.DONE) {
- 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 = text.getSpans(start - 1, end + 1, SpellCheckSpan.class);
- SuggestionSpan[] suggestionSpans = text.getSpans(start - 1, end + 1, SuggestionSpan.class);
- final int numberOfSpellCheckSpans = spellCheckSpans.length;
-
- // Iterate over the newly added text and schedule new SpellCheckSpans
- while (wordStart <= shiftedEnd) {
- if (wordEnd >= shiftedStart) {
- // A new word has been created across the interval boundaries. Remove previous spans
- if (wordStart < shiftedStart && wordEnd > shiftedStart) {
- removeSpansAt(start, spellCheckSpans, text);
- removeSpansAt(start, suggestionSpans, text);
- }
-
- if (wordStart < shiftedEnd && wordEnd > shiftedEnd) {
- removeSpansAt(end, spellCheckSpans, text);
- removeSpansAt(end, suggestionSpans, text);
- }
-
- // Do not create new boundary spans if they already exist
- boolean createSpellCheckSpan = true;
- if (wordEnd == shiftedStart) {
- for (int i = 0; i < numberOfSpellCheckSpans; i++) {
- final int spanEnd = text.getSpanEnd(spellCheckSpans[i]);
- if (spanEnd == start) {
- createSpellCheckSpan = false;
- break;
- }
- }
- }
-
- if (wordStart == shiftedEnd) {
- for (int i = 0; i < numberOfSpellCheckSpans; i++) {
- final int spanStart = text.getSpanStart(spellCheckSpans[i]);
- if (spanStart == end) {
- createSpellCheckSpan = false;
- break;
- }
- }
- }
-
- if (createSpellCheckSpan) {
- mSpellChecker.addSpellCheckSpan(wordStart + shift, wordEnd + shift);
- }
- }
-
- // iterate word by word
- wordEnd = mWordIterator.following(wordEnd);
- if (wordEnd == BreakIterator.DONE) break;
- wordStart = mWordIterator.getBeginning(wordEnd);
- if (wordStart == BreakIterator.DONE) {
- Log.e(LOG_TAG, "No word beginning from " + (wordEnd + shift) + "in " + mText);
- break;
- }
- }
-
- mSpellChecker.spellCheck();
- }
-
- private static <T> void removeSpansAt(int offset, T[] spans, Editable text) {
- final int length = spans.length;
- for (int i = 0; i < length; i++) {
- final T span = spans[i];
- final int start = text.getSpanStart(span);
- if (start > offset) continue;
- final int end = text.getSpanEnd(span);
- if (end < offset) continue;
- text.removeSpan(span);
+ if (isTextEditable() && isSuggestionsEnabled()) {
+ getSpellChecker().spellCheck(start, end);
}
}
@@ -8461,24 +8379,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- /**
- * Removes the suggestion spans for misspelled words.
- */
- private void removeMisspelledSpans() {
- if (mText instanceof Spannable) {
- Spannable spannable = (Spannable) mText;
- SuggestionSpan[] suggestionSpans = spannable.getSpans(0,
- spannable.length(), SuggestionSpan.class);
- for (int i = 0; i < suggestionSpans.length; i++) {
- int flags = suggestionSpans[i].getFlags();
- if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
- && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
- spannable.removeSpan(suggestionSpans[i]);
- }
- }
- }
- }
-
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable && mLayout != null) {
@@ -8934,37 +8834,57 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// If a URLSpan (web address, email, phone...) is found at that position, select it.
URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class);
- if (urlSpans.length == 1) {
- URLSpan url = urlSpans[0];
- selectionStart = ((Spanned) mText).getSpanStart(url);
- selectionEnd = ((Spanned) mText).getSpanEnd(url);
+ if (urlSpans.length >= 1) {
+ URLSpan urlSpan = urlSpans[0];
+ selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
+ selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
} else {
- final int shift = prepareWordIterator(minOffset, maxOffset);
+ if (mWordIterator == null) {
+ mWordIterator = new WordIterator();
+ }
+ mWordIterator.setCharSequence(mText, minOffset, maxOffset);
- selectionStart = mWordIterator.getBeginning(minOffset - shift);
+ selectionStart = mWordIterator.getBeginning(minOffset);
if (selectionStart == BreakIterator.DONE) return false;
- selectionStart += shift;
- selectionEnd = mWordIterator.getEnd(maxOffset - shift);
+ selectionEnd = mWordIterator.getEnd(maxOffset);
if (selectionEnd == BreakIterator.DONE) return false;
- selectionEnd += shift;
+
+ if (selectionStart == selectionEnd) {
+ // Possible when the word iterator does not properly handle the text's language
+ long range = getCharRange(selectionStart);
+ selectionStart = extractRangeStartFromLong(range);
+ selectionEnd = extractRangeEndFromLong(range);
+ }
}
Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
- return true;
+ return selectionEnd > selectionStart;
}
- int prepareWordIterator(int start, int end) {
- if (mWordIterator == null) {
- mWordIterator = new WordIterator();
+ private long getCharRange(int offset) {
+ final int textLength = mText.length();
+ if (offset + 1 < textLength) {
+ final char currentChar = mText.charAt(offset);
+ final char nextChar = mText.charAt(offset + 1);
+ if (Character.isSurrogatePair(currentChar, nextChar)) {
+ return packRangeInLong(offset, offset + 2);
+ }
}
-
- final int TEXT_WINDOW_WIDTH = 50; // Should be larger than the longest word's length
- final int windowStart = Math.max(0, start - TEXT_WINDOW_WIDTH);
- final int windowEnd = Math.min(mText.length(), end + TEXT_WINDOW_WIDTH);
- mWordIterator.setCharSequence(mText.subSequence(windowStart, windowEnd));
-
- return windowStart;
+ if (offset < textLength) {
+ return packRangeInLong(offset, offset + 1);
+ }
+ if (offset - 2 >= 0) {
+ final char previousChar = mText.charAt(offset - 1);
+ final char previousPreviousChar = mText.charAt(offset - 2);
+ if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
+ return packRangeInLong(offset - 2, offset);
+ }
+ }
+ if (offset - 1 >= 0) {
+ return packRangeInLong(offset - 1, offset);
+ }
+ return packRangeInLong(offset, offset);
}
private SpellChecker getSpellChecker() {
@@ -9340,7 +9260,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Start a new selection
if (!handled) {
- handled = startSelectionActionMode();
+ vibrate = handled = startSelectionActionMode();
}
if (vibrate) {
@@ -9688,15 +9608,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
SpannableStringBuilder text = new SpannableStringBuilder();
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
android.R.style.TextAppearance_SuggestionHighlight);
-
- void removeMisspelledFlag() {
- int suggestionSpanFlags = suggestionSpan.getFlags();
- if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
- suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
- suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
- suggestionSpan.setFlags(suggestionSpanFlags);
- }
- }
}
private class SuggestionAdapter extends BaseAdapter {
@@ -9930,7 +9841,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Fallback on the default highlight color when the first span does not provide one
mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
} else {
- final float BACKGROUND_TRANSPARENCY = 0.3f;
+ final float BACKGROUND_TRANSPARENCY = 0.4f;
final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
mSuggestionRangeSpan.setBackgroundColor(
(underlineColor & 0x00FFFFFF) + (newAlpha << 24));
@@ -9956,8 +9867,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// Add the text before and after the span.
- suggestionInfo.text.insert(0, mText.subSequence(unionStart, spanStart).toString());
- suggestionInfo.text.append(mText.subSequence(spanEnd, unionEnd).toString());
+ suggestionInfo.text.insert(0, mText.toString().substring(unionStart, spanStart));
+ suggestionInfo.text.append(mText.toString().substring(spanEnd, unionEnd));
}
@Override
@@ -9989,14 +9900,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
hide();
return;
}
- final String originalText = mText.subSequence(spanStart, spanEnd).toString();
+ final String originalText = mText.toString().substring(spanStart, spanEnd);
if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) {
Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
intent.putExtra("word", originalText);
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
- suggestionInfo.removeMisspelledFlag();
+ // There is no way to know if the word was indeed added. Re-check.
+ editable.removeSpan(suggestionInfo.suggestionSpan);
+ updateSpellCheckSpans(spanStart, spanEnd);
} else {
// SuggestionSpans are removed by replace: save them before
SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd,
@@ -10010,6 +9923,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
+
+ // Remove potential misspelled flags
+ int suggestionSpanFlags = suggestionSpan.getFlags();
+ if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
+ suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
+ suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
+ suggestionSpan.setFlags(suggestionSpanFlags);
+ }
}
final int suggestionStart = suggestionInfo.suggestionStart;
@@ -10018,14 +9939,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
suggestionStart, suggestionEnd).toString();
editable.replace(spanStart, spanEnd, suggestion);
- suggestionInfo.removeMisspelledFlag();
-
// Notify source IME of the suggestion pick. Do this before swaping texts.
if (!TextUtils.isEmpty(
suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
InputMethodManager imm = InputMethodManager.peekInstance();
- imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
- suggestionInfo.suggestionIndex);
+ if (imm != null) {
+ imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
+ suggestionInfo.suggestionIndex);
+ }
}
// Swap text content between actual text and Suggestion span
@@ -10045,7 +9966,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- // Move cursor at the end of the replacement word
+ // Move cursor at the end of the replaced word
Selection.setSelection(editable, spanEnd + lengthDifference);
}
@@ -10174,8 +10095,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (!hasSelection()) {
// There may already be a selection on device rotation
- boolean currentWordSelected = selectCurrentWord();
- if (!currentWordSelected) {
+ if (!selectCurrentWord()) {
// No word found under cursor or text selection not permitted.
return false;
}
@@ -10256,7 +10176,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme);
+ TypedArray styledAttributes = mContext.obtainStyledAttributes(
+ com.android.internal.R.styleable.SelectionModeDrawables);
boolean allowText = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
@@ -10269,7 +10190,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (!allowText) {
// Provide an icon, text will not be displayed on smaller screens.
selectAllIconId = styledAttributes.getResourceId(
- R.styleable.Theme_actionModeSelectAllDrawable, 0);
+ R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0);
}
menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
@@ -10281,7 +10202,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (canCut()) {
menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
setIcon(styledAttributes.getResourceId(
- R.styleable.Theme_actionModeCutDrawable, 0)).
+ R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)).
setAlphabeticShortcut('x').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
@@ -10290,7 +10211,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (canCopy()) {
menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
setIcon(styledAttributes.getResourceId(
- R.styleable.Theme_actionModeCopyDrawable, 0)).
+ R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)).
setAlphabeticShortcut('c').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
@@ -10299,7 +10220,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (canPaste()) {
menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
setIcon(styledAttributes.getResourceId(
- R.styleable.Theme_actionModePasteDrawable, 0)).
+ R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)).
setAlphabeticShortcut('v').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
@@ -11579,7 +11500,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private static final RectF sTempRect = new RectF();
// XXX should be much larger
- private static final int VERY_WIDE = 16384;
+ private static final int VERY_WIDE = 1024*1024;
private static final int BLINK = 500;
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 7444d46..f52e773 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -237,9 +237,7 @@ public class TimePicker extends FrameLayout {
}
// set the content descriptions
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- setContentDescriptions();
- }
+ setContentDescriptions();
}
@Override
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 351714e..6a0cd36 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -68,6 +68,7 @@ interface IBatteryStats {
void noteScanWifiLockReleasedFromSource(in WorkSource ws);
void noteWifiMulticastEnabledFromSource(in WorkSource ws);
void noteWifiMulticastDisabledFromSource(in WorkSource ws);
+ void noteNetworkInterfaceType(String iface, int type);
void setBatteryState(int status, int health, int plugType, int level, int temp, int volt);
long getAwakeTimeBattery();
long getAwakeTimePlugged();
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/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java
index 719847e..36fc24e 100644
--- a/core/java/com/android/internal/app/RingtonePickerActivity.java
+++ b/core/java/com/android/internal/app/RingtonePickerActivity.java
@@ -47,7 +47,9 @@ public final class RingtonePickerActivity extends AlertActivity implements
private static final String TAG = "RingtonePickerActivity";
private static final int DELAY_MS_SELECTION_PLAYED = 300;
-
+
+ private static final String SAVE_CLICKED_POS = "clicked_pos";
+
private RingtoneManager mRingtoneManager;
private Cursor mCursor;
@@ -120,7 +122,10 @@ public final class RingtonePickerActivity extends AlertActivity implements
if (mUriForDefaultItem == null) {
mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI;
}
-
+
+ if (savedInstanceState != null) {
+ mClickedPos = savedInstanceState.getInt(SAVE_CLICKED_POS, -1);
+ }
// Get whether to show the 'Silent' item
mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
@@ -167,6 +172,12 @@ public final class RingtonePickerActivity extends AlertActivity implements
setupAlert();
}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(SAVE_CLICKED_POS, mClickedPos);
+ }
+
public void onPrepareListView(ListView listView) {
if (mHasDefaultItem) {
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/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
new file mode 100644
index 0000000..ee3f23b
--- /dev/null
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static com.android.server.NetworkManagementSocketTagger.kernelToTag;
+
+import android.net.NetworkStats;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+import libcore.io.IoUtils;
+
+/**
+ * Creates {@link NetworkStats} instances by parsing various {@code /proc/}
+ * files as needed.
+ */
+public class NetworkStatsFactory {
+ private static final String TAG = "NetworkStatsFactory";
+
+ // TODO: consider moving parsing to native code
+
+ /** Path to {@code /proc/net/dev}. */
+ @Deprecated
+ private final File mStatsIface;
+ /** Path to {@code /proc/net/xt_qtaguid/iface_stat}. */
+ @Deprecated
+ private final File mStatsXtIface;
+ /** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
+ private final File mStatsXtIfaceAll;
+ /** Path to {@code /proc/net/xt_qtaguid/stats}. */
+ private final File mStatsXtUid;
+
+ /** {@link #mStatsXtUid} and {@link #mStatsXtIfaceAll} headers. */
+ private static final String KEY_IDX = "idx";
+ private static final String KEY_IFACE = "iface";
+ private static final String KEY_ACTIVE = "active";
+ private static final String KEY_UID = "uid_tag_int";
+ private static final String KEY_COUNTER_SET = "cnt_set";
+ private static final String KEY_TAG_HEX = "acct_tag_hex";
+ private static final String KEY_SNAP_RX_BYTES = "snap_rx_bytes";
+ private static final String KEY_SNAP_RX_PACKETS = "snap_rx_packets";
+ private static final String KEY_SNAP_TX_BYTES = "snap_tx_bytes";
+ private static final String KEY_SNAP_TX_PACKETS = "snap_tx_packets";
+ private static final String KEY_RX_BYTES = "rx_bytes";
+ private static final String KEY_RX_PACKETS = "rx_packets";
+ private static final String KEY_TX_BYTES = "tx_bytes";
+ private static final String KEY_TX_PACKETS = "tx_packets";
+
+ public NetworkStatsFactory() {
+ this(new File("/proc/"));
+ }
+
+ // @VisibleForTesting
+ public NetworkStatsFactory(File procRoot) {
+ mStatsIface = new File(procRoot, "net/dev");
+ mStatsXtUid = new File(procRoot, "net/xt_qtaguid/stats");
+ mStatsXtIface = new File(procRoot, "net/xt_qtaguid/iface_stat");
+ mStatsXtIfaceAll = new File(procRoot, "net/xt_qtaguid/iface_stat_all");
+ }
+
+ /**
+ * Parse and return interface-level summary {@link NetworkStats}. Values
+ * monotonically increase since device boot, and may include details about
+ * inactive interfaces.
+ *
+ * @throws IllegalStateException when problem parsing stats.
+ */
+ public NetworkStats readNetworkStatsSummary() throws IllegalStateException {
+ if (mStatsXtIfaceAll.exists()) {
+ return readNetworkStatsSummarySingleFile();
+ } else {
+ return readNetworkStatsSummaryMultipleFiles();
+ }
+ }
+
+ private NetworkStats readNetworkStatsSummarySingleFile() {
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+ // TODO: read directly from proc once headers are added
+ final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
+ KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
+ KEY_RX_PACKETS, KEY_TX_BYTES, KEY_TX_PACKETS);
+ final ArrayList<String> values = Lists.newArrayList();
+ final HashMap<String, String> parsed = Maps.newHashMap();
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(mStatsXtIfaceAll));
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ splitLine(line, values);
+ parseLine(keys, values, parsed);
+
+ entry.iface = parsed.get(KEY_IFACE);
+ entry.uid = UID_ALL;
+ entry.set = SET_DEFAULT;
+ entry.tag = TAG_NONE;
+
+ // always include snapshot values
+ entry.rxBytes = getParsedLong(parsed, KEY_SNAP_RX_BYTES);
+ entry.rxPackets = getParsedLong(parsed, KEY_SNAP_RX_PACKETS);
+ entry.txBytes = getParsedLong(parsed, KEY_SNAP_TX_BYTES);
+ entry.txPackets = getParsedLong(parsed, KEY_SNAP_TX_PACKETS);
+
+ // fold in active numbers, but only when active
+ final boolean active = getParsedInt(parsed, KEY_ACTIVE) != 0;
+ if (active) {
+ entry.rxBytes += getParsedLong(parsed, KEY_RX_BYTES);
+ entry.rxPackets += getParsedLong(parsed, KEY_RX_PACKETS);
+ entry.txBytes += getParsedLong(parsed, KEY_TX_BYTES);
+ entry.txPackets += getParsedLong(parsed, KEY_TX_PACKETS);
+ }
+
+ stats.addValues(entry);
+ }
+ } catch (NullPointerException e) {
+ throw new IllegalStateException("problem parsing stats: " + e);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("problem parsing stats: " + e);
+ } catch (IOException e) {
+ throw new IllegalStateException("problem parsing stats: " + e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+ return stats;
+ }
+
+ /**
+ * @deprecated remove once {@code iface_stat_all} is merged to all kernels.
+ */
+ @Deprecated
+ private NetworkStats readNetworkStatsSummaryMultipleFiles() {
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+ final HashSet<String> knownIfaces = Sets.newHashSet();
+ final HashSet<String> activeIfaces = Sets.newHashSet();
+
+ // collect any historical stats and active state
+ for (String iface : fileListWithoutNull(mStatsXtIface)) {
+ final File ifacePath = new File(mStatsXtIface, iface);
+
+ final long active = readSingleLongFromFile(new File(ifacePath, "active"));
+ if (active == 1) {
+ knownIfaces.add(iface);
+ activeIfaces.add(iface);
+ } else if (active == 0) {
+ knownIfaces.add(iface);
+ } else {
+ continue;
+ }
+
+ entry.iface = iface;
+ entry.uid = UID_ALL;
+ entry.set = SET_DEFAULT;
+ entry.tag = TAG_NONE;
+ entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes"));
+ entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets"));
+ entry.txBytes = readSingleLongFromFile(new File(ifacePath, "tx_bytes"));
+ entry.txPackets = readSingleLongFromFile(new File(ifacePath, "tx_packets"));
+
+ stats.addValues(entry);
+ }
+
+ final ArrayList<String> values = Lists.newArrayList();
+
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(mStatsIface));
+
+ // skip first two header lines
+ reader.readLine();
+ reader.readLine();
+
+ // parse remaining lines
+ String line;
+ while ((line = reader.readLine()) != null) {
+ splitLine(line, values);
+
+ try {
+ entry.iface = values.get(0);
+ entry.uid = UID_ALL;
+ entry.set = SET_DEFAULT;
+ entry.tag = TAG_NONE;
+ entry.rxBytes = Long.parseLong(values.get(1));
+ entry.rxPackets = Long.parseLong(values.get(2));
+ entry.txBytes = Long.parseLong(values.get(9));
+ entry.txPackets = Long.parseLong(values.get(10));
+
+ if (activeIfaces.contains(entry.iface)) {
+ // combine stats when iface is active
+ stats.combineValues(entry);
+ } else if (!knownIfaces.contains(entry.iface)) {
+ // add stats when iface is unknown
+ stats.addValues(entry);
+ }
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "problem parsing stats row '" + line + "': " + e);
+ }
+ }
+ } catch (NullPointerException e) {
+ throw new IllegalStateException("problem parsing stats: " + e);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("problem parsing stats: " + e);
+ } catch (IOException e) {
+ throw new IllegalStateException("problem parsing stats: " + e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+
+ return stats;
+ }
+
+ public NetworkStats readNetworkStatsDetail() {
+ return readNetworkStatsDetail(UID_ALL);
+ }
+
+ /**
+ * Parse and return {@link NetworkStats} with UID-level details. Values
+ * monotonically increase since device boot.
+ *
+ * @throws IllegalStateException when problem parsing stats.
+ */
+ public NetworkStats readNetworkStatsDetail(int limitUid) throws IllegalStateException {
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+
+ // TODO: remove knownLines check once 5087722 verified
+ final HashSet<String> knownLines = Sets.newHashSet();
+ // TODO: remove lastIdx check once 5270106 verified
+ int lastIdx;
+
+ final ArrayList<String> keys = Lists.newArrayList();
+ final ArrayList<String> values = Lists.newArrayList();
+ final HashMap<String, String> parsed = Maps.newHashMap();
+
+ BufferedReader reader = null;
+ String line = null;
+ try {
+ reader = new BufferedReader(new FileReader(mStatsXtUid));
+
+ // parse first line as header
+ line = reader.readLine();
+ splitLine(line, keys);
+ lastIdx = 1;
+
+ // parse remaining lines
+ while ((line = reader.readLine()) != null) {
+ splitLine(line, values);
+ parseLine(keys, values, parsed);
+
+ if (!knownLines.add(line)) {
+ throw new IllegalStateException("duplicate proc entry: " + line);
+ }
+
+ final int idx = getParsedInt(parsed, KEY_IDX);
+ if (idx != lastIdx + 1) {
+ throw new IllegalStateException(
+ "inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
+ }
+ lastIdx = idx;
+
+ entry.iface = parsed.get(KEY_IFACE);
+ entry.uid = getParsedInt(parsed, KEY_UID);
+ entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
+ entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
+ entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
+ entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
+ entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
+ entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+
+ if (limitUid == UID_ALL || limitUid == entry.uid) {
+ stats.addValues(entry);
+ }
+ }
+ } catch (NullPointerException e) {
+ throw new IllegalStateException("problem parsing line: " + line, e);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("problem parsing line: " + line, e);
+ } catch (IOException e) {
+ throw new IllegalStateException("problem parsing line: " + line, e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+ return stats;
+ }
+
+ private static int getParsedInt(HashMap<String, String> parsed, String key) {
+ final String value = parsed.get(key);
+ return value != null ? Integer.parseInt(value) : 0;
+ }
+
+ private static long getParsedLong(HashMap<String, String> parsed, String key) {
+ final String value = parsed.get(key);
+ return value != null ? Long.parseLong(value) : 0;
+ }
+
+ /**
+ * Split given line into {@link ArrayList}.
+ */
+ private static void splitLine(String line, ArrayList<String> outSplit) {
+ outSplit.clear();
+
+ final StringTokenizer t = new StringTokenizer(line, " \t\n\r\f:");
+ while (t.hasMoreTokens()) {
+ outSplit.add(t.nextToken());
+ }
+ }
+
+ /**
+ * Zip the two given {@link ArrayList} as key and value pairs into
+ * {@link HashMap}.
+ */
+ private static void parseLine(
+ ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
+ outParsed.clear();
+
+ final int size = Math.min(keys.size(), values.size());
+ for (int i = 0; i < size; i++) {
+ outParsed.put(keys.get(i), values.get(i));
+ }
+ }
+
+ /**
+ * Utility method to read a single plain-text {@link Long} from the given
+ * {@link File}, usually from a {@code /proc/} filesystem.
+ */
+ private static long readSingleLongFromFile(File file) {
+ try {
+ final byte[] buffer = IoUtils.readFileAsByteArray(file.toString());
+ return Long.parseLong(new String(buffer).trim());
+ } catch (NumberFormatException e) {
+ return -1;
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ /**
+ * Wrapper for {@link File#list()} that returns empty array instead of
+ * {@code null}.
+ */
+ private static String[] fileListWithoutNull(File file) {
+ final String[] list = file.list();
+ return list != null ? list : new String[0];
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index df5071f..e2a2566 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,11 +16,15 @@
package com.android.internal.os;
-import com.android.internal.util.JournaledFile;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.UID_ALL;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
-import android.net.TrafficStats;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.NetworkStats;
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.FileUtils;
@@ -43,6 +47,11 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
+import com.android.internal.R;
+import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.util.JournaledFile;
+import com.google.android.collect.Sets;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -52,6 +61,7 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -69,6 +79,8 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final boolean DEBUG_HISTORY = false;
private static final boolean USE_OLD_HISTORY = false; // for debugging.
+ // TODO: remove "tcp" from network methods, since we measure total stats.
+
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
@@ -314,6 +326,11 @@ public final class BatteryStatsImpl extends BatteryStats {
private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>();
+ private final NetworkStatsFactory mNetworkStatsFactory = new NetworkStatsFactory();
+
+ /** Network ifaces that {@link ConnectivityManager} has claimed as mobile. */
+ private HashSet<String> mMobileIfaces = Sets.newHashSet();
+
// For debugging
public BatteryStatsImpl() {
mFile = null;
@@ -1036,7 +1053,8 @@ public final class BatteryStatsImpl extends BatteryStats {
String name;
int count;
long totalTime;
- int startIndex, endIndex;
+ int startIndex;
+ int endIndex;
int numUpdatedWlNames = 0;
// Advance past the first line.
@@ -1390,30 +1408,48 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
- for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
- Uid u = mUidStats.valueAt(iu);
- u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid);
- u.mStartedTcpBytesSent = TrafficStats.getUidTxBytes(u.mUid);
+ NetworkStats.Entry entry = null;
+
+ // Track UID data usage
+ final NetworkStats uidStats = getNetworkStatsDetailGroupedByUid();
+ final int size = uidStats.size();
+ for (int i = 0; i < size; i++) {
+ entry = uidStats.getValues(i, entry);
+
+ final Uid u = mUidStats.get(entry.uid);
+ if (u == null) continue;
+
+ u.mStartedTcpBytesReceived = entry.rxBytes;
+ u.mStartedTcpBytesSent = entry.txBytes;
u.mTcpBytesReceivedAtLastUnplug = u.mCurrentTcpBytesReceived;
u.mTcpBytesSentAtLastUnplug = u.mCurrentTcpBytesSent;
}
+
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
}
- // Track total mobile data
- doDataUnplug(mMobileDataRx, TrafficStats.getMobileRxBytes());
- doDataUnplug(mMobileDataTx, TrafficStats.getMobileTxBytes());
- doDataUnplug(mTotalDataRx, TrafficStats.getTotalRxBytes());
- doDataUnplug(mTotalDataTx, TrafficStats.getTotalTxBytes());
+
+ // Track both mobile and total overall data
+ final NetworkStats ifaceStats = getNetworkStatsSummary();
+ entry = ifaceStats.getTotal(entry, mMobileIfaces);
+ doDataUnplug(mMobileDataRx, entry.rxBytes);
+ doDataUnplug(mMobileDataTx, entry.txBytes);
+ entry = ifaceStats.getTotal(entry);
+ doDataUnplug(mTotalDataRx, entry.rxBytes);
+ doDataUnplug(mTotalDataTx, entry.txBytes);
+
// Track radio awake time
mRadioDataStart = getCurrentRadioDataUptime();
mRadioDataUptime = 0;
+
// Track bt headset ping count
mBluetoothPingStart = getCurrentBluetoothPingCount();
mBluetoothPingCount = 0;
}
public void doPlugLocked(long batteryUptime, long batteryRealtime) {
+ NetworkStats.Entry entry = null;
+
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
Uid u = mUidStats.valueAt(iu);
if (u.mStartedTcpBytesReceived >= 0) {
@@ -1428,10 +1464,16 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
}
- doDataPlug(mMobileDataRx, TrafficStats.getMobileRxBytes());
- doDataPlug(mMobileDataTx, TrafficStats.getMobileTxBytes());
- doDataPlug(mTotalDataRx, TrafficStats.getTotalRxBytes());
- doDataPlug(mTotalDataTx, TrafficStats.getTotalTxBytes());
+
+ // Track both mobile and total overall data
+ final NetworkStats ifaceStats = getNetworkStatsSummary();
+ entry = ifaceStats.getTotal(entry, mMobileIfaces);
+ doDataPlug(mMobileDataRx, entry.rxBytes);
+ doDataPlug(mMobileDataTx, entry.txBytes);
+ entry = ifaceStats.getTotal(entry);
+ doDataPlug(mTotalDataRx, entry.rxBytes);
+ doDataPlug(mTotalDataTx, entry.txBytes);
+
// Track radio awake time
mRadioDataUptime = getRadioDataUptime();
mRadioDataStart = -1;
@@ -2216,6 +2258,14 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
+ if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
+ mMobileIfaces.add(iface);
+ } else {
+ mMobileIfaces.remove(iface);
+ }
+ }
+
@Override public long getScreenOnTime(long batteryRealtime, int which) {
return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
}
@@ -2400,8 +2450,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public long computeCurrentTcpBytesReceived() {
+ final long uidRxBytes = getNetworkStatsDetailGroupedByUid().getTotal(
+ null, mUid).rxBytes;
return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
- ? (TrafficStats.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
+ ? (uidRxBytes - mStartedTcpBytesReceived) : 0);
}
@Override
@@ -2619,8 +2671,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public long computeCurrentTcpBytesSent() {
+ final long uidTxBytes = getNetworkStatsDetailGroupedByUid().getTotal(
+ null, mUid).txBytes;
return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
- ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
+ ? (uidTxBytes - mStartedTcpBytesSent) : 0);
}
/**
@@ -4518,22 +4572,26 @@ public final class BatteryStatsImpl extends BatteryStats {
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesSent(int which) {
- return getTcpBytes(TrafficStats.getMobileTxBytes(), mMobileDataTx, which);
+ final long mobileTxBytes = getNetworkStatsSummary().getTotal(null, mMobileIfaces).txBytes;
+ return getTcpBytes(mobileTxBytes, mMobileDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getMobileTcpBytesReceived(int which) {
- return getTcpBytes(TrafficStats.getMobileRxBytes(), mMobileDataRx, which);
+ final long mobileRxBytes = getNetworkStatsSummary().getTotal(null, mMobileIfaces).rxBytes;
+ return getTcpBytes(mobileRxBytes, mMobileDataRx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesSent(int which) {
- return getTcpBytes(TrafficStats.getTotalTxBytes(), mTotalDataTx, which);
+ final long totalTxBytes = getNetworkStatsSummary().getTotal(null).txBytes;
+ return getTcpBytes(totalTxBytes, mTotalDataTx, which);
}
/** Only STATS_UNPLUGGED works properly */
public long getTotalTcpBytesReceived(int which) {
- return getTcpBytes(TrafficStats.getTotalRxBytes(), mTotalDataRx, which);
+ final long totalRxBytes = getNetworkStatsSummary().getTotal(null).rxBytes;
+ return getTcpBytes(totalRxBytes, mTotalDataRx, which);
}
@Override
@@ -5637,7 +5695,47 @@ public final class BatteryStatsImpl extends BatteryStats {
mGlobalWifiRunningTimer.logState(pr, " ");
pr.println("*** Bluetooth timer:");
mBluetoothOnTimer.logState(pr, " ");
+ pr.println("*** Mobile ifaces:");
+ pr.println(mMobileIfaces.toString());
}
super.dumpLocked(pw);
}
+
+ private NetworkStats mNetworkSummaryCache;
+ private NetworkStats mNetworkDetailCache;
+
+ private NetworkStats getNetworkStatsSummary() {
+ // NOTE: calls from BatteryStatsService already hold this lock
+ synchronized (this) {
+ if (mNetworkSummaryCache == null
+ || mNetworkSummaryCache.getElapsedRealtimeAge() > SECOND_IN_MILLIS) {
+ try {
+ mNetworkSummaryCache = mNetworkStatsFactory.readNetworkStatsSummary();
+ } catch (IllegalStateException e) {
+ // log problem and return empty object
+ Log.wtf(TAG, "problem reading network stats", e);
+ mNetworkSummaryCache = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ }
+ }
+ return mNetworkSummaryCache;
+ }
+ }
+
+ private NetworkStats getNetworkStatsDetailGroupedByUid() {
+ // NOTE: calls from BatteryStatsService already hold this lock
+ synchronized (this) {
+ if (mNetworkDetailCache == null
+ || mNetworkDetailCache.getElapsedRealtimeAge() > SECOND_IN_MILLIS) {
+ try {
+ mNetworkDetailCache = mNetworkStatsFactory
+ .readNetworkStatsDetail().groupedByUid();
+ } catch (IllegalStateException e) {
+ // log problem and return empty object
+ Log.wtf(TAG, "problem reading network stats", e);
+ mNetworkDetailCache = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ }
+ }
+ return mNetworkDetailCache;
+ }
+ }
}
diff --git a/core/java/com/android/internal/policy/IFaceLockCallback.aidl b/core/java/com/android/internal/policy/IFaceLockCallback.aidl
index 4f76c71..add3f1c 100644
--- a/core/java/com/android/internal/policy/IFaceLockCallback.aidl
+++ b/core/java/com/android/internal/policy/IFaceLockCallback.aidl
@@ -21,4 +21,5 @@ import android.os.IBinder;
oneway interface IFaceLockCallback {
void unlock();
void cancel();
+ void pokeWakelock();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 3916e86..aca1fa2 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -34,7 +34,6 @@ oneway interface IStatusBar
void topAppWindowChanged(boolean menuVisible);
void setImeWindowStatus(in IBinder token, int vis, int backDisposition);
void setHardKeyboardStatus(boolean available, boolean enabled);
- void userActivity();
void toggleRecentApps();
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 07430e7..ecebfc0 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -46,6 +46,5 @@ interface IStatusBarService
void onNotificationClear(String pkg, String tag, int id);
void setSystemUiVisibility(int vis);
void setHardKeyboardEnabled(boolean enabled);
- void userActivity();
void toggleRecentApps();
}
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index b9948fe..b227700 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -27,6 +27,7 @@ import android.view.IWindowSession;
public class BaseIWindow extends IWindow.Stub {
private IWindowSession mSession;
+ public int mSeq;
public void setSession(IWindowSession session) {
mSession = session;
@@ -69,7 +70,9 @@ public class BaseIWindow extends IWindow.Stub {
public void dispatchDragEvent(DragEvent event) {
}
- public void dispatchSystemUiVisibilityChanged(int visibility) {
+ public void dispatchSystemUiVisibilityChanged(int seq, int globalUi,
+ int localValue, int localChanges) {
+ mSeq = seq;
}
public void dispatchWallpaperCommand(String action, int x, int y,
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
index ecda47e..edf4443 100644
--- a/core/java/com/android/internal/view/StandaloneActionMode.java
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -36,17 +36,19 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call
private ActionMode.Callback mCallback;
private WeakReference<View> mCustomView;
private boolean mFinished;
+ private boolean mFocusable;
private MenuBuilder mMenu;
public StandaloneActionMode(Context context, ActionBarContextView view,
- ActionMode.Callback callback) {
+ ActionMode.Callback callback, boolean isFocusable) {
mContext = context;
mContextView = view;
mCallback = callback;
mMenu = new MenuBuilder(context).setDefaultShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
mMenu.setCallback(this);
+ mFocusable = isFocusable;
}
@Override
@@ -139,4 +141,8 @@ public class StandaloneActionMode extends ActionMode implements MenuBuilder.Call
invalidate();
mContextView.showOverflowMenu();
}
+
+ public boolean isUiFocusable() {
+ return mFocusable;
+ }
}
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/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index 723ece4..47058ad 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -63,11 +63,6 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men
setChildrenDrawingCacheEnabled(false);
}
- @Override
- protected boolean recycleOnMeasure() {
- return false;
- }
-
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
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/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index a1e16d4..df579c6 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -34,6 +34,7 @@ import android.widget.TextView;
* The item view for each item in the ListView-based MenuViews.
*/
public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
+ private static final String TAG = "ListMenuItemView";
private MenuItemImpl mItemData;
private ImageView mIconView;
@@ -121,27 +122,25 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
public void setCheckable(boolean checkable) {
-
if (!checkable && mRadioButton == null && mCheckBox == null) {
return;
}
- if (mRadioButton == null) {
- insertRadioButton();
- }
- if (mCheckBox == null) {
- insertCheckBox();
- }
-
// Depending on whether its exclusive check or not, the checkbox or
// radio button will be the one in use (and the other will be otherCompoundButton)
final CompoundButton compoundButton;
final CompoundButton otherCompoundButton;
if (mItemData.isExclusiveCheckable()) {
+ if (mRadioButton == null) {
+ insertRadioButton();
+ }
compoundButton = mRadioButton;
otherCompoundButton = mCheckBox;
} else {
+ if (mCheckBox == null) {
+ insertCheckBox();
+ }
compoundButton = mCheckBox;
otherCompoundButton = mRadioButton;
}
@@ -155,12 +154,12 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
// Make sure the other compound button isn't visible
- if (otherCompoundButton.getVisibility() != GONE) {
+ if (otherCompoundButton != null && otherCompoundButton.getVisibility() != GONE) {
otherCompoundButton.setVisibility(GONE);
}
} else {
- mCheckBox.setVisibility(GONE);
- mRadioButton.setVisibility(GONE);
+ if (mCheckBox != null) mCheckBox.setVisibility(GONE);
+ if (mRadioButton != null) mRadioButton.setVisibility(GONE);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index f95de62..0c572705 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -118,6 +118,14 @@ public class ActionBarContainer extends FrameLayout {
return true;
}
+ @Override
+ public boolean onHoverEvent(MotionEvent ev) {
+ super.onHoverEvent(ev);
+
+ // An action bar always eats hover events.
+ return true;
+ }
+
public void setTabContainer(ScrollingTabContainerView tabView) {
if (mTabContainer != null) {
removeView(mTabContainer);
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/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 3795a7c..d5450e4 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -24,11 +24,15 @@ import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
import android.os.FileObserver;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.os.storage.IMountService;
import android.provider.Settings;
import android.security.KeyStore;
@@ -55,6 +59,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
public class LockPatternUtils {
+ private static final String OPTION_ENABLE_FACELOCK = "enable_facelock";
+
private static final String TAG = "LockPatternUtils";
private static final String SYSTEM_DIRECTORY = "/system/";
@@ -110,8 +116,11 @@ public class LockPatternUtils {
public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
private final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
+ private final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK
= "lockscreen.biometric_weak_fallback";
+ public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY
+ = "lockscreen.biometricweakeverchosen";
private final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
@@ -126,8 +135,6 @@ public class LockPatternUtils {
private static FileObserver sPasswordObserver;
- private static boolean mLastAttemptWasBiometric = false;
-
private static class PasswordFileObserver extends FileObserver {
public PasswordFileObserver(String path, int mask) {
super(path, mask);
@@ -336,17 +343,36 @@ public class LockPatternUtils {
}
/**
+ * Return true if the user has ever chosen biometric weak. This is true even if biometric
+ * weak is not current set.
+ *
+ * @return True if the user has ever chosen biometric weak.
+ */
+ public boolean isBiometricWeakEverChosen() {
+ return getBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY);
+ }
+
+ /**
* Used by device policy manager to validate the current password
* information it has.
*/
public int getActivePasswordQuality() {
int activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- switch (getKeyguardStoredPasswordQuality()) {
+ // Note we don't want to use getKeyguardStoredPasswordQuality() because we want this to
+ // return biometric_weak if that is being used instead of the backup
+ int quality =
+ (int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ switch (quality) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
if (isLockPatternEnabled()) {
activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
}
break;
+ case DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK:
+ if (isBiometricWeakInstalled()) {
+ activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+ }
+ break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
if (isLockPasswordEnabled()) {
activePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
@@ -368,6 +394,7 @@ public class LockPatternUtils {
}
break;
}
+
return activePasswordQuality;
}
@@ -394,13 +421,6 @@ public class LockPatternUtils {
}
/**
- * Sets whether the last lockscreen setup attempt was biometric
- */
- public static void setLastAttemptWasBiometric(boolean val) {
- mLastAttemptWasBiometric = val;
- }
-
- /**
* Determine if LockScreen can be disabled. This is used, for example, to tell if we should
* show LockScreen or go straight to the home screen.
*
@@ -430,22 +450,20 @@ public class LockPatternUtils {
}
/**
- * Calls back SetupFaceLock to delete the temporary gallery file if this is the backup lock.
+ * Calls back SetupFaceLock to delete the temporary gallery file
*/
public void deleteTempGallery() {
- //if(mLastAttemptWasBiometric) {
- Intent intent = new Intent().setClassName("com.android.facelock",
- "com.android.facelock.SetupFaceLock");
- intent.putExtra("deleteTempGallery", true);
- mContext.startActivity(intent);
- //}
+ Intent intent = new Intent().setClassName("com.android.facelock",
+ "com.android.facelock.SetupFaceLock");
+ intent.putExtra("deleteTempGallery", true);
+ mContext.startActivity(intent);
}
/**
* Calls back SetupFaceLock to delete the gallery file when the lock type is changed
*/
void deleteGallery() {
- if(isBiometricEnabled()) {
+ if(usingBiometricWeak()) {
Intent intent = new Intent().setClassName("com.android.facelock",
"com.android.facelock.SetupFaceLock");
intent.putExtra("deleteGallery", true);
@@ -483,6 +501,7 @@ public class LockPatternUtils {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
setLong(PASSWORD_TYPE_ALTERNATE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+ setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true);
moveTempGallery();
}
dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern
@@ -600,6 +619,7 @@ public class LockPatternUtils {
} else {
setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK);
setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality));
+ setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true);
moveTempGallery();
}
if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
@@ -688,6 +708,9 @@ public class LockPatternUtils {
return quality;
}
+ /**
+ * @return true if the lockscreen method is set to biometric weak
+ */
public boolean usingBiometricWeak() {
int quality =
(int) getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
@@ -821,7 +844,7 @@ public class LockPatternUtils {
|| backupMode == DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
return savedPasswordExists() && (passwordEnabled ||
- (isBiometricEnabled() && backupEnabled));
+ (usingBiometricWeak() && backupEnabled));
}
/**
@@ -835,16 +858,36 @@ public class LockPatternUtils {
return getBoolean(Settings.Secure.LOCK_PATTERN_ENABLED)
&& (getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
== DevicePolicyManager.PASSWORD_QUALITY_SOMETHING ||
- (isBiometricEnabled() && backupEnabled));
+ (usingBiometricWeak() && backupEnabled));
}
/**
- * @return Whether biometric weak lock is enabled.
+ * @return Whether biometric weak lock is installed and that the front facing camera exists
*/
- public boolean isBiometricEnabled() {
- // TODO: check if it's installed
- return getLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
- == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+ public boolean isBiometricWeakInstalled() {
+ // Check that the system flag was set
+ if (!OPTION_ENABLE_FACELOCK.equals(getString(LOCKSCREEN_OPTIONS))) {
+ return false;
+ }
+
+ // Check that it's installed
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ pm.getPackageInfo("com.android.facelock", PackageManager.GET_ACTIVITIES);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ // Check that the camera is enabled
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
+ return false;
+ }
+ if (getDevicePolicyManager().getCameraDisabled(null)) {
+ return false;
+ }
+
+
+ return true;
}
/**
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 366b983..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;
@@ -48,11 +49,11 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
private PasswordEntryKeyboard mSymbolsKeyboard;
private PasswordEntryKeyboard mSymbolsKeyboardShifted;
private PasswordEntryKeyboard mNumericKeyboard;
- private Context mContext;
- private View mTargetView;
- private KeyboardView mKeyboardView;
+ private final Context mContext;
+ private final View mTargetView;
+ private final KeyboardView mKeyboardView;
private long[] mVibratePattern;
- private 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() {
@@ -228,8 +232,9 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
}
}
- private void handleBackspace() {
+ 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;
}