summaryrefslogtreecommitdiff
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/app/Activity.java49
-rw-r--r--core/java/android/app/ActivityManager.java82
-rw-r--r--core/java/android/app/ActivityManagerNative.java176
-rw-r--r--core/java/android/app/ActivityThread.java1618
-rw-r--r--core/java/android/app/AppGlobals.java49
-rw-r--r--core/java/android/app/ApplicationErrorReport.java77
-rw-r--r--core/java/android/app/ApplicationLoaders.java10
-rw-r--r--core/java/android/app/ApplicationThreadNative.java33
-rw-r--r--core/java/android/app/ContextImpl.java466
-rw-r--r--core/java/android/app/Dialog.java2
-rw-r--r--core/java/android/app/IActivityManager.java54
-rw-r--r--core/java/android/app/IApplicationThread.java5
-rw-r--r--core/java/android/app/ListActivity.java4
-rw-r--r--core/java/android/app/LoadedApk.java1107
-rw-r--r--core/java/android/app/NativeActivity.java369
-rw-r--r--core/java/android/app/Notification.java73
-rw-r--r--core/java/android/app/NotificationManager.java1
-rw-r--r--core/java/android/app/QueuedWork.java91
-rw-r--r--core/java/android/app/Service.java10
-rw-r--r--core/java/android/app/StatusBarManager.java34
-rw-r--r--core/java/android/app/backup/SharedPreferencesBackupHelper.java8
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java4
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java25
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java73
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java58
-rw-r--r--core/java/android/bluetooth/BluetoothDeviceProfileState.java665
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java95
-rw-r--r--core/java/android/bluetooth/BluetoothProfileState.java144
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl8
-rw-r--r--core/java/android/bluetooth/IBluetoothA2dp.aidl3
-rw-r--r--core/java/android/bluetooth/IBluetoothHeadset.aidl10
-rw-r--r--core/java/android/content/ContentProvider.java10
-rw-r--r--core/java/android/content/ContentResolver.java5
-rw-r--r--core/java/android/content/ContentService.java3
-rw-r--r--core/java/android/content/Context.java24
-rw-r--r--core/java/android/content/Intent.java24
-rw-r--r--core/java/android/content/IntentSender.java20
-rw-r--r--core/java/android/content/SharedPreferences.java52
-rw-r--r--core/java/android/content/SyncManager.java7
-rw-r--r--core/java/android/content/SyncStorageEngine.java4
-rw-r--r--core/java/android/content/pm/ActivityInfo.java6
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java70
-rw-r--r--core/java/android/content/pm/ComponentInfo.java8
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl4
-rw-r--r--core/java/android/content/pm/InstrumentationInfo.java12
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java47
-rw-r--r--core/java/android/content/pm/PackageManager.java182
-rw-r--r--core/java/android/content/pm/PackageParser.java117
-rw-r--r--core/java/android/content/pm/Signature.java14
-rw-r--r--core/java/android/content/res/AssetManager.java1
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java39
-rw-r--r--core/java/android/content/res/Configuration.java4
-rwxr-xr-x[-rw-r--r--]core/java/android/content/res/ObbInfo.aidl (renamed from core/java/android/os/storage/IMountShutdownObserver.aidl)20
-rw-r--r--core/java/android/content/res/ObbInfo.java89
-rw-r--r--core/java/android/content/res/ObbScanner.java40
-rw-r--r--core/java/android/content/res/Resources.java2
-rw-r--r--core/java/android/content/res/TypedArray.java4
-rw-r--r--core/java/android/database/Cursor.java3
-rw-r--r--core/java/android/database/DatabaseUtils.java6
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java35
-rw-r--r--core/java/android/database/sqlite/SQLiteOpenHelper.java10
-rw-r--r--core/java/android/database/sqlite/SQLiteProgram.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteQuery.java3
-rw-r--r--core/java/android/database/sqlite/SQLiteStatement.java9
-rw-r--r--core/java/android/hardware/Camera.java373
-rw-r--r--core/java/android/hardware/Sensor.java119
-rw-r--r--core/java/android/hardware/SensorEvent.java318
-rw-r--r--core/java/android/hardware/SensorManager.java1088
-rw-r--r--core/java/android/hardware/Usb.java99
-rw-r--r--core/java/android/inputmethodservice/ExtractEditText.java18
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java15
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java52
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java16
-rw-r--r--core/java/android/net/ConnectivityManager.java22
-rw-r--r--core/java/android/net/DownloadManager.java901
-rw-r--r--core/java/android/net/IConnectivityManager.aidl2
-rw-r--r--core/java/android/net/MobileDataStateTracker.java34
-rw-r--r--core/java/android/net/NetworkStateTracker.java17
-rw-r--r--core/java/android/net/NetworkUtils.java15
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java4
-rw-r--r--core/java/android/net/http/HttpsConnection.java4
-rw-r--r--core/java/android/os/BatteryStats.java484
-rw-r--r--core/java/android/os/Binder.java39
-rw-r--r--core/java/android/os/Build.java19
-rw-r--r--core/java/android/os/DropBoxManager.java2
-rw-r--r--core/java/android/os/Environment.java48
-rw-r--r--core/java/android/os/Handler.java4
-rw-r--r--core/java/android/os/IPowerManager.aidl5
-rw-r--r--core/java/android/os/Looper.java8
-rw-r--r--core/java/android/os/Message.java7
-rw-r--r--core/java/android/os/MessageQueue.java69
-rw-r--r--core/java/android/os/Parcel.java88
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java4
-rw-r--r--core/java/android/os/PowerManager.java29
-rw-r--r--core/java/android/os/Process.java9
-rw-r--r--core/java/android/os/RecoverySystem.java17
-rw-r--r--core/java/android/os/StrictMode.java657
-rw-r--r--core/java/android/os/WorkSource.aidl18
-rw-r--r--core/java/android/os/WorkSource.java311
-rw-r--r--core/java/android/os/storage/IMountService.aidl155
-rw-r--r--core/java/android/os/storage/IMountService.java1046
-rw-r--r--core/java/android/os/storage/IMountServiceListener.aidl43
-rw-r--r--core/java/android/os/storage/IMountServiceListener.java176
-rw-r--r--core/java/android/os/storage/IMountShutdownObserver.java124
-rw-r--r--core/java/android/os/storage/IObbActionListener.java130
-rw-r--r--core/java/android/os/storage/StorageManager.java134
-rw-r--r--core/java/android/pim/vcard/JapaneseUtils.java5
-rw-r--r--core/java/android/pim/vcard/VCardBuilder.java498
-rw-r--r--core/java/android/pim/vcard/VCardComposer.java246
-rw-r--r--core/java/android/pim/vcard/VCardConfig.java419
-rw-r--r--core/java/android/pim/vcard/VCardConstants.java49
-rw-r--r--core/java/android/pim/vcard/VCardEntry.java239
-rw-r--r--core/java/android/pim/vcard/VCardEntryCommitter.java4
-rw-r--r--core/java/android/pim/vcard/VCardEntryConstructor.java261
-rw-r--r--core/java/android/pim/vcard/VCardEntryHandler.java9
-rw-r--r--core/java/android/pim/vcard/VCardInterpreter.java2
-rw-r--r--core/java/android/pim/vcard/VCardInterpreterCollection.java4
-rw-r--r--core/java/android/pim/vcard/VCardParser.java88
-rw-r--r--core/java/android/pim/vcard/VCardParserImpl_V21.java1046
-rw-r--r--core/java/android/pim/vcard/VCardParserImpl_V30.java388
-rw-r--r--core/java/android/pim/vcard/VCardParserImpl_V40.java92
-rw-r--r--core/java/android/pim/vcard/VCardParser_V21.java947
-rw-r--r--core/java/android/pim/vcard/VCardParser_V30.java359
-rw-r--r--core/java/android/pim/vcard/VCardParser_V40.java78
-rw-r--r--core/java/android/pim/vcard/VCardSourceDetector.java128
-rw-r--r--core/java/android/pim/vcard/VCardUtils.java283
-rw-r--r--core/java/android/pim/vcard/exception/VCardInvalidLineException.java1
-rw-r--r--core/java/android/pim/vcard/exception/VCardVersionException.java1
-rw-r--r--core/java/android/preference/DialogPreference.java3
-rw-r--r--core/java/android/preference/Preference.java2
-rw-r--r--core/java/android/preference/PreferenceManager.java4
-rw-r--r--core/java/android/provider/ContactsContract.java96
-rw-r--r--core/java/android/provider/Downloads.java89
-rw-r--r--core/java/android/provider/MediaStore.java54
-rw-r--r--core/java/android/provider/Settings.java104
-rw-r--r--core/java/android/server/BluetoothA2dpService.java109
-rw-r--r--core/java/android/server/BluetoothEventLoop.java135
-rw-r--r--core/java/android/server/BluetoothService.java332
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java150
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java3
-rw-r--r--core/java/android/text/Selection.java4
-rw-r--r--core/java/android/text/TextUtils.java31
-rw-r--r--core/java/android/text/format/DateUtils.java61
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java379
-rw-r--r--core/java/android/text/method/TextKeyListener.java6
-rw-r--r--core/java/android/text/method/Touch.java7
-rw-r--r--core/java/android/text/util/Rfc822Tokenizer.java12
-rw-r--r--core/java/android/util/DisplayMetrics.java13
-rw-r--r--core/java/android/util/Log.java35
-rw-r--r--core/java/android/util/TimeUtils.java150
-rw-r--r--core/java/android/view/AbsSavedState.java4
-rw-r--r--core/java/android/view/Display.java3
-rw-r--r--core/java/android/view/IWindow.aidl3
-rw-r--r--core/java/android/view/IWindowManager.aidl13
-rw-r--r--core/java/android/view/IWindowSession.aidl8
-rw-r--r--core/java/android/view/InputChannel.aidl (renamed from core/java/android/hardware/ISensorService.aidl)17
-rw-r--r--core/java/android/view/InputChannel.java157
-rw-r--r--core/java/android/view/InputDevice.aidl20
-rwxr-xr-xcore/java/android/view/InputDevice.java551
-rw-r--r--core/java/android/view/InputEvent.aidl20
-rwxr-xr-xcore/java/android/view/InputEvent.java114
-rw-r--r--core/java/android/view/InputHandler.java43
-rw-r--r--core/java/android/view/InputQueue.java167
-rw-r--r--core/java/android/view/KeyCharacterMap.java8
-rwxr-xr-x[-rw-r--r--]core/java/android/view/KeyEvent.java190
-rw-r--r--core/java/android/view/LayoutInflater.java5
-rw-r--r--core/java/android/view/MotionEvent.java1238
-rw-r--r--core/java/android/view/RawInputEvent.java201
-rw-r--r--core/java/android/view/ScaleGestureDetector.java4
-rw-r--r--core/java/android/view/Surface.java10
-rw-r--r--core/java/android/view/SurfaceHolder.java18
-rw-r--r--core/java/android/view/SurfaceView.java95
-rw-r--r--core/java/android/view/VelocityTracker.java339
-rw-r--r--core/java/android/view/View.java328
-rw-r--r--core/java/android/view/ViewConfiguration.java31
-rw-r--r--core/java/android/view/ViewDebug.java233
-rw-r--r--core/java/android/view/ViewGroup.java29
-rw-r--r--core/java/android/view/ViewRoot.java955
-rw-r--r--core/java/android/view/Window.java15
-rw-r--r--core/java/android/view/WindowManager.java5
-rw-r--r--core/java/android/view/WindowManagerPolicy.java132
-rwxr-xr-xcore/java/android/view/WindowOrientationListener.java263
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java1
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java125
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java27
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java8
-rw-r--r--core/java/android/webkit/CacheManager.java4
-rw-r--r--core/java/android/webkit/JWebCoreJavaBridge.java22
-rw-r--r--core/java/android/webkit/MimeTypeMap.java2
-rw-r--r--core/java/android/webkit/PluginManager.java28
-rw-r--r--core/java/android/webkit/WebChromeClient.java18
-rw-r--r--core/java/android/webkit/WebSettings.java32
-rw-r--r--core/java/android/webkit/WebView.java785
-rw-r--r--core/java/android/webkit/WebViewCore.java13
-rw-r--r--core/java/android/webkit/WebViewDatabase.java3
-rw-r--r--core/java/android/widget/AbsListView.java587
-rw-r--r--core/java/android/widget/AdapterView.java23
-rw-r--r--core/java/android/widget/CursorTreeAdapter.java8
-rw-r--r--core/java/android/widget/DatePicker.java1
-rw-r--r--core/java/android/widget/EdgeGlow.java282
-rw-r--r--core/java/android/widget/FrameLayout.java21
-rw-r--r--core/java/android/widget/Gallery.java2
-rw-r--r--core/java/android/widget/GridView.java7
-rw-r--r--core/java/android/widget/HorizontalScrollView.java177
-rw-r--r--core/java/android/widget/ImageView.java18
-rw-r--r--core/java/android/widget/LinearLayout.java22
-rw-r--r--core/java/android/widget/ListView.java144
-rw-r--r--core/java/android/widget/MediaController.java2
-rw-r--r--core/java/android/widget/OverScroller.java849
-rw-r--r--core/java/android/widget/ProgressBar.java36
-rw-r--r--core/java/android/widget/QuickContactBadge.java3
-rw-r--r--core/java/android/widget/RelativeLayout.java4
-rw-r--r--core/java/android/widget/RemoteViews.java9
-rw-r--r--core/java/android/widget/ScrollView.java160
-rw-r--r--core/java/android/widget/Scroller.java26
-rw-r--r--core/java/android/widget/SimpleCursorTreeAdapter.java55
-rw-r--r--core/java/android/widget/TableRow.java4
-rw-r--r--core/java/android/widget/TextView.java1440
-rw-r--r--core/java/com/android/internal/app/ExternalMediaFormatActivity.java5
-rw-r--r--core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java156
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl24
-rwxr-xr-xcore/java/com/android/internal/app/IMediaContainerService.aidl4
-rwxr-xr-xcore/java/com/android/internal/app/NetInitiatedActivity.java9
-rw-r--r--core/java/com/android/internal/app/PlatLogoActivity.java48
-rw-r--r--core/java/com/android/internal/app/RingtonePickerActivity.java2
-rw-r--r--core/java/com/android/internal/app/ShutdownThread.java6
-rw-r--r--core/java/com/android/internal/content/NativeLibraryHelper.java326
-rw-r--r--core/java/com/android/internal/content/PackageHelper.java16
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java1912
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java18
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java4
-rw-r--r--core/java/com/android/internal/os/SamplingProfilerIntegration.java82
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java27
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl34
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl43
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarIcon.aidl (renamed from core/java/android/app/IStatusBar.aidl)21
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarIcon.java105
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarIconList.aidl20
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarIconList.java161
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarNotification.aidl20
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarNotification.java126
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl20
-rw-r--r--core/java/com/android/internal/util/HierarchicalStateMachine.java244
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java53
-rw-r--r--core/java/com/android/internal/view/BaseInputHandler.java35
-rw-r--r--core/java/com/android/internal/view/BaseSurfaceHolder.java34
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java39
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl4
-rw-r--r--core/java/com/android/internal/view/IInputContextCallback.aidl1
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl2
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java45
-rw-r--r--core/java/com/android/internal/view/RootViewSurfaceTaker.java13
-rw-r--r--core/java/com/android/internal/widget/DigitalClock.java4
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java65
-rw-r--r--core/java/com/android/internal/widget/PointerLocationView.java445
-rw-r--r--core/java/com/google/android/mms/pdu/PduParser.java53
258 files changed, 26128 insertions, 8205 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 1cd7aa77e271..1d9e0f166c5b 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1483,7 +1483,13 @@ public class AccountManagerService
}
private static String getDatabaseName() {
- return DATABASE_NAME;
+ if(Environment.isEncryptedFilesystemEnabled()) {
+ // Hard-coded path in case of encrypted file system
+ return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME;
+ } else {
+ // Regular path in case of non-encrypted file system
+ return DATABASE_NAME;
+ }
}
private class DatabaseHelper extends SQLiteOpenHelper {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a9623917f757..6e6e86f78950 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -39,7 +39,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.RemoteException;
import android.text.Selection;
import android.text.SpannableStringBuilder;
@@ -68,6 +67,8 @@ import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1161,6 +1162,7 @@ public class Activity extends ContextThemeWrapper
*/
protected void onPause() {
mCalled = true;
+ QueuedWork.waitToFinish();
}
/**
@@ -1205,19 +1207,37 @@ public class Activity extends ContextThemeWrapper
* @see #onPause
*/
public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
- final View view = mDecor;
- if (view == null) {
+ if (mDecor == null) {
return false;
}
- final int vw = view.getWidth();
- final int vh = view.getHeight();
- final int dw = outBitmap.getWidth();
- final int dh = outBitmap.getHeight();
+ int paddingLeft = 0;
+ int paddingRight = 0;
+ int paddingTop = 0;
+ int paddingBottom = 0;
+
+ // Find System window and use padding so we ignore space reserved for decorations
+ // like the status bar and such.
+ final FrameLayout top = (FrameLayout) mDecor;
+ for (int i = 0; i < top.getChildCount(); i++) {
+ View child = top.getChildAt(i);
+ if (child.isFitsSystemWindowsFlagSet()) {
+ paddingLeft = child.getPaddingLeft();
+ paddingRight = child.getPaddingRight();
+ paddingTop = child.getPaddingTop();
+ paddingBottom = child.getPaddingBottom();
+ break;
+ }
+ }
+
+ final int visibleWidth = mDecor.getWidth() - paddingLeft - paddingRight;
+ final int visibleHeight = mDecor.getHeight() - paddingTop - paddingBottom;
canvas.save();
- canvas.scale(((float)dw)/vw, ((float)dh)/vh);
- view.draw(canvas);
+ canvas.scale( (float) outBitmap.getWidth() / visibleWidth,
+ (float) outBitmap.getHeight() / visibleHeight);
+ canvas.translate(-paddingLeft, -paddingTop);
+ mDecor.draw(canvas);
canvas.restore();
return true;
@@ -1600,6 +1620,9 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * @deprecated This functionality will be removed in the future; please do
+ * not use.
+ *
* Control whether this activity is required to be persistent. By default
* activities are not persistent; setting this to true will prevent the
* system from stopping this activity or its process when running low on
@@ -1614,6 +1637,7 @@ public class Activity extends ContextThemeWrapper
* persistent, true if so, false for the normal
* behavior.
*/
+ @Deprecated
public void setPersistent(boolean isPersistent) {
if (mParent == null) {
try {
@@ -3839,7 +3863,14 @@ public class Activity extends ContextThemeWrapper
}
final void performPause() {
+ mCalled = false;
onPause();
+ if (!mCalled && getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.GINGERBREAD) {
+ throw new SuperNotCalledException(
+ "Activity " + mComponent.toShortString() +
+ " did not call through to super.onPause()");
+ }
}
final void performUserLeaving() {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c9096cfe6e99..4736404d9b0f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -285,24 +285,54 @@ public class ActivityManager {
* @param maxNum The maximum number of entries to return in the list. The
* actual number returned may be smaller, depending on how many tasks the
* user has started.
- *
+ *
+ * @param flags Optional flags
+ * @param receiver Optional receiver for delayed thumbnails
+ *
* @return Returns a list of RunningTaskInfo records describing each of
* the running tasks.
*
+ * Some thumbnails may not be available at the time of this call. The optional
+ * receiver may be used to receive those thumbnails.
+ *
* @throws SecurityException Throws SecurityException if the caller does
* not hold the {@link android.Manifest.permission#GET_TASKS} permission.
+ *
+ * @hide
*/
- public List<RunningTaskInfo> getRunningTasks(int maxNum)
+ public List<RunningTaskInfo> getRunningTasks(int maxNum, int flags, IThumbnailReceiver receiver)
throws SecurityException {
try {
- return (List<RunningTaskInfo>)ActivityManagerNative.getDefault()
- .getTasks(maxNum, 0, null);
+ return ActivityManagerNative.getDefault().getTasks(maxNum, flags, receiver);
} catch (RemoteException e) {
// System dead, we will be dead too soon!
return null;
}
}
-
+
+ /**
+ * Return a list of the tasks that are currently running, with
+ * the most recent being first and older ones after in order. Note that
+ * "running" does not mean any of the task's code is currently loaded or
+ * activity -- the task may have been frozen by the system, so that it
+ * can be restarted in its previous state when next brought to the
+ * foreground.
+ *
+ * @param maxNum The maximum number of entries to return in the list. The
+ * actual number returned may be smaller, depending on how many tasks the
+ * user has started.
+ *
+ * @return Returns a list of RunningTaskInfo records describing each of
+ * the running tasks.
+ *
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not hold the {@link android.Manifest.permission#GET_TASKS} permission.
+ */
+ public List<RunningTaskInfo> getRunningTasks(int maxNum)
+ throws SecurityException {
+ return getRunningTasks(maxNum, 0, null);
+ }
+
/**
* Information you can retrieve about a particular Service that is
* currently running in the system.
@@ -335,7 +365,8 @@ public class ActivityManager {
/**
* The time when the service was first made active, either by someone
- * starting or binding to it.
+ * starting or binding to it. This
+ * is in units of {@link android.os.SystemClock#elapsedRealtime()}.
*/
public long activeSince;
@@ -357,7 +388,8 @@ public class ActivityManager {
/**
* The time when there was last activity in the service (either
- * explicit requests to start it or clients binding to it).
+ * explicit requests to start it or clients binding to it). This
+ * is in units of {@link android.os.SystemClock#uptimeMillis()}.
*/
public long lastActivityTime;
@@ -717,9 +749,27 @@ public class ActivityManager {
*/
public int uid;
+ /**
+ * All packages that have been loaded into the process.
+ */
public String pkgList[];
/**
+ * Constant for {@link #flags}: this is an app that is unable to
+ * correctly save its state when going to the background,
+ * so it can not be killed while in the background.
+ * @hide
+ */
+ public static final int FLAG_CANT_SAVE_STATE = 1<<0;
+
+ /**
+ * Flags of information. May be any of
+ * {@link #FLAG_CANT_SAVE_STATE}.
+ * @hide
+ */
+ public int flags;
+
+ /**
* Constant for {@link #importance}: this process is running the
* foreground UI.
*/
@@ -727,11 +777,25 @@ public class ActivityManager {
/**
* Constant for {@link #importance}: this process is running something
- * that is considered to be actively visible to the user.
+ * that is actively visible to the user, though not in the immediate
+ * foreground.
*/
public static final int IMPORTANCE_VISIBLE = 200;
/**
+ * Constant for {@link #importance}: this process is running something
+ * that is considered to be actively perceptible to the user. An
+ * example would be an application performing background music playback.
+ */
+ public static final int IMPORTANCE_PERCEPTIBLE = 130;
+
+ /**
+ * Constant for {@link #importance}: this process is running a
+ * heavy-weight application and thus should not be killed.
+ */
+ public static final int IMPORTANCE_HEAVY_WEIGHT = 170;
+
+ /**
* Constant for {@link #importance}: this process is contains services
* that should remain running.
*/
@@ -832,6 +896,7 @@ public class ActivityManager {
dest.writeInt(pid);
dest.writeInt(uid);
dest.writeStringArray(pkgList);
+ dest.writeInt(this.flags);
dest.writeInt(importance);
dest.writeInt(lru);
dest.writeInt(importanceReasonCode);
@@ -844,6 +909,7 @@ public class ActivityManager {
pid = source.readInt();
uid = source.readInt();
pkgList = source.readStringArray();
+ flags = source.readInt();
importance = source.readInt();
lru = source.readInt();
importanceReasonCode = source.readInt();
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index f69428589e07..a1808377c69c 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -37,6 +37,7 @@ import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
@@ -1021,16 +1022,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case REPORT_PSS_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
- IBinder b = data.readStrongBinder();
- IApplicationThread app = ApplicationThreadNative.asInterface(b);
- int pss = data.readInt();
- reportPss(app, pss);
- reply.writeNoException();
- return true;
- }
-
case START_RUNNING_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String pkg = data.readString();
@@ -1062,6 +1053,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder app = data.readStrongBinder();
+ int violationMask = data.readInt();
+ StrictMode.ViolationInfo info = new StrictMode.ViolationInfo(data);
+ handleApplicationStrictModeViolation(app, violationMask, info);
+ reply.writeNoException();
+ return true;
+ }
+
case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int sig = data.readInt();
@@ -1247,10 +1248,64 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case IS_USER_A_MONKEY_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
- reply.writeInt(isUserAMonkey() ? 1 : 0);
+ boolean areThey = isUserAMonkey();
reply.writeNoException();
+ reply.writeInt(areThey ? 1 : 0);
return true;
}
+
+ case FINISH_HEAVY_WEIGHT_APP_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ finishHeavyWeightApp();
+ reply.writeNoException();
+ return true;
+ }
+
+ case CRASH_APPLICATION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int uid = data.readInt();
+ int initialPid = data.readInt();
+ String packageName = data.readString();
+ String message = data.readString();
+ crashApplication(uid, initialPid, packageName, message);
+ reply.writeNoException();
+ return true;
+ }
+
+ case NEW_URI_PERMISSION_OWNER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String name = data.readString();
+ IBinder perm = newUriPermissionOwner(name);
+ reply.writeNoException();
+ reply.writeStrongBinder(perm);
+ return true;
+ }
+
+ case GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder owner = data.readStrongBinder();
+ int fromUid = data.readInt();
+ String targetPkg = data.readString();
+ Uri uri = Uri.CREATOR.createFromParcel(data);
+ int mode = data.readInt();
+ grantUriPermissionFromOwner(owner, fromUid, targetPkg, uri, mode);
+ reply.writeNoException();
+ return true;
+ }
+
+ case REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder owner = data.readStrongBinder();
+ Uri uri = null;
+ if (data.readInt() != 0) {
+ Uri.CREATOR.createFromParcel(data);
+ }
+ int mode = data.readInt();
+ revokeUriPermissionFromOwner(owner, uri, mode);
+ reply.writeNoException();
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -2476,14 +2531,6 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return res;
}
- public void reportPss(IApplicationThread caller, int pss) throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeStrongBinder(caller.asBinder());
- data.writeInt(pss);
- mRemote.transact(REPORT_PSS_TRANSACTION, data, null, 0);
- data.recycle();
- }
public void startRunning(String pkg, String cls, String action,
String indata) throws RemoteException {
Parcel data = Parcel.obtain();
@@ -2516,6 +2563,7 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
data.recycle();
}
+
public boolean handleApplicationWtf(IBinder app, String tag,
ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
{
@@ -2533,6 +2581,22 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public void handleApplicationStrictModeViolation(IBinder app,
+ int violationMask,
+ StrictMode.ViolationInfo info) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(app);
+ data.writeInt(violationMask);
+ info.writeToParcel(data, 0);
+ mRemote.transact(HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
public void signalPersistentProcesses(int sig) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2758,5 +2822,79 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public void finishHeavyWeightApp() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(FINISH_HEAVY_WEIGHT_APP_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void crashApplication(int uid, int initialPid, String packageName,
+ String message) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(uid);
+ data.writeInt(initialPid);
+ data.writeString(packageName);
+ data.writeString(message);
+ mRemote.transact(CRASH_APPLICATION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public IBinder newUriPermissionOwner(String name)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(name);
+ mRemote.transact(NEW_URI_PERMISSION_OWNER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ IBinder res = reply.readStrongBinder();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
+ public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
+ Uri uri, int mode) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(owner);
+ data.writeInt(fromUid);
+ data.writeString(targetPkg);
+ uri.writeToParcel(data, 0);
+ data.writeInt(mode);
+ mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
+ int mode) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(owner);
+ if (uri != null) {
+ data.writeInt(1);
+ uri.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
+ data.writeInt(mode);
+ mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 773c344bb3f6..f8407c22d7ec 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -25,7 +25,6 @@ import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IIntentReceiver;
-import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -42,6 +41,7 @@ import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -53,6 +53,7 @@ import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.util.AndroidRuntimeException;
import android.util.Config;
@@ -72,7 +73,6 @@ import android.view.WindowManagerImpl;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
-import com.android.internal.util.ArrayUtils;
import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
@@ -80,12 +80,9 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.net.URL;
import java.util.ArrayList;
-import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -96,20 +93,14 @@ import java.util.regex.Pattern;
import dalvik.system.SamplingProfiler;
-final class IntentReceiverLeaked extends AndroidRuntimeException {
- public IntentReceiverLeaked(String msg) {
- super(msg);
- }
-}
-
-final class ServiceConnectionLeaked extends AndroidRuntimeException {
- public ServiceConnectionLeaked(String msg) {
+final class SuperNotCalledException extends AndroidRuntimeException {
+ public SuperNotCalledException(String msg) {
super(msg);
}
}
-final class SuperNotCalledException extends AndroidRuntimeException {
- public SuperNotCalledException(String msg) {
+final class RemoteServiceException extends AndroidRuntimeException {
+ public RemoteServiceException(String msg) {
super(msg);
}
}
@@ -123,10 +114,11 @@ final class SuperNotCalledException extends AndroidRuntimeException {
* {@hide}
*/
public final class ActivityThread {
- private static final String TAG = "ActivityThread";
+ static final String TAG = "ActivityThread";
+ private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
private static final boolean DEBUG = false;
- private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
- private static final boolean DEBUG_BROADCAST = false;
+ static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+ static final boolean DEBUG_BROADCAST = false;
private static final boolean DEBUG_RESULTS = false;
private static final boolean DEBUG_BACKUP = false;
private static final boolean DEBUG_CONFIGURATION = false;
@@ -136,1162 +128,67 @@ public final class ActivityThread {
private static final int LOG_ON_PAUSE_CALLED = 30021;
private static final int LOG_ON_RESUME_CALLED = 30022;
+ static ContextImpl mSystemContext = null;
- public static final ActivityThread currentActivityThread() {
- return (ActivityThread)sThreadLocal.get();
- }
-
- public static final String currentPackageName()
- {
- ActivityThread am = currentActivityThread();
- return (am != null && am.mBoundApplication != null)
- ? am.mBoundApplication.processName : null;
- }
-
- public static IPackageManager getPackageManager() {
- if (sPackageManager != null) {
- //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
- return sPackageManager;
- }
- IBinder b = ServiceManager.getService("package");
- //Slog.v("PackageManager", "default service binder = " + b);
- sPackageManager = IPackageManager.Stub.asInterface(b);
- //Slog.v("PackageManager", "default service = " + sPackageManager);
- return sPackageManager;
- }
-
- DisplayMetrics getDisplayMetricsLocked(boolean forceUpdate) {
- if (mDisplayMetrics != null && !forceUpdate) {
- return mDisplayMetrics;
- }
- if (mDisplay == null) {
- WindowManager wm = WindowManagerImpl.getDefault();
- mDisplay = wm.getDefaultDisplay();
- }
- DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics();
- mDisplay.getMetrics(metrics);
- //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
- // + metrics.heightPixels + " den=" + metrics.density
- // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
- return metrics;
- }
-
- /**
- * Creates the top level Resources for applications with the given compatibility info.
- *
- * @param resDir the resource directory.
- * @param compInfo the compability info. It will use the default compatibility info when it's
- * null.
- */
- Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
- ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
- Resources r;
- synchronized (mPackages) {
- // Resources is app scale dependent.
- if (false) {
- Slog.w(TAG, "getTopLevelResources: " + resDir + " / "
- + compInfo.applicationScale);
- }
- WeakReference<Resources> wr = mActiveResources.get(key);
- r = wr != null ? wr.get() : null;
- //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
- if (r != null && r.getAssets().isUpToDate()) {
- if (false) {
- Slog.w(TAG, "Returning cached resources " + r + " " + resDir
- + ": appScale=" + r.getCompatibilityInfo().applicationScale);
- }
- return r;
- }
- }
-
- //if (r != null) {
- // Slog.w(TAG, "Throwing away out-of-date resources!!!! "
- // + r + " " + resDir);
- //}
-
- AssetManager assets = new AssetManager();
- if (assets.addAssetPath(resDir) == 0) {
- return null;
- }
-
- //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
- DisplayMetrics metrics = getDisplayMetricsLocked(false);
- r = new Resources(assets, metrics, getConfiguration(), compInfo);
- if (false) {
- Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
- + r.getConfiguration() + " appScale="
- + r.getCompatibilityInfo().applicationScale);
- }
-
- synchronized (mPackages) {
- WeakReference<Resources> wr = mActiveResources.get(key);
- Resources existing = wr != null ? wr.get() : null;
- if (existing != null && existing.getAssets().isUpToDate()) {
- // Someone else already created the resources while we were
- // unlocked; go ahead and use theirs.
- r.getAssets().close();
- return existing;
- }
-
- // XXX need to remove entries when weak references go away
- mActiveResources.put(key, new WeakReference<Resources>(r));
- return r;
- }
- }
-
- /**
- * Creates the top level resources for the given package.
- */
- Resources getTopLevelResources(String resDir, PackageInfo pkgInfo) {
- return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo);
- }
-
- final Handler getHandler() {
- return mH;
- }
-
- public final static class PackageInfo {
-
- private final ActivityThread mActivityThread;
- private final ApplicationInfo mApplicationInfo;
- private final String mPackageName;
- private final String mAppDir;
- private final String mResDir;
- private final String[] mSharedLibraries;
- private final String mDataDir;
- private final File mDataDirFile;
- private final ClassLoader mBaseClassLoader;
- private final boolean mSecurityViolation;
- private final boolean mIncludeCode;
- private Resources mResources;
- private ClassLoader mClassLoader;
- private Application mApplication;
- private CompatibilityInfo mCompatibilityInfo;
-
- private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
- = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>();
- private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mUnregisteredReceivers
- = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>();
- private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mServices
- = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>();
- private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mUnboundServices
- = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>();
-
- int mClientCount = 0;
-
- Application getApplication() {
- return mApplication;
- }
-
- public PackageInfo(ActivityThread activityThread, ApplicationInfo aInfo,
- ActivityThread mainThread, ClassLoader baseLoader,
- boolean securityViolation, boolean includeCode) {
- mActivityThread = activityThread;
- mApplicationInfo = aInfo;
- mPackageName = aInfo.packageName;
- mAppDir = aInfo.sourceDir;
- mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir
- : aInfo.publicSourceDir;
- mSharedLibraries = aInfo.sharedLibraryFiles;
- mDataDir = aInfo.dataDir;
- mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
- mBaseClassLoader = baseLoader;
- mSecurityViolation = securityViolation;
- mIncludeCode = includeCode;
- mCompatibilityInfo = new CompatibilityInfo(aInfo);
-
- if (mAppDir == null) {
- if (mSystemContext == null) {
- mSystemContext =
- ContextImpl.createSystemContext(mainThread);
- mSystemContext.getResources().updateConfiguration(
- mainThread.getConfiguration(),
- mainThread.getDisplayMetricsLocked(false));
- //Slog.i(TAG, "Created system resources "
- // + mSystemContext.getResources() + ": "
- // + mSystemContext.getResources().getConfiguration());
- }
- mClassLoader = mSystemContext.getClassLoader();
- mResources = mSystemContext.getResources();
- }
- }
-
- public PackageInfo(ActivityThread activityThread, String name,
- Context systemContext, ApplicationInfo info) {
- mActivityThread = activityThread;
- mApplicationInfo = info != null ? info : new ApplicationInfo();
- mApplicationInfo.packageName = name;
- mPackageName = name;
- mAppDir = null;
- mResDir = null;
- mSharedLibraries = null;
- mDataDir = null;
- mDataDirFile = null;
- mBaseClassLoader = null;
- mSecurityViolation = false;
- mIncludeCode = true;
- mClassLoader = systemContext.getClassLoader();
- mResources = systemContext.getResources();
- mCompatibilityInfo = new CompatibilityInfo(mApplicationInfo);
- }
-
- public String getPackageName() {
- return mPackageName;
- }
-
- public ApplicationInfo getApplicationInfo() {
- return mApplicationInfo;
- }
-
- public boolean isSecurityViolation() {
- return mSecurityViolation;
- }
-
- /**
- * Gets the array of shared libraries that are listed as
- * used by the given package.
- *
- * @param packageName the name of the package (note: not its
- * file name)
- * @return null-ok; the array of shared libraries, each one
- * a fully-qualified path
- */
- private static String[] getLibrariesFor(String packageName) {
- ApplicationInfo ai = null;
- try {
- ai = getPackageManager().getApplicationInfo(packageName,
- PackageManager.GET_SHARED_LIBRARY_FILES);
- } catch (RemoteException e) {
- throw new AssertionError(e);
- }
-
- if (ai == null) {
- return null;
- }
-
- return ai.sharedLibraryFiles;
- }
-
- /**
- * Combines two arrays (of library names) such that they are
- * concatenated in order but are devoid of duplicates. The
- * result is a single string with the names of the libraries
- * separated by colons, or <code>null</code> if both lists
- * were <code>null</code> or empty.
- *
- * @param list1 null-ok; the first list
- * @param list2 null-ok; the second list
- * @return null-ok; the combination
- */
- private static String combineLibs(String[] list1, String[] list2) {
- StringBuilder result = new StringBuilder(300);
- boolean first = true;
-
- if (list1 != null) {
- for (String s : list1) {
- if (first) {
- first = false;
- } else {
- result.append(':');
- }
- result.append(s);
- }
- }
-
- // Only need to check for duplicates if list1 was non-empty.
- boolean dupCheck = !first;
-
- if (list2 != null) {
- for (String s : list2) {
- if (dupCheck && ArrayUtils.contains(list1, s)) {
- continue;
- }
-
- if (first) {
- first = false;
- } else {
- result.append(':');
- }
- result.append(s);
- }
- }
-
- return result.toString();
- }
-
- public ClassLoader getClassLoader() {
- synchronized (this) {
- if (mClassLoader != null) {
- return mClassLoader;
- }
-
- if (mIncludeCode && !mPackageName.equals("android")) {
- String zip = mAppDir;
-
- /*
- * The following is a bit of a hack to inject
- * instrumentation into the system: If the app
- * being started matches one of the instrumentation names,
- * then we combine both the "instrumentation" and
- * "instrumented" app into the path, along with the
- * concatenation of both apps' shared library lists.
- */
-
- String instrumentationAppDir =
- mActivityThread.mInstrumentationAppDir;
- String instrumentationAppPackage =
- mActivityThread.mInstrumentationAppPackage;
- String instrumentedAppDir =
- mActivityThread.mInstrumentedAppDir;
- String[] instrumentationLibs = null;
-
- if (mAppDir.equals(instrumentationAppDir)
- || mAppDir.equals(instrumentedAppDir)) {
- zip = instrumentationAppDir + ":" + instrumentedAppDir;
- if (! instrumentedAppDir.equals(instrumentationAppDir)) {
- instrumentationLibs =
- getLibrariesFor(instrumentationAppPackage);
- }
- }
-
- if ((mSharedLibraries != null) ||
- (instrumentationLibs != null)) {
- zip =
- combineLibs(mSharedLibraries, instrumentationLibs)
- + ':' + zip;
- }
-
- /*
- * With all the combination done (if necessary, actually
- * create the class loader.
- */
-
- if (localLOGV) Slog.v(TAG, "Class path: " + zip);
-
- mClassLoader =
- ApplicationLoaders.getDefault().getClassLoader(
- zip, mDataDir, mBaseClassLoader);
- initializeJavaContextClassLoader();
- } else {
- if (mBaseClassLoader == null) {
- mClassLoader = ClassLoader.getSystemClassLoader();
- } else {
- mClassLoader = mBaseClassLoader;
- }
- }
- return mClassLoader;
- }
- }
-
- /**
- * Setup value for Thread.getContextClassLoader(). If the
- * package will not run in in a VM with other packages, we set
- * the Java context ClassLoader to the
- * PackageInfo.getClassLoader value. However, if this VM can
- * contain multiple packages, we intead set the Java context
- * ClassLoader to a proxy that will warn about the use of Java
- * context ClassLoaders and then fall through to use the
- * system ClassLoader.
- *
- * <p> Note that this is similar to but not the same as the
- * android.content.Context.getClassLoader(). While both
- * context class loaders are typically set to the
- * PathClassLoader used to load the package archive in the
- * single application per VM case, a single Android process
- * may contain several Contexts executing on one thread with
- * their own logical ClassLoaders while the Java context
- * ClassLoader is a thread local. This is why in the case when
- * we have multiple packages per VM we do not set the Java
- * context ClassLoader to an arbitrary but instead warn the
- * user to set their own if we detect that they are using a
- * Java library that expects it to be set.
- */
- private void initializeJavaContextClassLoader() {
- IPackageManager pm = getPackageManager();
- android.content.pm.PackageInfo pi;
- try {
- pi = pm.getPackageInfo(mPackageName, 0);
- } catch (RemoteException e) {
- throw new AssertionError(e);
- }
- /*
- * Two possible indications that this package could be
- * sharing its virtual machine with other packages:
- *
- * 1.) the sharedUserId attribute is set in the manifest,
- * indicating a request to share a VM with other
- * packages with the same sharedUserId.
- *
- * 2.) the application element of the manifest has an
- * attribute specifying a non-default process name,
- * indicating the desire to run in another packages VM.
- */
- boolean sharedUserIdSet = (pi.sharedUserId != null);
- boolean processNameNotDefault =
- (pi.applicationInfo != null &&
- !mPackageName.equals(pi.applicationInfo.processName));
- boolean sharable = (sharedUserIdSet || processNameNotDefault);
- ClassLoader contextClassLoader =
- (sharable)
- ? new WarningContextClassLoader()
- : mClassLoader;
- Thread.currentThread().setContextClassLoader(contextClassLoader);
- }
-
- private static class WarningContextClassLoader extends ClassLoader {
-
- private static boolean warned = false;
-
- private void warn(String methodName) {
- if (warned) {
- return;
- }
- warned = true;
- Thread.currentThread().setContextClassLoader(getParent());
- Slog.w(TAG, "ClassLoader." + methodName + ": " +
- "The class loader returned by " +
- "Thread.getContextClassLoader() may fail for processes " +
- "that host multiple applications. You should explicitly " +
- "specify a context class loader. For example: " +
- "Thread.setContextClassLoader(getClass().getClassLoader());");
- }
-
- @Override public URL getResource(String resName) {
- warn("getResource");
- return getParent().getResource(resName);
- }
-
- @Override public Enumeration<URL> getResources(String resName) throws IOException {
- warn("getResources");
- return getParent().getResources(resName);
- }
-
- @Override public InputStream getResourceAsStream(String resName) {
- warn("getResourceAsStream");
- return getParent().getResourceAsStream(resName);
- }
-
- @Override public Class<?> loadClass(String className) throws ClassNotFoundException {
- warn("loadClass");
- return getParent().loadClass(className);
- }
-
- @Override public void setClassAssertionStatus(String cname, boolean enable) {
- warn("setClassAssertionStatus");
- getParent().setClassAssertionStatus(cname, enable);
- }
-
- @Override public void setPackageAssertionStatus(String pname, boolean enable) {
- warn("setPackageAssertionStatus");
- getParent().setPackageAssertionStatus(pname, enable);
- }
-
- @Override public void setDefaultAssertionStatus(boolean enable) {
- warn("setDefaultAssertionStatus");
- getParent().setDefaultAssertionStatus(enable);
- }
-
- @Override public void clearAssertionStatus() {
- warn("clearAssertionStatus");
- getParent().clearAssertionStatus();
- }
- }
-
- public String getAppDir() {
- return mAppDir;
- }
-
- public String getResDir() {
- return mResDir;
- }
-
- public String getDataDir() {
- return mDataDir;
- }
-
- public File getDataDirFile() {
- return mDataDirFile;
- }
-
- public AssetManager getAssets(ActivityThread mainThread) {
- return getResources(mainThread).getAssets();
- }
-
- public Resources getResources(ActivityThread mainThread) {
- if (mResources == null) {
- mResources = mainThread.getTopLevelResources(mResDir, this);
- }
- return mResources;
- }
-
- public Application makeApplication(boolean forceDefaultAppClass,
- Instrumentation instrumentation) {
- if (mApplication != null) {
- return mApplication;
- }
-
- Application app = null;
-
- String appClass = mApplicationInfo.className;
- if (forceDefaultAppClass || (appClass == null)) {
- appClass = "android.app.Application";
- }
-
- try {
- java.lang.ClassLoader cl = getClassLoader();
- ContextImpl appContext = new ContextImpl();
- appContext.init(this, null, mActivityThread);
- app = mActivityThread.mInstrumentation.newApplication(
- cl, appClass, appContext);
- appContext.setOuterContext(app);
- } catch (Exception e) {
- if (!mActivityThread.mInstrumentation.onException(app, e)) {
- throw new RuntimeException(
- "Unable to instantiate application " + appClass
- + ": " + e.toString(), e);
- }
- }
- mActivityThread.mAllApplications.add(app);
- mApplication = app;
-
- if (instrumentation != null) {
- try {
- instrumentation.callApplicationOnCreate(app);
- } catch (Exception e) {
- if (!instrumentation.onException(app, e)) {
- throw new RuntimeException(
- "Unable to create application " + app.getClass().getName()
- + ": " + e.toString(), e);
- }
- }
- }
-
- return app;
- }
-
- public void removeContextRegistrations(Context context,
- String who, String what) {
- HashMap<BroadcastReceiver, ReceiverDispatcher> rmap =
- mReceivers.remove(context);
- if (rmap != null) {
- Iterator<ReceiverDispatcher> it = rmap.values().iterator();
- while (it.hasNext()) {
- ReceiverDispatcher rd = it.next();
- IntentReceiverLeaked leak = new IntentReceiverLeaked(
- what + " " + who + " has leaked IntentReceiver "
- + rd.getIntentReceiver() + " that was " +
- "originally registered here. Are you missing a " +
- "call to unregisterReceiver()?");
- leak.setStackTrace(rd.getLocation().getStackTrace());
- Slog.e(TAG, leak.getMessage(), leak);
- try {
- ActivityManagerNative.getDefault().unregisterReceiver(
- rd.getIIntentReceiver());
- } catch (RemoteException e) {
- // system crashed, nothing we can do
- }
- }
- }
- mUnregisteredReceivers.remove(context);
- //Slog.i(TAG, "Receiver registrations: " + mReceivers);
- HashMap<ServiceConnection, ServiceDispatcher> smap =
- mServices.remove(context);
- if (smap != null) {
- Iterator<ServiceDispatcher> it = smap.values().iterator();
- while (it.hasNext()) {
- ServiceDispatcher sd = it.next();
- ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
- what + " " + who + " has leaked ServiceConnection "
- + sd.getServiceConnection() + " that was originally bound here");
- leak.setStackTrace(sd.getLocation().getStackTrace());
- Slog.e(TAG, leak.getMessage(), leak);
- try {
- ActivityManagerNative.getDefault().unbindService(
- sd.getIServiceConnection());
- } catch (RemoteException e) {
- // system crashed, nothing we can do
- }
- sd.doForget();
- }
- }
- mUnboundServices.remove(context);
- //Slog.i(TAG, "Service registrations: " + mServices);
- }
-
- public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
- Context context, Handler handler,
- Instrumentation instrumentation, boolean registered) {
- synchronized (mReceivers) {
- ReceiverDispatcher rd = null;
- HashMap<BroadcastReceiver, ReceiverDispatcher> map = null;
- if (registered) {
- map = mReceivers.get(context);
- if (map != null) {
- rd = map.get(r);
- }
- }
- if (rd == null) {
- rd = new ReceiverDispatcher(r, context, handler,
- instrumentation, registered);
- if (registered) {
- if (map == null) {
- map = new HashMap<BroadcastReceiver, ReceiverDispatcher>();
- mReceivers.put(context, map);
- }
- map.put(r, rd);
- }
- } else {
- rd.validate(context, handler);
- }
- return rd.getIIntentReceiver();
- }
- }
-
- public IIntentReceiver forgetReceiverDispatcher(Context context,
- BroadcastReceiver r) {
- synchronized (mReceivers) {
- HashMap<BroadcastReceiver, ReceiverDispatcher> map = mReceivers.get(context);
- ReceiverDispatcher rd = null;
- if (map != null) {
- rd = map.get(r);
- if (rd != null) {
- map.remove(r);
- if (map.size() == 0) {
- mReceivers.remove(context);
- }
- if (r.getDebugUnregister()) {
- HashMap<BroadcastReceiver, ReceiverDispatcher> holder
- = mUnregisteredReceivers.get(context);
- if (holder == null) {
- holder = new HashMap<BroadcastReceiver, ReceiverDispatcher>();
- mUnregisteredReceivers.put(context, holder);
- }
- RuntimeException ex = new IllegalArgumentException(
- "Originally unregistered here:");
- ex.fillInStackTrace();
- rd.setUnregisterLocation(ex);
- holder.put(r, rd);
- }
- return rd.getIIntentReceiver();
- }
- }
- HashMap<BroadcastReceiver, ReceiverDispatcher> holder
- = mUnregisteredReceivers.get(context);
- if (holder != null) {
- rd = holder.get(r);
- if (rd != null) {
- RuntimeException ex = rd.getUnregisterLocation();
- throw new IllegalArgumentException(
- "Unregistering Receiver " + r
- + " that was already unregistered", ex);
- }
- }
- if (context == null) {
- throw new IllegalStateException("Unbinding Receiver " + r
- + " from Context that is no longer in use: " + context);
- } else {
- throw new IllegalArgumentException("Receiver not registered: " + r);
- }
-
- }
- }
-
- static final class ReceiverDispatcher {
-
- final static class InnerReceiver extends IIntentReceiver.Stub {
- final WeakReference<ReceiverDispatcher> mDispatcher;
- final ReceiverDispatcher mStrongRef;
-
- InnerReceiver(ReceiverDispatcher rd, boolean strong) {
- mDispatcher = new WeakReference<ReceiverDispatcher>(rd);
- mStrongRef = strong ? rd : null;
- }
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered, boolean sticky) {
- ReceiverDispatcher rd = mDispatcher.get();
- if (DEBUG_BROADCAST) {
- int seq = intent.getIntExtra("seq", -1);
- Slog.i(TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq
- + " to " + (rd != null ? rd.mReceiver : null));
- }
- if (rd != null) {
- rd.performReceive(intent, resultCode, data, extras,
- ordered, sticky);
- } else {
- // The activity manager dispatched a broadcast to a registered
- // receiver in this process, but before it could be delivered the
- // receiver was unregistered. Acknowledge the broadcast on its
- // behalf so that the system's broadcast sequence can continue.
- if (DEBUG_BROADCAST) Slog.i(TAG,
- "Finishing broadcast to unregistered receiver");
- IActivityManager mgr = ActivityManagerNative.getDefault();
- try {
- mgr.finishReceiver(this, resultCode, data, extras, false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Couldn't finish broadcast to unregistered receiver");
- }
- }
- }
- }
-
- final IIntentReceiver.Stub mIIntentReceiver;
- final BroadcastReceiver mReceiver;
- final Context mContext;
- final Handler mActivityThread;
- final Instrumentation mInstrumentation;
- final boolean mRegistered;
- final IntentReceiverLeaked mLocation;
- RuntimeException mUnregisterLocation;
-
- final class Args implements Runnable {
- private Intent mCurIntent;
- private int mCurCode;
- private String mCurData;
- private Bundle mCurMap;
- private boolean mCurOrdered;
- private boolean mCurSticky;
-
- public void run() {
- BroadcastReceiver receiver = mReceiver;
- if (DEBUG_BROADCAST) {
- int seq = mCurIntent.getIntExtra("seq", -1);
- Slog.i(TAG, "Dispatching broadcast " + mCurIntent.getAction()
- + " seq=" + seq + " to " + mReceiver);
- Slog.i(TAG, " mRegistered=" + mRegistered
- + " mCurOrdered=" + mCurOrdered);
- }
-
- IActivityManager mgr = ActivityManagerNative.getDefault();
- Intent intent = mCurIntent;
- mCurIntent = null;
-
- if (receiver == null) {
- if (mRegistered && mCurOrdered) {
- try {
- if (DEBUG_BROADCAST) Slog.i(TAG,
- "Finishing null broadcast to " + mReceiver);
- mgr.finishReceiver(mIIntentReceiver,
- mCurCode, mCurData, mCurMap, false);
- } catch (RemoteException ex) {
- }
- }
- return;
- }
-
- try {
- ClassLoader cl = mReceiver.getClass().getClassLoader();
- intent.setExtrasClassLoader(cl);
- if (mCurMap != null) {
- mCurMap.setClassLoader(cl);
- }
- receiver.setOrderedHint(true);
- receiver.setResult(mCurCode, mCurData, mCurMap);
- receiver.clearAbortBroadcast();
- receiver.setOrderedHint(mCurOrdered);
- receiver.setInitialStickyHint(mCurSticky);
- receiver.onReceive(mContext, intent);
- } catch (Exception e) {
- if (mRegistered && mCurOrdered) {
- try {
- if (DEBUG_BROADCAST) Slog.i(TAG,
- "Finishing failed broadcast to " + mReceiver);
- mgr.finishReceiver(mIIntentReceiver,
- mCurCode, mCurData, mCurMap, false);
- } catch (RemoteException ex) {
- }
- }
- if (mInstrumentation == null ||
- !mInstrumentation.onException(mReceiver, e)) {
- throw new RuntimeException(
- "Error receiving broadcast " + intent
- + " in " + mReceiver, e);
- }
- }
- if (mRegistered && mCurOrdered) {
- try {
- if (DEBUG_BROADCAST) Slog.i(TAG,
- "Finishing broadcast to " + mReceiver);
- mgr.finishReceiver(mIIntentReceiver,
- receiver.getResultCode(),
- receiver.getResultData(),
- receiver.getResultExtras(false),
- receiver.getAbortBroadcast());
- } catch (RemoteException ex) {
- }
- }
- }
- }
-
- ReceiverDispatcher(BroadcastReceiver receiver, Context context,
- Handler activityThread, Instrumentation instrumentation,
- boolean registered) {
- if (activityThread == null) {
- throw new NullPointerException("Handler must not be null");
- }
-
- mIIntentReceiver = new InnerReceiver(this, !registered);
- mReceiver = receiver;
- mContext = context;
- mActivityThread = activityThread;
- mInstrumentation = instrumentation;
- mRegistered = registered;
- mLocation = new IntentReceiverLeaked(null);
- mLocation.fillInStackTrace();
- }
-
- void validate(Context context, Handler activityThread) {
- if (mContext != context) {
- throw new IllegalStateException(
- "Receiver " + mReceiver +
- " registered with differing Context (was " +
- mContext + " now " + context + ")");
- }
- if (mActivityThread != activityThread) {
- throw new IllegalStateException(
- "Receiver " + mReceiver +
- " registered with differing handler (was " +
- mActivityThread + " now " + activityThread + ")");
- }
- }
-
- IntentReceiverLeaked getLocation() {
- return mLocation;
- }
-
- BroadcastReceiver getIntentReceiver() {
- return mReceiver;
- }
-
- IIntentReceiver getIIntentReceiver() {
- return mIIntentReceiver;
- }
-
- void setUnregisterLocation(RuntimeException ex) {
- mUnregisterLocation = ex;
- }
-
- RuntimeException getUnregisterLocation() {
- return mUnregisterLocation;
- }
-
- public void performReceive(Intent intent, int resultCode,
- String data, Bundle extras, boolean ordered, boolean sticky) {
- if (DEBUG_BROADCAST) {
- int seq = intent.getIntExtra("seq", -1);
- Slog.i(TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
- + " to " + mReceiver);
- }
- Args args = new Args();
- args.mCurIntent = intent;
- args.mCurCode = resultCode;
- args.mCurData = data;
- args.mCurMap = extras;
- args.mCurOrdered = ordered;
- args.mCurSticky = sticky;
- if (!mActivityThread.post(args)) {
- if (mRegistered && ordered) {
- IActivityManager mgr = ActivityManagerNative.getDefault();
- try {
- if (DEBUG_BROADCAST) Slog.i(TAG,
- "Finishing sync broadcast to " + mReceiver);
- mgr.finishReceiver(mIIntentReceiver, args.mCurCode,
- args.mCurData, args.mCurMap, false);
- } catch (RemoteException ex) {
- }
- }
- }
- }
-
- }
-
- public final IServiceConnection getServiceDispatcher(ServiceConnection c,
- Context context, Handler handler, int flags) {
- synchronized (mServices) {
- ServiceDispatcher sd = null;
- HashMap<ServiceConnection, ServiceDispatcher> map = mServices.get(context);
- if (map != null) {
- sd = map.get(c);
- }
- if (sd == null) {
- sd = new ServiceDispatcher(c, context, handler, flags);
- if (map == null) {
- map = new HashMap<ServiceConnection, ServiceDispatcher>();
- mServices.put(context, map);
- }
- map.put(c, sd);
- } else {
- sd.validate(context, handler);
- }
- return sd.getIServiceConnection();
- }
- }
-
- public final IServiceConnection forgetServiceDispatcher(Context context,
- ServiceConnection c) {
- synchronized (mServices) {
- HashMap<ServiceConnection, ServiceDispatcher> map
- = mServices.get(context);
- ServiceDispatcher sd = null;
- if (map != null) {
- sd = map.get(c);
- if (sd != null) {
- map.remove(c);
- sd.doForget();
- if (map.size() == 0) {
- mServices.remove(context);
- }
- if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) {
- HashMap<ServiceConnection, ServiceDispatcher> holder
- = mUnboundServices.get(context);
- if (holder == null) {
- holder = new HashMap<ServiceConnection, ServiceDispatcher>();
- mUnboundServices.put(context, holder);
- }
- RuntimeException ex = new IllegalArgumentException(
- "Originally unbound here:");
- ex.fillInStackTrace();
- sd.setUnbindLocation(ex);
- holder.put(c, sd);
- }
- return sd.getIServiceConnection();
- }
- }
- HashMap<ServiceConnection, ServiceDispatcher> holder
- = mUnboundServices.get(context);
- if (holder != null) {
- sd = holder.get(c);
- if (sd != null) {
- RuntimeException ex = sd.getUnbindLocation();
- throw new IllegalArgumentException(
- "Unbinding Service " + c
- + " that was already unbound", ex);
- }
- }
- if (context == null) {
- throw new IllegalStateException("Unbinding Service " + c
- + " from Context that is no longer in use: " + context);
- } else {
- throw new IllegalArgumentException("Service not registered: " + c);
- }
- }
- }
-
- static final class ServiceDispatcher {
- private final InnerConnection mIServiceConnection;
- private final ServiceConnection mConnection;
- private final Context mContext;
- private final Handler mActivityThread;
- private final ServiceConnectionLeaked mLocation;
- private final int mFlags;
-
- private RuntimeException mUnbindLocation;
-
- private boolean mDied;
-
- private static class ConnectionInfo {
- IBinder binder;
- IBinder.DeathRecipient deathMonitor;
- }
-
- private static class InnerConnection extends IServiceConnection.Stub {
- final WeakReference<ServiceDispatcher> mDispatcher;
-
- InnerConnection(ServiceDispatcher sd) {
- mDispatcher = new WeakReference<ServiceDispatcher>(sd);
- }
-
- public void connected(ComponentName name, IBinder service) throws RemoteException {
- ServiceDispatcher sd = mDispatcher.get();
- if (sd != null) {
- sd.connected(name, service);
- }
- }
- }
-
- private final HashMap<ComponentName, ConnectionInfo> mActiveConnections
- = new HashMap<ComponentName, ConnectionInfo>();
-
- ServiceDispatcher(ServiceConnection conn,
- Context context, Handler activityThread, int flags) {
- mIServiceConnection = new InnerConnection(this);
- mConnection = conn;
- mContext = context;
- mActivityThread = activityThread;
- mLocation = new ServiceConnectionLeaked(null);
- mLocation.fillInStackTrace();
- mFlags = flags;
- }
-
- void validate(Context context, Handler activityThread) {
- if (mContext != context) {
- throw new RuntimeException(
- "ServiceConnection " + mConnection +
- " registered with differing Context (was " +
- mContext + " now " + context + ")");
- }
- if (mActivityThread != activityThread) {
- throw new RuntimeException(
- "ServiceConnection " + mConnection +
- " registered with differing handler (was " +
- mActivityThread + " now " + activityThread + ")");
- }
- }
-
- void doForget() {
- synchronized(this) {
- Iterator<ConnectionInfo> it = mActiveConnections.values().iterator();
- while (it.hasNext()) {
- ConnectionInfo ci = it.next();
- ci.binder.unlinkToDeath(ci.deathMonitor, 0);
- }
- mActiveConnections.clear();
- }
- }
-
- ServiceConnectionLeaked getLocation() {
- return mLocation;
- }
-
- ServiceConnection getServiceConnection() {
- return mConnection;
- }
-
- IServiceConnection getIServiceConnection() {
- return mIServiceConnection;
- }
-
- int getFlags() {
- return mFlags;
- }
-
- void setUnbindLocation(RuntimeException ex) {
- mUnbindLocation = ex;
- }
-
- RuntimeException getUnbindLocation() {
- return mUnbindLocation;
- }
-
- public void connected(ComponentName name, IBinder service) {
- if (mActivityThread != null) {
- mActivityThread.post(new RunConnection(name, service, 0));
- } else {
- doConnected(name, service);
- }
- }
-
- public void death(ComponentName name, IBinder service) {
- ConnectionInfo old;
-
- synchronized (this) {
- mDied = true;
- old = mActiveConnections.remove(name);
- if (old == null || old.binder != service) {
- // Death for someone different than who we last
- // reported... just ignore it.
- return;
- }
- old.binder.unlinkToDeath(old.deathMonitor, 0);
- }
-
- if (mActivityThread != null) {
- mActivityThread.post(new RunConnection(name, service, 1));
- } else {
- doDeath(name, service);
- }
- }
-
- public void doConnected(ComponentName name, IBinder service) {
- ConnectionInfo old;
- ConnectionInfo info;
-
- synchronized (this) {
- old = mActiveConnections.get(name);
- if (old != null && old.binder == service) {
- // Huh, already have this one. Oh well!
- return;
- }
-
- if (service != null) {
- // A new service is being connected... set it all up.
- mDied = false;
- info = new ConnectionInfo();
- info.binder = service;
- info.deathMonitor = new DeathMonitor(name, service);
- try {
- service.linkToDeath(info.deathMonitor, 0);
- mActiveConnections.put(name, info);
- } catch (RemoteException e) {
- // This service was dead before we got it... just
- // don't do anything with it.
- mActiveConnections.remove(name);
- return;
- }
-
- } else {
- // The named service is being disconnected... clean up.
- mActiveConnections.remove(name);
- }
-
- if (old != null) {
- old.binder.unlinkToDeath(old.deathMonitor, 0);
- }
- }
-
- // If there was an old service, it is not disconnected.
- if (old != null) {
- mConnection.onServiceDisconnected(name);
- }
- // If there is a new service, it is now connected.
- if (service != null) {
- mConnection.onServiceConnected(name, service);
- }
- }
-
- public void doDeath(ComponentName name, IBinder service) {
- mConnection.onServiceDisconnected(name);
- }
-
- private final class RunConnection implements Runnable {
- RunConnection(ComponentName name, IBinder service, int command) {
- mName = name;
- mService = service;
- mCommand = command;
- }
-
- public void run() {
- if (mCommand == 0) {
- doConnected(mName, mService);
- } else if (mCommand == 1) {
- doDeath(mName, mService);
- }
- }
+ static IPackageManager sPackageManager;
- final ComponentName mName;
- final IBinder mService;
- final int mCommand;
- }
+ final ApplicationThread mAppThread = new ApplicationThread();
+ final Looper mLooper = Looper.myLooper();
+ final H mH = new H();
+ final HashMap<IBinder, ActivityClientRecord> mActivities
+ = new HashMap<IBinder, ActivityClientRecord>();
+ // List of new activities (via ActivityRecord.nextIdle) that should
+ // be reported when next we idle.
+ ActivityClientRecord mNewActivities = null;
+ // Number of activities that are currently visible on-screen.
+ int mNumVisibleActivities = 0;
+ final HashMap<IBinder, Service> mServices
+ = new HashMap<IBinder, Service>();
+ AppBindData mBoundApplication;
+ Configuration mConfiguration;
+ Configuration mResConfiguration;
+ Application mInitialApplication;
+ final ArrayList<Application> mAllApplications
+ = new ArrayList<Application>();
+ // set of instantiated backup agents, keyed by package name
+ final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
+ static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal();
+ Instrumentation mInstrumentation;
+ String mInstrumentationAppDir = null;
+ String mInstrumentationAppPackage = null;
+ String mInstrumentedAppDir = null;
+ boolean mSystemThread = false;
+ boolean mJitEnabled = false;
- private final class DeathMonitor implements IBinder.DeathRecipient
- {
- DeathMonitor(ComponentName name, IBinder service) {
- mName = name;
- mService = service;
- }
+ // These can be accessed by multiple threads; mPackages is the lock.
+ // XXX For now we keep around information about all packages we have
+ // seen, not removing entries from this map.
+ final HashMap<String, WeakReference<LoadedApk>> mPackages
+ = new HashMap<String, WeakReference<LoadedApk>>();
+ final HashMap<String, WeakReference<LoadedApk>> mResourcePackages
+ = new HashMap<String, WeakReference<LoadedApk>>();
+ Display mDisplay = null;
+ DisplayMetrics mDisplayMetrics = null;
+ final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
+ = new HashMap<ResourcesKey, WeakReference<Resources> >();
+ final ArrayList<ActivityClientRecord> mRelaunchingActivities
+ = new ArrayList<ActivityClientRecord>();
+ Configuration mPendingConfiguration = null;
- public void binderDied() {
- death(mName, mService);
- }
+ // The lock of mProviderMap protects the following variables.
+ final HashMap<String, ProviderClientRecord> mProviderMap
+ = new HashMap<String, ProviderClientRecord>();
+ final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap
+ = new HashMap<IBinder, ProviderRefCount>();
+ final HashMap<IBinder, ProviderClientRecord> mLocalProviders
+ = new HashMap<IBinder, ProviderClientRecord>();
- final ComponentName mName;
- final IBinder mService;
- }
- }
- }
+ final GcIdler mGcIdler = new GcIdler();
+ boolean mGcIdlerScheduled = false;
- private static ContextImpl mSystemContext = null;
+ static Handler sMainThreadHandler; // set once in main()
- private static final class ActivityRecord {
+ private static final class ActivityClientRecord {
IBinder token;
int ident;
Intent intent;
@@ -1307,10 +204,10 @@ public final class ActivityThread {
boolean hideForNow;
Configuration newConfig;
Configuration createdConfig;
- ActivityRecord nextIdle;
+ ActivityClientRecord nextIdle;
ActivityInfo activityInfo;
- PackageInfo packageInfo;
+ LoadedApk packageInfo;
List<ResultInfo> pendingResults;
List<Intent> pendingIntents;
@@ -1318,7 +215,7 @@ public final class ActivityThread {
boolean startsNotResumed;
boolean isForward;
- ActivityRecord() {
+ ActivityClientRecord() {
parent = null;
embeddedID = null;
paused = false;
@@ -1337,12 +234,12 @@ public final class ActivityThread {
}
}
- private final class ProviderRecord implements IBinder.DeathRecipient {
+ private final class ProviderClientRecord implements IBinder.DeathRecipient {
final String mName;
final IContentProvider mProvider;
final ContentProvider mLocalProvider;
- ProviderRecord(String name, IContentProvider provider,
+ ProviderClientRecord(String name, IContentProvider provider,
ContentProvider localProvider) {
mName = name;
mProvider = provider;
@@ -1419,7 +316,7 @@ public final class ActivityThread {
}
private static final class AppBindData {
- PackageInfo info;
+ LoadedApk info;
String processName;
ApplicationInfo appInfo;
List<ProviderInfo> providers;
@@ -1509,7 +406,7 @@ public final class ActivityThread {
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Bundle state, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) {
- ActivityRecord r = new ActivityRecord();
+ ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
@@ -1529,7 +426,7 @@ public final class ActivityThread {
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config) {
- ActivityRecord r = new ActivityRecord();
+ ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.pendingResults = pendingResults;
@@ -1722,14 +619,6 @@ public final class ActivityThread {
queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
}
- public void requestPss() {
- try {
- ActivityManagerNative.getDefault().reportPss(this,
- (int)Process.getPss(Process.myPid()));
- } catch (RemoteException e) {
- }
- }
-
public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) {
ProfilerControlData pcd = new ProfilerControlData();
pcd.path = path;
@@ -1756,6 +645,10 @@ public final class ActivityThread {
public void dispatchPackageBroadcast(int cmd, String[] packages) {
queueOrSendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
}
+
+ public void scheduleCrash(String msg) {
+ queueOrSendMessage(H.SCHEDULE_CRASH, msg);
+ }
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -1944,10 +837,6 @@ public final class ActivityThread {
}
private final class H extends Handler {
- private H() {
- SamplingProfiler.getInstance().setEventThread(mLooper.getThread());
- }
-
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
@@ -1982,6 +871,7 @@ public final class ActivityThread {
public static final int REMOVE_PROVIDER = 131;
public static final int ENABLE_JIT = 132;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
+ public static final int SCHEDULE_CRASH = 134;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -2019,6 +909,7 @@ public final class ActivityThread {
case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
case ENABLE_JIT: return "ENABLE_JIT";
case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
+ case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
}
}
return "(unknown)";
@@ -2026,14 +917,14 @@ public final class ActivityThread {
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
- ActivityRecord r = (ActivityRecord)msg.obj;
+ ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo);
handleLaunchActivity(r, null);
} break;
case RELAUNCH_ACTIVITY: {
- ActivityRecord r = (ActivityRecord)msg.obj;
+ ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r, msg.arg1);
} break;
case PAUSE_ACTIVITY:
@@ -2142,6 +1033,8 @@ public final class ActivityThread {
case DISPATCH_PACKAGE_BROADCAST:
handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
break;
+ case SCHEDULE_CRASH:
+ throw new RemoteServiceException((String)msg.obj);
}
}
@@ -2155,11 +1048,11 @@ public final class ActivityThread {
private final class Idler implements MessageQueue.IdleHandler {
public final boolean queueIdle() {
- ActivityRecord a = mNewActivities;
+ ActivityClientRecord a = mNewActivities;
if (a != null) {
mNewActivities = null;
IActivityManager am = ActivityManagerNative.getDefault();
- ActivityRecord prev;
+ ActivityClientRecord prev;
do {
if (localLOGV) Slog.v(
TAG, "Reporting idle of " + a +
@@ -2215,71 +1108,132 @@ public final class ActivityThread {
}
}
- static IPackageManager sPackageManager;
+ public static final ActivityThread currentActivityThread() {
+ return sThreadLocal.get();
+ }
- final ApplicationThread mAppThread = new ApplicationThread();
- final Looper mLooper = Looper.myLooper();
- final H mH = new H();
- final HashMap<IBinder, ActivityRecord> mActivities
- = new HashMap<IBinder, ActivityRecord>();
- // List of new activities (via ActivityRecord.nextIdle) that should
- // be reported when next we idle.
- ActivityRecord mNewActivities = null;
- // Number of activities that are currently visible on-screen.
- int mNumVisibleActivities = 0;
- final HashMap<IBinder, Service> mServices
- = new HashMap<IBinder, Service>();
- AppBindData mBoundApplication;
- Configuration mConfiguration;
- Configuration mResConfiguration;
- Application mInitialApplication;
- final ArrayList<Application> mAllApplications
- = new ArrayList<Application>();
- // set of instantiated backup agents, keyed by package name
- final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
- static final ThreadLocal sThreadLocal = new ThreadLocal();
- Instrumentation mInstrumentation;
- String mInstrumentationAppDir = null;
- String mInstrumentationAppPackage = null;
- String mInstrumentedAppDir = null;
- boolean mSystemThread = false;
- boolean mJitEnabled = false;
+ public static final String currentPackageName() {
+ ActivityThread am = currentActivityThread();
+ return (am != null && am.mBoundApplication != null)
+ ? am.mBoundApplication.processName : null;
+ }
- // These can be accessed by multiple threads; mPackages is the lock.
- // XXX For now we keep around information about all packages we have
- // seen, not removing entries from this map.
- final HashMap<String, WeakReference<PackageInfo>> mPackages
- = new HashMap<String, WeakReference<PackageInfo>>();
- final HashMap<String, WeakReference<PackageInfo>> mResourcePackages
- = new HashMap<String, WeakReference<PackageInfo>>();
- Display mDisplay = null;
- DisplayMetrics mDisplayMetrics = null;
- final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources
- = new HashMap<ResourcesKey, WeakReference<Resources> >();
- final ArrayList<ActivityRecord> mRelaunchingActivities
- = new ArrayList<ActivityRecord>();
- Configuration mPendingConfiguration = null;
+ public static final Application currentApplication() {
+ ActivityThread am = currentActivityThread();
+ return am != null ? am.mInitialApplication : null;
+ }
- // The lock of mProviderMap protects the following variables.
- final HashMap<String, ProviderRecord> mProviderMap
- = new HashMap<String, ProviderRecord>();
- final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap
- = new HashMap<IBinder, ProviderRefCount>();
- final HashMap<IBinder, ProviderRecord> mLocalProviders
- = new HashMap<IBinder, ProviderRecord>();
+ public static IPackageManager getPackageManager() {
+ if (sPackageManager != null) {
+ //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
+ return sPackageManager;
+ }
+ IBinder b = ServiceManager.getService("package");
+ //Slog.v("PackageManager", "default service binder = " + b);
+ sPackageManager = IPackageManager.Stub.asInterface(b);
+ //Slog.v("PackageManager", "default service = " + sPackageManager);
+ return sPackageManager;
+ }
- final GcIdler mGcIdler = new GcIdler();
- boolean mGcIdlerScheduled = false;
+ DisplayMetrics getDisplayMetricsLocked(boolean forceUpdate) {
+ if (mDisplayMetrics != null && !forceUpdate) {
+ return mDisplayMetrics;
+ }
+ if (mDisplay == null) {
+ WindowManager wm = WindowManagerImpl.getDefault();
+ mDisplay = wm.getDefaultDisplay();
+ }
+ DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics();
+ mDisplay.getMetrics(metrics);
+ //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
+ // + metrics.heightPixels + " den=" + metrics.density
+ // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
+ return metrics;
+ }
- public final PackageInfo getPackageInfo(String packageName, int flags) {
+ /**
+ * Creates the top level Resources for applications with the given compatibility info.
+ *
+ * @param resDir the resource directory.
+ * @param compInfo the compability info. It will use the default compatibility info when it's
+ * null.
+ */
+ Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
+ ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
+ Resources r;
synchronized (mPackages) {
- WeakReference<PackageInfo> ref;
+ // Resources is app scale dependent.
+ if (false) {
+ Slog.w(TAG, "getTopLevelResources: " + resDir + " / "
+ + compInfo.applicationScale);
+ }
+ WeakReference<Resources> wr = mActiveResources.get(key);
+ r = wr != null ? wr.get() : null;
+ //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
+ if (r != null && r.getAssets().isUpToDate()) {
+ if (false) {
+ Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ + ": appScale=" + r.getCompatibilityInfo().applicationScale);
+ }
+ return r;
+ }
+ }
+
+ //if (r != null) {
+ // Slog.w(TAG, "Throwing away out-of-date resources!!!! "
+ // + r + " " + resDir);
+ //}
+
+ AssetManager assets = new AssetManager();
+ if (assets.addAssetPath(resDir) == 0) {
+ return null;
+ }
+
+ //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
+ DisplayMetrics metrics = getDisplayMetricsLocked(false);
+ r = new Resources(assets, metrics, getConfiguration(), compInfo);
+ if (false) {
+ Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ + r.getConfiguration() + " appScale="
+ + r.getCompatibilityInfo().applicationScale);
+ }
+
+ synchronized (mPackages) {
+ WeakReference<Resources> wr = mActiveResources.get(key);
+ Resources existing = wr != null ? wr.get() : null;
+ if (existing != null && existing.getAssets().isUpToDate()) {
+ // Someone else already created the resources while we were
+ // unlocked; go ahead and use theirs.
+ r.getAssets().close();
+ return existing;
+ }
+
+ // XXX need to remove entries when weak references go away
+ mActiveResources.put(key, new WeakReference<Resources>(r));
+ return r;
+ }
+ }
+
+ /**
+ * Creates the top level resources for the given package.
+ */
+ Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) {
+ return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo);
+ }
+
+ final Handler getHandler() {
+ return mH;
+ }
+
+ public final LoadedApk getPackageInfo(String packageName, int flags) {
+ synchronized (mPackages) {
+ WeakReference<LoadedApk> ref;
if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) {
ref = mPackages.get(packageName);
} else {
ref = mResourcePackages.get(packageName);
}
- PackageInfo packageInfo = ref != null ? ref.get() : null;
+ LoadedApk packageInfo = ref != null ? ref.get() : null;
//Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo);
//if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir
// + ": " + packageInfo.mResources.getAssets().isUpToDate());
@@ -2311,7 +1265,7 @@ public final class ActivityThread {
return null;
}
- public final PackageInfo getPackageInfo(ApplicationInfo ai, int flags) {
+ public final LoadedApk getPackageInfo(ApplicationInfo ai, int flags) {
boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
boolean securityViolation = includeCode && ai.uid != 0
&& ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
@@ -2333,20 +1287,20 @@ public final class ActivityThread {
return getPackageInfo(ai, null, securityViolation, includeCode);
}
- public final PackageInfo getPackageInfoNoCheck(ApplicationInfo ai) {
+ public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) {
return getPackageInfo(ai, null, false, true);
}
- private final PackageInfo getPackageInfo(ApplicationInfo aInfo,
+ private final LoadedApk getPackageInfo(ApplicationInfo aInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode) {
synchronized (mPackages) {
- WeakReference<PackageInfo> ref;
+ WeakReference<LoadedApk> ref;
if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
- PackageInfo packageInfo = ref != null ? ref.get() : null;
+ LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
@@ -2355,15 +1309,15 @@ public final class ActivityThread {
? mBoundApplication.processName : null)
+ ")");
packageInfo =
- new PackageInfo(this, aInfo, this, baseLoader,
+ new LoadedApk(this, aInfo, this, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0);
if (includeCode) {
mPackages.put(aInfo.packageName,
- new WeakReference<PackageInfo>(packageInfo));
+ new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
- new WeakReference<PackageInfo>(packageInfo));
+ new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
@@ -2412,7 +1366,7 @@ public final class ActivityThread {
if (mSystemContext == null) {
ContextImpl context =
ContextImpl.createSystemContext(this);
- PackageInfo info = new PackageInfo(this, "android", context, null);
+ LoadedApk info = new LoadedApk(this, "android", context, null);
context.init(info, null, this);
context.getResources().updateConfiguration(
getConfiguration(), getDisplayMetricsLocked(false));
@@ -2427,7 +1381,7 @@ public final class ActivityThread {
public void installSystemApplicationInfo(ApplicationInfo info) {
synchronized (this) {
ContextImpl context = getSystemContext();
- context.init(new PackageInfo(this, "android", context, info), null, this);
+ context.init(new LoadedApk(this, "android", context, info), null, this);
}
}
@@ -2479,7 +1433,7 @@ public final class ActivityThread {
public final Activity startActivityNow(Activity parent, String id,
Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
Object lastNonConfigurationInstance) {
- ActivityRecord r = new ActivityRecord();
+ ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = 0;
r.intent = intent;
@@ -2550,7 +1504,7 @@ public final class ActivityThread {
queueOrSendMessage(H.CLEAN_UP_CONTEXT, cci);
}
- private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) {
+ private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
@@ -2669,7 +1623,7 @@ public final class ActivityThread {
return activity;
}
- private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) {
+ private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
@@ -2729,7 +1683,7 @@ public final class ActivityThread {
}
}
- private final void deliverNewIntents(ActivityRecord r,
+ private final void deliverNewIntents(ActivityClientRecord r,
List<Intent> intents) {
final int N = intents.size();
for (int i=0; i<N; i++) {
@@ -2741,7 +1695,7 @@ public final class ActivityThread {
public final void performNewIntents(IBinder token,
List<Intent> intents) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
if (r != null) {
final boolean resumed = !r.paused;
if (resumed) {
@@ -2765,7 +1719,7 @@ public final class ActivityThread {
String component = data.intent.getComponent().getClassName();
- PackageInfo packageInfo = getPackageInfoNoCheck(
+ LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo);
IActivityManager mgr = ActivityManagerNative.getDefault();
@@ -2824,6 +1778,8 @@ public final class ActivityThread {
}
}
+ QueuedWork.waitToFinish();
+
try {
if (data.sync) {
if (DEBUG_BROADCAST) Slog.i(TAG,
@@ -2849,7 +1805,7 @@ public final class ActivityThread {
unscheduleGcIdler();
// instantiate the BackupAgent class named in the manifest
- PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
String packageName = packageInfo.mPackageName;
if (mBackupAgents.get(packageName) != null) {
Slog.d(TAG, "BackupAgent " + " for " + packageName
@@ -2911,7 +1867,7 @@ public final class ActivityThread {
private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
- PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo);
String packageName = packageInfo.mPackageName;
BackupAgent agent = mBackupAgents.get(packageName);
if (agent != null) {
@@ -2932,7 +1888,7 @@ public final class ActivityThread {
// we are back active so skip it.
unscheduleGcIdler();
- PackageInfo packageInfo = getPackageInfoNoCheck(
+ LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo);
Service service = null;
try {
@@ -3051,6 +2007,9 @@ public final class ActivityThread {
data.args.setExtrasClassLoader(s.getClassLoader());
}
int res = s.onStartCommand(data.args, data.flags, data.startId);
+
+ QueuedWork.waitToFinish();
+
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 1, data.startId, res);
@@ -3079,6 +2038,9 @@ public final class ActivityThread {
final String who = s.getClassName();
((ContextImpl) context).scheduleFinalCleanup(who, "Service");
}
+
+ QueuedWork.waitToFinish();
+
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
token, 0, 0, 0);
@@ -3096,9 +2058,9 @@ public final class ActivityThread {
//Slog.i(TAG, "Running services: " + mServices);
}
- public final ActivityRecord performResumeActivity(IBinder token,
+ public final ActivityClientRecord performResumeActivity(IBinder token,
boolean clearHide) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
if (localLOGV) Slog.v(TAG, "Performing resume of " + r
+ " finished=" + r.activity.mFinished);
if (r != null && !r.activity.mFinished) {
@@ -3140,7 +2102,7 @@ public final class ActivityThread {
// we are back active so skip it.
unscheduleGcIdler();
- ActivityRecord r = performResumeActivity(token, clearHide);
+ ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
@@ -3239,7 +2201,7 @@ public final class ActivityThread {
private int mThumbnailWidth = -1;
private int mThumbnailHeight = -1;
- private final Bitmap createThumbnailBitmap(ActivityRecord r) {
+ private final Bitmap createThumbnailBitmap(ActivityClientRecord r) {
Bitmap thumbnail = null;
try {
int w = mThumbnailWidth;
@@ -3255,13 +2217,24 @@ public final class ActivityThread {
h = mThumbnailHeight;
}
- // XXX Only set hasAlpha if needed?
- thumbnail = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
- thumbnail.eraseColor(0);
- Canvas cv = new Canvas(thumbnail);
- if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
- thumbnail = null;
+ // On platforms where we don't want thumbnails, set dims to (0,0)
+ if ((w > 0) && (h > 0)) {
+ View topView = r.activity.getWindow().getDecorView();
+
+ // Maximize bitmap by capturing in native aspect.
+ if (topView.getWidth() >= topView.getHeight()) {
+ thumbnail = Bitmap.createBitmap(w, h, THUMBNAIL_FORMAT);
+ } else {
+ thumbnail = Bitmap.createBitmap(h, w, THUMBNAIL_FORMAT);
+ }
+
+ thumbnail.eraseColor(0);
+ Canvas cv = new Canvas(thumbnail);
+ if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
+ thumbnail = null;
+ }
}
+
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
@@ -3277,7 +2250,7 @@ public final class ActivityThread {
private final void handlePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
if (r != null) {
//Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r);
if (userLeaving) {
@@ -3295,17 +2268,17 @@ public final class ActivityThread {
}
}
- final void performUserLeavingActivity(ActivityRecord r) {
+ final void performUserLeavingActivity(ActivityClientRecord r) {
mInstrumentation.callActivityOnUserLeaving(r.activity);
}
final Bundle performPauseActivity(IBinder token, boolean finished,
boolean saveState) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
return r != null ? performPauseActivity(r, finished, saveState) : null;
}
- final Bundle performPauseActivity(ActivityRecord r, boolean finished,
+ final Bundle performPauseActivity(ActivityClientRecord r, boolean finished,
boolean saveState) {
if (r.paused) {
if (r.activity.mFinished) {
@@ -3356,7 +2329,7 @@ public final class ActivityThread {
}
final void performStopActivity(IBinder token) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
performStopActivityInner(r, null, false);
}
@@ -3372,7 +2345,7 @@ public final class ActivityThread {
}
}
- private final void performStopActivityInner(ActivityRecord r,
+ private final void performStopActivityInner(ActivityClientRecord r,
StopInfo info, boolean keepShown) {
if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
if (r != null) {
@@ -3392,7 +2365,7 @@ public final class ActivityThread {
if (info != null) {
try {
// First create a thumbnail for the activity...
- //info.thumbnail = createThumbnailBitmap(r);
+ info.thumbnail = createThumbnailBitmap(r);
info.description = r.activity.onCreateDescription();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
@@ -3423,7 +2396,7 @@ public final class ActivityThread {
}
}
- private final void updateVisibility(ActivityRecord r, boolean show) {
+ private final void updateVisibility(ActivityClientRecord r, boolean show) {
View v = r.activity.mDecor;
if (v != null) {
if (show) {
@@ -3451,7 +2424,7 @@ public final class ActivityThread {
}
private final void handleStopActivity(IBinder token, boolean show, int configChanges) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
StopInfo info = new StopInfo();
@@ -3472,7 +2445,7 @@ public final class ActivityThread {
}
final void performRestartActivity(IBinder token) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
if (r.stopped) {
r.activity.performRestart();
r.stopped = false;
@@ -3480,7 +2453,7 @@ public final class ActivityThread {
}
private final void handleWindowVisibility(IBinder token, boolean show) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
if (!show && !r.stopped) {
performStopActivityInner(r, null, show);
} else if (show && r.stopped) {
@@ -3498,7 +2471,7 @@ public final class ActivityThread {
}
}
- private final void deliverResults(ActivityRecord r, List<ResultInfo> results) {
+ private final void deliverResults(ActivityClientRecord r, List<ResultInfo> results) {
final int N = results.size();
for (int i=0; i<N; i++) {
ResultInfo ri = results.get(i);
@@ -3522,7 +2495,7 @@ public final class ActivityThread {
}
private final void handleSendResult(ResultData res) {
- ActivityRecord r = mActivities.get(res.token);
+ ActivityClientRecord r = mActivities.get(res.token);
if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
if (r != null) {
final boolean resumed = !r.paused;
@@ -3561,13 +2534,13 @@ public final class ActivityThread {
}
}
- public final ActivityRecord performDestroyActivity(IBinder token, boolean finishing) {
+ public final ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing) {
return performDestroyActivity(token, finishing, 0, false);
}
- private final ActivityRecord performDestroyActivity(IBinder token, boolean finishing,
+ private final ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
if (r != null) {
r.activity.mConfigChangeFlags |= configChanges;
@@ -3670,7 +2643,7 @@ public final class ActivityThread {
private final void handleDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
- ActivityRecord r = performDestroyActivity(token, finishing,
+ ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance);
if (r != null) {
WindowManager wm = r.activity.getWindowManager();
@@ -3711,7 +2684,7 @@ public final class ActivityThread {
}
}
- private final void handleRelaunchActivity(ActivityRecord tmp, int configChanges) {
+ private final void handleRelaunchActivity(ActivityClientRecord tmp, int configChanges) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
@@ -3730,7 +2703,7 @@ public final class ActivityThread {
IBinder token = tmp.token;
tmp = null;
for (int i=0; i<N; i++) {
- ActivityRecord r = mRelaunchingActivities.get(i);
+ ActivityClientRecord r = mRelaunchingActivities.get(i);
if (r.token == token) {
tmp = r;
mRelaunchingActivities.remove(i);
@@ -3772,7 +2745,7 @@ public final class ActivityThread {
handleConfigurationChanged(changedConfig);
}
- ActivityRecord r = mActivities.get(tmp.token);
+ ActivityClientRecord r = mActivities.get(tmp.token);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
if (r == null) {
return;
@@ -3816,7 +2789,7 @@ public final class ActivityThread {
}
private final void handleRequestThumbnail(IBinder token) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
Bitmap thumbnail = createThumbnailBitmap(r);
CharSequence description = null;
try {
@@ -3843,9 +2816,9 @@ public final class ActivityThread {
= new ArrayList<ComponentCallbacks>();
if (mActivities.size() > 0) {
- Iterator<ActivityRecord> it = mActivities.values().iterator();
+ Iterator<ActivityClientRecord> it = mActivities.values().iterator();
while (it.hasNext()) {
- ActivityRecord ar = it.next();
+ ActivityClientRecord ar = it.next();
Activity a = ar.activity;
if (a != null) {
if (!ar.activity.mFinished && (allActivities ||
@@ -3874,7 +2847,7 @@ public final class ActivityThread {
}
synchronized (mProviderMap) {
if (mLocalProviders.size() > 0) {
- Iterator<ProviderRecord> it = mLocalProviders.values().iterator();
+ Iterator<ProviderClientRecord> it = mLocalProviders.values().iterator();
while (it.hasNext()) {
callbacks.add(it.next().mLocalProvider);
}
@@ -4020,7 +2993,7 @@ public final class ActivityThread {
}
final void handleActivityConfigurationChanged(IBinder token) {
- ActivityRecord r = mActivities.get(token);
+ ActivityClientRecord r = mActivities.get(token);
if (r == null || r.activity == null) {
return;
}
@@ -4057,7 +3030,7 @@ public final class ActivityThread {
for (int i=packages.length-1; i>=0; i--) {
//Slog.i(TAG, "Cleaning old package: " + packages[i]);
if (!hasPkgInfo) {
- WeakReference<PackageInfo> ref;
+ WeakReference<LoadedApk> ref;
ref = mPackages.get(packages[i]);
if (ref != null && ref.get() != null) {
hasPkgInfo = true;
@@ -4132,6 +3105,23 @@ public final class ActivityThread {
data.info = getPackageInfoNoCheck(data.appInfo);
/**
+ * For system applications on userdebug/eng builds, log stack
+ * traces of disk and network access to dropbox for analysis.
+ *
+ * Similar logic exists in SystemServer.java.
+ */
+ if ((data.appInfo.flags &
+ (ApplicationInfo.FLAG_SYSTEM |
+ ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0 &&
+ !"user".equals(Build.TYPE)) {
+ StrictMode.setThreadPolicy(
+ StrictMode.DISALLOW_DISK_WRITE |
+ StrictMode.DISALLOW_DISK_READ |
+ StrictMode.DISALLOW_NETWORK |
+ StrictMode.PENALTY_DROPBOX);
+ }
+
+ /**
* Switch this process to density compatibility mode if needed.
*/
if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
@@ -4189,7 +3179,8 @@ public final class ActivityThread {
instrApp.sourceDir = ii.sourceDir;
instrApp.publicSourceDir = ii.publicSourceDir;
instrApp.dataDir = ii.dataDir;
- PackageInfo pi = getPackageInfo(instrApp,
+ instrApp.nativeLibraryDir = ii.nativeLibraryDir;
+ LoadedApk pi = getPackageInfo(instrApp,
appContext.getClassLoader(), false, true);
ContextImpl instrContext = new ContextImpl();
instrContext.init(pi, null, this);
@@ -4300,7 +3291,7 @@ public final class ActivityThread {
private final IContentProvider getProvider(Context context, String name) {
synchronized(mProviderMap) {
- final ProviderRecord pr = mProviderMap.get(name);
+ final ProviderClientRecord pr = mProviderMap.get(name);
if (pr != null) {
return pr.mProvider;
}
@@ -4316,10 +3307,6 @@ public final class ActivityThread {
Slog.e(TAG, "Failed to find provider info for " + name);
return null;
}
- if (holder.permissionFailure != null) {
- throw new SecurityException("Permission " + holder.permissionFailure
- + " required for provider " + name);
- }
IContentProvider prov = installProvider(context, holder.provider,
holder.info, true);
@@ -4411,9 +3398,9 @@ public final class ActivityThread {
String name = null;
// remove the provider from mProviderMap
- Iterator<ProviderRecord> iter = mProviderMap.values().iterator();
+ Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator();
while (iter.hasNext()) {
- ProviderRecord pr = iter.next();
+ ProviderClientRecord pr = iter.next();
IBinder myBinder = pr.mProvider.asBinder();
if (myBinder == providerBinder) {
//find if its published by this process itself
@@ -4438,10 +3425,10 @@ public final class ActivityThread {
final void removeDeadProvider(String name, IContentProvider provider) {
synchronized(mProviderMap) {
- ProviderRecord pr = mProviderMap.get(name);
+ ProviderClientRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
Slog.i(TAG, "Removing dead content provider: " + name);
- ProviderRecord removed = mProviderMap.remove(name);
+ ProviderClientRecord removed = mProviderMap.remove(name);
if (removed != null) {
removed.mProvider.asBinder().unlinkToDeath(removed, 0);
}
@@ -4450,10 +3437,10 @@ public final class ActivityThread {
}
final void removeDeadProviderLocked(String name, IContentProvider provider) {
- ProviderRecord pr = mProviderMap.get(name);
+ ProviderClientRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
Slog.i(TAG, "Removing dead content provider: " + name);
- ProviderRecord removed = mProviderMap.remove(name);
+ ProviderClientRecord removed = mProviderMap.remove(name);
if (removed != null) {
removed.mProvider.asBinder().unlinkToDeath(removed, 0);
}
@@ -4521,7 +3508,7 @@ public final class ActivityThread {
// Cache the pointer for the remote provider.
String names[] = PATTERN_SEMICOLON.split(info.authority);
for (int i=0; i<names.length; i++) {
- ProviderRecord pr = new ProviderRecord(names[i], provider,
+ ProviderClientRecord pr = new ProviderClientRecord(names[i], provider,
localProvider);
try {
provider.asBinder().linkToDeath(pr, 0);
@@ -4532,7 +3519,7 @@ public final class ActivityThread {
}
if (localProvider != null) {
mLocalProviders.put(provider.asBinder(),
- new ProviderRecord(null, provider, localProvider));
+ new ProviderClientRecord(null, provider, localProvider));
}
}
@@ -4620,6 +3607,9 @@ public final class ActivityThread {
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
+ if (sMainThreadHandler == null) {
+ sMainThreadHandler = new Handler();
+ }
ActivityThread thread = new ActivityThread();
thread.attach(false);
diff --git a/core/java/android/app/AppGlobals.java b/core/java/android/app/AppGlobals.java
new file mode 100644
index 000000000000..9a391296bbdf
--- /dev/null
+++ b/core/java/android/app/AppGlobals.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.pm.IPackageManager;
+
+/**
+ * Special private access for certain globals related to a process.
+ * @hide
+ */
+public class AppGlobals {
+ /**
+ * Return the first Application object made in the process.
+ * NOTE: Only works on the main thread.
+ */
+ public static Application getInitialApplication() {
+ return ActivityThread.currentApplication();
+ }
+
+ /**
+ * Return the package name of the first .apk loaded into the process.
+ * NOTE: Only works on the main thread.
+ */
+ public static String getInitialPackage() {
+ return ActivityThread.currentPackageName();
+ }
+
+ /**
+ * Return the raw interface to the package manager.
+ * @return
+ */
+ public static IPackageManager getPackageManager() {
+ return ActivityThread.getPackageManager();
+ }
+}
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index f0cef98751af..8f940d5d12b9 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -24,9 +24,11 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Printer;
+
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -52,7 +54,6 @@ public class ApplicationErrorReport implements Parcelable {
// System property defining default error report receiver
static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default";
-
/**
* Uninitialized error report.
*/
@@ -74,8 +75,15 @@ public class ApplicationErrorReport implements Parcelable {
public static final int TYPE_BATTERY = 3;
/**
+ * A report from a user to a developer about a running service that the
+ * user doesn't think should be running.
+ */
+ public static final int TYPE_RUNNING_SERVICE = 5;
+
+ /**
* Type of this report. Can be one of {@link #TYPE_NONE},
- * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, or {@link #TYPE_BATTERY}.
+ * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, {@link #TYPE_BATTERY},
+ * or {@link #TYPE_RUNNING_SERVICE}.
*/
public int type;
@@ -123,7 +131,13 @@ public class ApplicationErrorReport implements Parcelable {
* of BatteryInfo; otherwise null.
*/
public BatteryInfo batteryInfo;
-
+
+ /**
+ * If this report is of type {@link #TYPE_RUNNING_SERVICE}, contains an instance
+ * of RunningServiceInfo; otherwise null.
+ */
+ public RunningServiceInfo runningServiceInfo;
+
/**
* Create an uninitialized instance of {@link ApplicationErrorReport}.
*/
@@ -218,6 +232,9 @@ public class ApplicationErrorReport implements Parcelable {
case TYPE_BATTERY:
batteryInfo.writeToParcel(dest, flags);
break;
+ case TYPE_RUNNING_SERVICE:
+ runningServiceInfo.writeToParcel(dest, flags);
+ break;
}
}
@@ -234,16 +251,25 @@ public class ApplicationErrorReport implements Parcelable {
crashInfo = new CrashInfo(in);
anrInfo = null;
batteryInfo = null;
+ runningServiceInfo = null;
break;
case TYPE_ANR:
anrInfo = new AnrInfo(in);
crashInfo = null;
batteryInfo = null;
+ runningServiceInfo = null;
break;
case TYPE_BATTERY:
batteryInfo = new BatteryInfo(in);
anrInfo = null;
crashInfo = null;
+ runningServiceInfo = null;
+ break;
+ case TYPE_RUNNING_SERVICE:
+ batteryInfo = null;
+ anrInfo = null;
+ crashInfo = null;
+ runningServiceInfo = new RunningServiceInfo(in);
break;
}
}
@@ -474,6 +500,51 @@ public class ApplicationErrorReport implements Parcelable {
}
}
+ /**
+ * Describes a running service report.
+ */
+ public static class RunningServiceInfo {
+ /**
+ * Duration in milliseconds that the service has been running.
+ */
+ public long durationMillis;
+
+ /**
+ * Dump of debug information about the service.
+ */
+ public String serviceDetails;
+
+ /**
+ * Create an uninitialized instance of RunningServiceInfo.
+ */
+ public RunningServiceInfo() {
+ }
+
+ /**
+ * Create an instance of RunningServiceInfo initialized from a Parcel.
+ */
+ public RunningServiceInfo(Parcel in) {
+ durationMillis = in.readLong();
+ serviceDetails = in.readString();
+ }
+
+ /**
+ * Save a RunningServiceInfo instance to a parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(durationMillis);
+ dest.writeString(serviceDetails);
+ }
+
+ /**
+ * Dump a BatteryInfo instance to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "durationMillis: " + durationMillis);
+ pw.println(prefix + "serviceDetails: " + serviceDetails);
+ }
+ }
+
public static final Parcelable.Creator<ApplicationErrorReport> CREATOR
= new Parcelable.Creator<ApplicationErrorReport>() {
public ApplicationErrorReport createFromParcel(Parcel source) {
diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java
index 2e301c9afd56..9e3cd7eb8b57 100644
--- a/core/java/android/app/ApplicationLoaders.java
+++ b/core/java/android/app/ApplicationLoaders.java
@@ -19,6 +19,7 @@ package android.app;
import dalvik.system.PathClassLoader;
import java.util.HashMap;
+import java.util.Map;
class ApplicationLoaders
{
@@ -27,8 +28,7 @@ class ApplicationLoaders
return gApplicationLoaders;
}
- public ClassLoader getClassLoader(String zip, String appDataDir,
- ClassLoader parent)
+ public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
{
/*
* This is the parent we use if they pass "null" in. In theory
@@ -49,13 +49,13 @@ class ApplicationLoaders
* new ClassLoader for the zip archive.
*/
if (parent == baseParent) {
- ClassLoader loader = (ClassLoader)mLoaders.get(zip);
+ ClassLoader loader = mLoaders.get(zip);
if (loader != null) {
return loader;
}
PathClassLoader pathClassloader =
- new PathClassLoader(zip, appDataDir + "/lib", parent);
+ new PathClassLoader(zip, libPath, parent);
mLoaders.put(zip, pathClassloader);
return pathClassloader;
@@ -65,7 +65,7 @@ class ApplicationLoaders
}
}
- private final HashMap mLoaders = new HashMap();
+ private final Map<String, ClassLoader> mLoaders = new HashMap<String, ClassLoader>();
private static final ApplicationLoaders gApplicationLoaders
= new ApplicationLoaders();
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index da26a78f8198..1c20062f3f84 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -341,13 +341,6 @@ public abstract class ApplicationThreadNative extends Binder
return true;
}
- case REQUEST_PSS_TRANSACTION:
- {
- data.enforceInterface(IApplicationThread.descriptor);
- requestPss();
- return true;
- }
-
case PROFILER_CONTROL_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
@@ -402,6 +395,14 @@ public abstract class ApplicationThreadNative extends Binder
dispatchPackageBroadcast(cmd, packages);
return true;
}
+
+ case SCHEDULE_CRASH_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ String msg = data.readString();
+ scheduleCrash(msg);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -771,14 +772,6 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public final void requestPss() throws RemoteException {
- Parcel data = Parcel.obtain();
- data.writeInterfaceToken(IApplicationThread.descriptor);
- mRemote.transact(REQUEST_PSS_TRANSACTION, data, null,
- IBinder.FLAG_ONEWAY);
- data.recycle();
- }
-
public void profilerControl(boolean start, String path,
ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
@@ -826,5 +819,15 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+
+ public void scheduleCrash(String msg) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeString(msg);
+ mRemote.transact(SCHEDULE_CRASH_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ data.recycle();
+
+ }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f471f579e482..09ef71024ad4 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -67,6 +67,7 @@ import android.location.LocationManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.DownloadManager;
import android.net.ThrottleManager;
import android.net.IThrottleManager;
import android.net.Uri;
@@ -118,9 +119,11 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;
-import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
@@ -169,11 +172,11 @@ class ContextImpl extends Context {
private static ThrottleManager sThrottleManager;
private static WifiManager sWifiManager;
private static LocationManager sLocationManager;
- private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
- new HashMap<File, SharedPreferencesImpl>();
+ private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs =
+ new HashMap<String, SharedPreferencesImpl>();
private AudioManager mAudioManager;
- /*package*/ ActivityThread.PackageInfo mPackageInfo;
+ /*package*/ LoadedApk mPackageInfo;
private Resources mResources;
/*package*/ ActivityThread mMainThread;
private Context mOuterContext;
@@ -199,6 +202,7 @@ class ContextImpl extends Context {
private DropBoxManager mDropBoxManager = null;
private DevicePolicyManager mDevicePolicyManager = null;
private UiModeManager mUiModeManager = null;
+ private DownloadManager mDownloadManager = null;
private final Object mSync = new Object();
@@ -208,7 +212,7 @@ class ContextImpl extends Context {
private File mCacheDir;
private File mExternalFilesDir;
private File mExternalCacheDir;
-
+
private static long sInstanceCount = 0;
private static final String[] EMPTY_FILE_LIST = {};
@@ -321,7 +325,7 @@ class ContextImpl extends Context {
}
throw new RuntimeException("Not supported in system context");
}
-
+
private static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
@@ -333,55 +337,54 @@ class ContextImpl extends Context {
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
- File f = getSharedPrefsFile(name);
+ File prefsFile;
+ boolean needInitialLoad = false;
synchronized (sSharedPrefs) {
- sp = sSharedPrefs.get(f);
- if (sp != null && !sp.hasFileChanged()) {
- //Log.i(TAG, "Returning existing prefs " + name + ": " + sp);
+ sp = sSharedPrefs.get(name);
+ if (sp != null && !sp.hasFileChangedUnexpectedly()) {
return sp;
}
- }
-
- FileInputStream str = null;
- File backup = makeBackupFile(f);
- if (backup.exists()) {
- f.delete();
- backup.renameTo(f);
+ prefsFile = getSharedPrefsFile(name);
+ if (sp == null) {
+ sp = new SharedPreferencesImpl(prefsFile, mode, null);
+ sSharedPrefs.put(name, sp);
+ needInitialLoad = true;
+ }
}
- // Debugging
- if (f.exists() && !f.canRead()) {
- Log.w(TAG, "Attempt to read preferences file " + f + " without permission");
- }
-
- Map map = null;
- if (f.exists() && f.canRead()) {
- try {
- str = new FileInputStream(f);
- map = XmlUtils.readMapXml(str);
- str.close();
- } catch (org.xmlpull.v1.XmlPullParserException e) {
- Log.w(TAG, "getSharedPreferences", e);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "getSharedPreferences", e);
- } catch (IOException e) {
- Log.w(TAG, "getSharedPreferences", e);
+ synchronized (sp) {
+ if (needInitialLoad && sp.isLoaded()) {
+ // lost the race to load; another thread handled it
+ return sp;
+ }
+ File backup = makeBackupFile(prefsFile);
+ if (backup.exists()) {
+ prefsFile.delete();
+ backup.renameTo(prefsFile);
}
- }
- synchronized (sSharedPrefs) {
- if (sp != null) {
- //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map);
- sp.replace(map);
- } else {
- sp = sSharedPrefs.get(f);
- if (sp == null) {
- sp = new SharedPreferencesImpl(f, mode, map);
- sSharedPrefs.put(f, sp);
+ // Debugging
+ if (prefsFile.exists() && !prefsFile.canRead()) {
+ Log.w(TAG, "Attempt to read preferences file " + prefsFile + " without permission");
+ }
+
+ Map map = null;
+ if (prefsFile.exists() && prefsFile.canRead()) {
+ try {
+ FileInputStream str = new FileInputStream(prefsFile);
+ map = XmlUtils.readMapXml(str);
+ str.close();
+ } catch (org.xmlpull.v1.XmlPullParserException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (IOException e) {
+ Log.w(TAG, "getSharedPreferences", e);
}
}
- return sp;
+ sp.replace(map);
}
+ return sp;
}
private File getPreferencesDir() {
@@ -696,7 +699,7 @@ class ContextImpl extends Context {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = new ActivityThread.PackageInfo.ReceiverDispatcher(
+ rd = new LoadedApk.ReceiverDispatcher(
resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
}
}
@@ -739,7 +742,7 @@ class ContextImpl extends Context {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = new ActivityThread.PackageInfo.ReceiverDispatcher(
+ rd = new LoadedApk.ReceiverDispatcher(
resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver();
}
}
@@ -795,7 +798,7 @@ class ContextImpl extends Context {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
- rd = new ActivityThread.PackageInfo.ReceiverDispatcher(
+ rd = new LoadedApk.ReceiverDispatcher(
receiver, context, scheduler, null, true).getIIntentReceiver();
}
}
@@ -973,6 +976,8 @@ class ContextImpl extends Context {
return getDevicePolicyManager();
} else if (UI_MODE_SERVICE.equals(name)) {
return getUiModeManager();
+ } else if (DOWNLOAD_SERVICE.equals(name)) {
+ return getDownloadManager();
}
return null;
@@ -1157,12 +1162,16 @@ class ContextImpl extends Context {
return mAudioManager;
}
+ /* package */ static DropBoxManager createDropBoxManager() {
+ IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
+ IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
+ return new DropBoxManager(service);
+ }
+
private DropBoxManager getDropBoxManager() {
synchronized (mSync) {
if (mDropBoxManager == null) {
- IBinder b = ServiceManager.getService(DROPBOX_SERVICE);
- IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b);
- mDropBoxManager = new DropBoxManager(service);
+ mDropBoxManager = createDropBoxManager();
}
}
return mDropBoxManager;
@@ -1187,6 +1196,15 @@ class ContextImpl extends Context {
return mUiModeManager;
}
+ private DownloadManager getDownloadManager() {
+ synchronized (mSync) {
+ if (mDownloadManager == null) {
+ mDownloadManager = new DownloadManager(getContentResolver(), getPackageName());
+ }
+ }
+ return mDownloadManager;
+ }
+
@Override
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
@@ -1421,7 +1439,7 @@ class ContextImpl extends Context {
return new ContextImpl(mMainThread.getSystemContext());
}
- ActivityThread.PackageInfo pi =
+ LoadedApk pi =
mMainThread.getPackageInfo(packageName, flags);
if (pi != null) {
ContextImpl c = new ContextImpl();
@@ -1488,12 +1506,12 @@ class ContextImpl extends Context {
mOuterContext = this;
}
- final void init(ActivityThread.PackageInfo packageInfo,
+ final void init(LoadedApk packageInfo,
IBinder activityToken, ActivityThread mainThread) {
init(packageInfo, activityToken, mainThread, null);
}
- final void init(ActivityThread.PackageInfo packageInfo,
+ final void init(LoadedApk packageInfo,
IBinder activityToken, ActivityThread mainThread,
Resources container) {
mPackageInfo = packageInfo;
@@ -1826,6 +1844,21 @@ class ContextImpl extends Context {
}
@Override
+ public ProviderInfo getProviderInfo(ComponentName className, int flags)
+ throws NameNotFoundException {
+ try {
+ ProviderInfo pi = mPM.getProviderInfo(className, flags);
+ if (pi != null) {
+ return pi;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+
+ throw new NameNotFoundException(className.toString());
+ }
+
+ @Override
public String[] getSystemSharedLibraryNames() {
try {
return mPM.getSystemSharedLibraryNames();
@@ -2173,6 +2206,39 @@ class ContextImpl extends Context {
throws NameNotFoundException {
return getApplicationIcon(getApplicationInfo(packageName, 0));
}
+
+ @Override
+ public Drawable getActivityLogo(ComponentName activityName)
+ throws NameNotFoundException {
+ return getActivityInfo(activityName, 0).loadLogo(this);
+ }
+
+ @Override
+ public Drawable getActivityLogo(Intent intent)
+ throws NameNotFoundException {
+ if (intent.getComponent() != null) {
+ return getActivityLogo(intent.getComponent());
+ }
+
+ ResolveInfo info = resolveActivity(
+ intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (info != null) {
+ return info.activityInfo.loadLogo(this);
+ }
+
+ throw new NameNotFoundException(intent.toUri(0));
+ }
+
+ @Override
+ public Drawable getApplicationLogo(ApplicationInfo info) {
+ return info.loadLogo(this);
+ }
+
+ @Override
+ public Drawable getApplicationLogo(String packageName)
+ throws NameNotFoundException {
+ return getApplicationLogo(getApplicationInfo(packageName, 0));
+ }
@Override public Resources getResourcesForActivity(
ComponentName activityName) throws NameNotFoundException {
@@ -2620,6 +2686,15 @@ class ContextImpl extends Context {
return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
}
+ @Override
+ public void setPackageObbPath(String packageName, String path) {
+ try {
+ mPM.setPackageObbPath(packageName, path);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
private final ContextImpl mContext;
private final IPackageManager mPM;
@@ -2636,13 +2711,20 @@ class ContextImpl extends Context {
private static final class SharedPreferencesImpl implements SharedPreferences {
+ // Lock ordering rules:
+ // - acquire SharedPreferencesImpl.this before EditorImpl.this
+ // - acquire mWritingToDiskLock before EditorImpl.this
+
private final File mFile;
private final File mBackupFile;
private final int mMode;
- private Map mMap;
- private final FileStatus mFileStatus = new FileStatus();
- private long mTimestamp;
+ private Map<String, Object> mMap; // guarded by 'this'
+ private long mTimestamp; // guarded by 'this'
+ private int mDiskWritesInFlight = 0; // guarded by 'this'
+ private boolean mLoaded = false; // guarded by 'this'
+
+ private final Object mWritingToDiskLock = new Object();
private static final Object mContent = new Object();
private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
@@ -2651,30 +2733,50 @@ class ContextImpl extends Context {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
- mMap = initialContents != null ? initialContents : new HashMap();
- if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) {
- mTimestamp = mFileStatus.mtime;
+ mLoaded = initialContents != null;
+ mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
+ FileStatus stat = new FileStatus();
+ if (FileUtils.getFileStatus(file.getPath(), stat)) {
+ mTimestamp = stat.mtime;
}
mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
}
- public boolean hasFileChanged() {
+ // Has this SharedPreferences ever had values assigned to it?
+ boolean isLoaded() {
synchronized (this) {
- if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
- return true;
+ return mLoaded;
+ }
+ }
+
+ // Has the file changed out from under us? i.e. writes that
+ // we didn't instigate.
+ public boolean hasFileChangedUnexpectedly() {
+ synchronized (this) {
+ if (mDiskWritesInFlight > 0) {
+ // If we know we caused it, it's not unexpected.
+ Log.d(TAG, "disk write in flight, not unexpected.");
+ return false;
}
- return mTimestamp != mFileStatus.mtime;
+ }
+ FileStatus stat = new FileStatus();
+ if (!FileUtils.getFileStatus(mFile.getPath(), stat)) {
+ return true;
+ }
+ synchronized (this) {
+ return mTimestamp != stat.mtime;
}
}
-
+
public void replace(Map newContents) {
- if (newContents != null) {
- synchronized (this) {
+ synchronized (this) {
+ mLoaded = true;
+ if (newContents != null) {
mMap = newContents;
}
}
}
-
+
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(this) {
mListeners.put(listener, mContent);
@@ -2690,7 +2792,7 @@ class ContextImpl extends Context {
public Map<String, ?> getAll() {
synchronized(this) {
//noinspection unchecked
- return new HashMap(mMap);
+ return new HashMap<String, Object>(mMap);
}
}
@@ -2709,7 +2811,7 @@ class ContextImpl extends Context {
}
public long getLong(String key, long defValue) {
synchronized (this) {
- Long v = (Long) mMap.get(key);
+ Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
@@ -2732,6 +2834,25 @@ class ContextImpl extends Context {
}
}
+ public Editor edit() {
+ return new EditorImpl();
+ }
+
+ // Return value from EditorImpl#commitToMemory()
+ private static class MemoryCommitResult {
+ public boolean changesMade; // any keys different?
+ public List<String> keysModified; // may be null
+ public Set<OnSharedPreferenceChangeListener> listeners; // may be null
+ public Map<?, ?> mapToWriteToDisk;
+ public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
+ public volatile boolean writeToDiskResult = false;
+
+ public void setDiskWriteResult(boolean result) {
+ writeToDiskResult = result;
+ writtenToDiskLatch.countDown();
+ }
+ }
+
public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;
@@ -2781,67 +2902,188 @@ class ContextImpl extends Context {
}
}
- public boolean commit() {
- boolean returnValue;
+ public void apply() {
+ final MemoryCommitResult mcr = commitToMemory();
+ final Runnable awaitCommit = new Runnable() {
+ public void run() {
+ try {
+ mcr.writtenToDiskLatch.await();
+ } catch (InterruptedException ignored) {
+ }
+ }
+ };
+
+ QueuedWork.add(awaitCommit);
+
+ Runnable postWriteRunnable = new Runnable() {
+ public void run() {
+ awaitCommit.run();
+ QueuedWork.remove(awaitCommit);
+ }
+ };
- boolean hasListeners;
- List<String> keysModified = null;
- Set<OnSharedPreferenceChangeListener> listeners = null;
+ SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
+ // Okay to notify the listeners before it's hit disk
+ // because the listeners should always get the same
+ // SharedPreferences instance back, which has the
+ // changes reflected in memory.
+ notifyListeners(mcr);
+ }
+
+ // Returns true if any changes were made
+ private MemoryCommitResult commitToMemory() {
+ MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
- hasListeners = mListeners.size() > 0;
+ // We optimistically don't make a deep copy until
+ // a memory commit comes in when we're already
+ // writing to disk.
+ if (mDiskWritesInFlight > 0) {
+ // We can't modify our mMap as a currently
+ // in-flight write owns it. Clone it before
+ // modifying it.
+ // noinspection unchecked
+ mMap = new HashMap<String, Object>(mMap);
+ }
+ mcr.mapToWriteToDisk = mMap;
+ mDiskWritesInFlight++;
+
+ boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
- keysModified = new ArrayList<String>();
- listeners =
- new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
+ mcr.keysModified = new ArrayList<String>();
+ mcr.listeners =
+ new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (this) {
if (mClear) {
- mMap.clear();
+ if (!mMap.isEmpty()) {
+ mcr.changesMade = true;
+ mMap.clear();
+ }
mClear = false;
}
for (Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
- if (v == this) {
+ if (v == this) { // magic value for a removal mutation
+ if (!mMap.containsKey(k)) {
+ continue;
+ }
mMap.remove(k);
} else {
+ boolean isSame = false;
+ if (mMap.containsKey(k)) {
+ Object existingValue = mMap.get(k);
+ if (existingValue != null && existingValue.equals(v)) {
+ continue;
+ }
+ }
mMap.put(k, v);
}
+ mcr.changesMade = true;
if (hasListeners) {
- keysModified.add(k);
+ mcr.keysModified.add(k);
}
}
mModified.clear();
}
+ }
+ return mcr;
+ }
- returnValue = writeFileLocked();
+ public boolean commit() {
+ MemoryCommitResult mcr = commitToMemory();
+ SharedPreferencesImpl.this.enqueueDiskWrite(
+ mcr, null /* sync write on this thread okay */);
+ try {
+ mcr.writtenToDiskLatch.await();
+ } catch (InterruptedException e) {
+ return false;
}
+ notifyListeners(mcr);
+ return mcr.writeToDiskResult;
+ }
- if (hasListeners) {
- for (int i = keysModified.size() - 1; i >= 0; i--) {
- final String key = keysModified.get(i);
- for (OnSharedPreferenceChangeListener listener : listeners) {
+ private void notifyListeners(final MemoryCommitResult mcr) {
+ if (mcr.listeners == null || mcr.keysModified == null ||
+ mcr.keysModified.size() == 0) {
+ return;
+ }
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
+ final String key = mcr.keysModified.get(i);
+ for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
}
}
}
+ } else {
+ // Run this function on the main thread.
+ ActivityThread.sMainThreadHandler.post(new Runnable() {
+ public void run() {
+ notifyListeners(mcr);
+ }
+ });
}
-
- return returnValue;
}
}
- public Editor edit() {
- return new EditorImpl();
+ /**
+ * Enqueue an already-committed-to-memory result to be written
+ * to disk.
+ *
+ * They will be written to disk one-at-a-time in the order
+ * that they're enqueued.
+ *
+ * @param postWriteRunnable if non-null, we're being called
+ * from apply() and this is the runnable to run after
+ * the write proceeds. if null (from a regular commit()),
+ * then we're allowed to do this disk write on the main
+ * thread (which in addition to reducing allocations and
+ * creating a background thread, this has the advantage that
+ * we catch them in userdebug StrictMode reports to convert
+ * them where possible to apply() ...)
+ */
+ private void enqueueDiskWrite(final MemoryCommitResult mcr,
+ final Runnable postWriteRunnable) {
+ final Runnable writeToDiskRunnable = new Runnable() {
+ public void run() {
+ synchronized (mWritingToDiskLock) {
+ writeToFile(mcr);
+ }
+ synchronized (SharedPreferencesImpl.this) {
+ mDiskWritesInFlight--;
+ }
+ if (postWriteRunnable != null) {
+ postWriteRunnable.run();
+ }
+ }
+ };
+
+ final boolean isFromSyncCommit = (postWriteRunnable == null);
+
+ // Typical #commit() path with fewer allocations, doing a write on
+ // the current thread.
+ if (isFromSyncCommit) {
+ boolean wasEmpty = false;
+ synchronized (SharedPreferencesImpl.this) {
+ wasEmpty = mDiskWritesInFlight == 1;
+ }
+ if (wasEmpty) {
+ writeToDiskRunnable.run();
+ return;
+ }
+ }
+
+ QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
-
- private FileOutputStream createFileOutputStream(File file) {
+
+ private static FileOutputStream createFileOutputStream(File file) {
FileOutputStream str = null;
try {
str = new FileOutputStream(file);
@@ -2864,42 +3106,56 @@ class ContextImpl extends Context {
return str;
}
- private boolean writeFileLocked() {
+ // Note: must hold mWritingToDiskLock
+ private void writeToFile(MemoryCommitResult mcr) {
// Rename the current file so it may be used as a backup during the next read
if (mFile.exists()) {
+ if (!mcr.changesMade) {
+ // If the file already exists, but no changes were
+ // made to the underlying map, it's wasteful to
+ // re-write the file. Return as if we wrote it
+ // out.
+ mcr.setDiskWriteResult(true);
+ return;
+ }
if (!mBackupFile.exists()) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
- return false;
+ mcr.setDiskWriteResult(false);
+ return;
}
} else {
mFile.delete();
}
}
-
+
// Attempt to write the file, delete the backup and return true as atomically as
// possible. If any exception occurs, delete the new file; next time we will restore
// from the backup.
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
- return false;
+ mcr.setDiskWriteResult(false);
+ return;
}
- XmlUtils.writeMapXml(mMap, str);
+ XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
str.close();
setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
- if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) {
- mTimestamp = mFileStatus.mtime;
+ FileStatus stat = new FileStatus();
+ if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
+ synchronized (this) {
+ mTimestamp = stat.mtime;
+ }
}
-
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
- return true;
+ mcr.setDiskWriteResult(true);
+ return;
} catch (XmlPullParserException e) {
- Log.w(TAG, "writeFileLocked: Got exception:", e);
+ Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
- Log.w(TAG, "writeFileLocked: Got exception:", e);
+ Log.w(TAG, "writeToFile: Got exception:", e);
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
@@ -2907,7 +3163,7 @@ class ContextImpl extends Context {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
- return false;
+ mcr.setDiskWriteResult(false);
}
}
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 02355991a885..da8c9e566977 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -138,7 +138,7 @@ public class Dialog implements DialogInterface, Window.Callback,
public Dialog(Context context, int theme) {
mContext = new ContextThemeWrapper(
context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);
- mWindowManager = (WindowManager)context.getSystemService("window");
+ mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 31f0a6359ee1..f9bd46134ccb 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -19,10 +19,10 @@ package android.app;
import android.content.ComponentName;
import android.content.ContentProviderNative;
import android.content.IContentProvider;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.IIntentSender;
-import android.content.IIntentReceiver;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
@@ -31,14 +31,15 @@ import android.content.pm.ProviderInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Debug;
-import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
-import android.os.Parcelable;
import android.os.ParcelFileDescriptor;
-import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.StrictMode;
import java.util.List;
@@ -247,8 +248,6 @@ public interface IActivityManager extends IInterface {
public boolean killPids(int[] pids, String reason) throws RemoteException;
- public void reportPss(IApplicationThread caller, int pss) throws RemoteException;
-
// Special low-level communication with activity manager.
public void startRunning(String pkg, String cls, String action,
String data) throws RemoteException;
@@ -256,7 +255,14 @@ public interface IActivityManager extends IInterface {
ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
public boolean handleApplicationWtf(IBinder app, String tag,
ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
-
+
+ // A StrictMode violation to be handled. The violationMask is a
+ // subset of the original StrictMode policy bitmask, with only the
+ // bit violated and penalty bits to be executed by the
+ // ActivityManagerService remaining set.
+ public void handleApplicationStrictModeViolation(IBinder app, int violationMask,
+ StrictMode.ViolationInfo crashInfo) throws RemoteException;
+
/*
* This will deliver the specified signal to all the persistent processes. Currently only
* SIGUSR1 is delivered. All others are ignored.
@@ -303,6 +309,17 @@ public interface IActivityManager extends IInterface {
public boolean isUserAMonkey() throws RemoteException;
+ public void finishHeavyWeightApp() throws RemoteException;
+
+ public void crashApplication(int uid, int initialPid, String packageName,
+ String message) throws RemoteException;
+
+ public IBinder newUriPermissionOwner(String name) throws RemoteException;
+ public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
+ Uri uri, int mode) throws RemoteException;
+ public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
+ int mode) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -311,28 +328,19 @@ public interface IActivityManager extends IInterface {
/** Information you can retrieve about a particular application. */
public static class ContentProviderHolder implements Parcelable {
public final ProviderInfo info;
- public final String permissionFailure;
public IContentProvider provider;
public boolean noReleaseNeeded;
public ContentProviderHolder(ProviderInfo _info) {
info = _info;
- permissionFailure = null;
}
- public ContentProviderHolder(ProviderInfo _info,
- String _permissionFailure) {
- info = _info;
- permissionFailure = _permissionFailure;
- }
-
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
info.writeToParcel(dest, 0);
- dest.writeString(permissionFailure);
if (provider != null) {
dest.writeStrongBinder(provider.asBinder());
} else {
@@ -354,7 +362,6 @@ public interface IActivityManager extends IInterface {
private ContentProviderHolder(Parcel source) {
info = ProviderInfo.CREATOR.createFromParcel(source);
- permissionFailure = source.readString();
provider = ContentProviderNative.asInterface(
source.readStrongBinder());
noReleaseNeeded = source.readInt() != 0;
@@ -486,7 +493,7 @@ public interface IActivityManager extends IInterface {
int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
int KILL_PIDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80;
- int REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
+
int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82;
int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83;
int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
@@ -513,4 +520,13 @@ public interface IActivityManager extends IInterface {
int WILL_ACTIVITY_BE_VISIBLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+105;
int START_ACTIVITY_WITH_CONFIG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+106;
int GET_RUNNING_EXTERNAL_APPLICATIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+107;
+ int FINISH_HEAVY_WEIGHT_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+108;
+ int HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+109;
+ int IS_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+110;
+ int SET_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+111;
+ int IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+112;
+ int CRASH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+113;
+ int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+114;
+ int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115;
+ int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index c917e8126843..c8ef17f1b7a9 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -95,7 +95,6 @@ public interface IApplicationThread extends IInterface {
throws RemoteException;
void scheduleLowMemory() throws RemoteException;
void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
- void requestPss() throws RemoteException;
void profilerControl(boolean start, String path, ParcelFileDescriptor fd)
throws RemoteException;
void setSchedulingGroup(int group) throws RemoteException;
@@ -103,6 +102,7 @@ public interface IApplicationThread extends IInterface {
static final int PACKAGE_REMOVED = 0;
static final int EXTERNAL_STORAGE_UNAVAILABLE = 1;
void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException;
+ void scheduleCrash(String msg) throws RemoteException;
String descriptor = "android.app.IApplicationThread";
@@ -131,7 +131,7 @@ public interface IApplicationThread extends IInterface {
int SCHEDULE_LOW_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23;
int SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24;
int SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
- int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
+
int PROFILER_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27;
int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28;
int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29;
@@ -139,4 +139,5 @@ public interface IApplicationThread extends IInterface {
int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31;
int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
+ int SCHEDULE_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34;
}
diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java
index 0a8020765e63..4bf55184af66 100644
--- a/core/java/android/app/ListActivity.java
+++ b/core/java/android/app/ListActivity.java
@@ -18,9 +18,7 @@ package android.app;
import android.os.Bundle;
import android.os.Handler;
-import android.view.KeyEvent;
import android.view.View;
-import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
@@ -316,7 +314,7 @@ public class ListActivity extends Activity {
}
private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() {
- public void onItemClick(AdapterView parent, View v, int position, long id)
+ public void onItemClick(AdapterView<?> parent, View v, int position, long id)
{
onListItemClick((ListView)parent, v, position, id);
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
new file mode 100644
index 000000000000..0644f96e44e5
--- /dev/null
+++ b/core/java/android/app/LoadedApk.java
@@ -0,0 +1,1107 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.AndroidRuntimeException;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+
+final class IntentReceiverLeaked extends AndroidRuntimeException {
+ public IntentReceiverLeaked(String msg) {
+ super(msg);
+ }
+}
+
+final class ServiceConnectionLeaked extends AndroidRuntimeException {
+ public ServiceConnectionLeaked(String msg) {
+ super(msg);
+ }
+}
+
+/**
+ * Local state maintained about a currently loaded .apk.
+ * @hide
+ */
+final class LoadedApk {
+
+ private final ActivityThread mActivityThread;
+ private final ApplicationInfo mApplicationInfo;
+ final String mPackageName;
+ private final String mAppDir;
+ private final String mResDir;
+ private final String[] mSharedLibraries;
+ private final String mDataDir;
+ private final String mLibDir;
+ private final File mDataDirFile;
+ private final ClassLoader mBaseClassLoader;
+ private final boolean mSecurityViolation;
+ private final boolean mIncludeCode;
+ Resources mResources;
+ private ClassLoader mClassLoader;
+ private Application mApplication;
+ CompatibilityInfo mCompatibilityInfo;
+
+ private final HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mReceivers
+ = new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+ private final HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
+ = new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();
+ private final HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
+ = new HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+ private final HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
+ = new HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>>();
+
+ int mClientCount = 0;
+
+ Application getApplication() {
+ return mApplication;
+ }
+
+ public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
+ ActivityThread mainThread, ClassLoader baseLoader,
+ boolean securityViolation, boolean includeCode) {
+ mActivityThread = activityThread;
+ mApplicationInfo = aInfo;
+ mPackageName = aInfo.packageName;
+ mAppDir = aInfo.sourceDir;
+ mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir
+ : aInfo.publicSourceDir;
+ mSharedLibraries = aInfo.sharedLibraryFiles;
+ mDataDir = aInfo.dataDir;
+ mDataDirFile = mDataDir != null ? new File(mDataDir) : null;
+ mLibDir = aInfo.nativeLibraryDir;
+ mBaseClassLoader = baseLoader;
+ mSecurityViolation = securityViolation;
+ mIncludeCode = includeCode;
+ mCompatibilityInfo = new CompatibilityInfo(aInfo);
+
+ if (mAppDir == null) {
+ if (ActivityThread.mSystemContext == null) {
+ ActivityThread.mSystemContext =
+ ContextImpl.createSystemContext(mainThread);
+ ActivityThread.mSystemContext.getResources().updateConfiguration(
+ mainThread.getConfiguration(),
+ mainThread.getDisplayMetricsLocked(false));
+ //Slog.i(TAG, "Created system resources "
+ // + mSystemContext.getResources() + ": "
+ // + mSystemContext.getResources().getConfiguration());
+ }
+ mClassLoader = ActivityThread.mSystemContext.getClassLoader();
+ mResources = ActivityThread.mSystemContext.getResources();
+ }
+ }
+
+ public LoadedApk(ActivityThread activityThread, String name,
+ Context systemContext, ApplicationInfo info) {
+ mActivityThread = activityThread;
+ mApplicationInfo = info != null ? info : new ApplicationInfo();
+ mApplicationInfo.packageName = name;
+ mPackageName = name;
+ mAppDir = null;
+ mResDir = null;
+ mSharedLibraries = null;
+ mDataDir = null;
+ mDataDirFile = null;
+ mLibDir = null;
+ mBaseClassLoader = null;
+ mSecurityViolation = false;
+ mIncludeCode = true;
+ mClassLoader = systemContext.getClassLoader();
+ mResources = systemContext.getResources();
+ mCompatibilityInfo = new CompatibilityInfo(mApplicationInfo);
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
+ public boolean isSecurityViolation() {
+ return mSecurityViolation;
+ }
+
+ /**
+ * Gets the array of shared libraries that are listed as
+ * used by the given package.
+ *
+ * @param packageName the name of the package (note: not its
+ * file name)
+ * @return null-ok; the array of shared libraries, each one
+ * a fully-qualified path
+ */
+ private static String[] getLibrariesFor(String packageName) {
+ ApplicationInfo ai = null;
+ try {
+ ai = ActivityThread.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_SHARED_LIBRARY_FILES);
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+
+ if (ai == null) {
+ return null;
+ }
+
+ return ai.sharedLibraryFiles;
+ }
+
+ /**
+ * Combines two arrays (of library names) such that they are
+ * concatenated in order but are devoid of duplicates. The
+ * result is a single string with the names of the libraries
+ * separated by colons, or <code>null</code> if both lists
+ * were <code>null</code> or empty.
+ *
+ * @param list1 null-ok; the first list
+ * @param list2 null-ok; the second list
+ * @return null-ok; the combination
+ */
+ private static String combineLibs(String[] list1, String[] list2) {
+ StringBuilder result = new StringBuilder(300);
+ boolean first = true;
+
+ if (list1 != null) {
+ for (String s : list1) {
+ if (first) {
+ first = false;
+ } else {
+ result.append(':');
+ }
+ result.append(s);
+ }
+ }
+
+ // Only need to check for duplicates if list1 was non-empty.
+ boolean dupCheck = !first;
+
+ if (list2 != null) {
+ for (String s : list2) {
+ if (dupCheck && ArrayUtils.contains(list1, s)) {
+ continue;
+ }
+
+ if (first) {
+ first = false;
+ } else {
+ result.append(':');
+ }
+ result.append(s);
+ }
+ }
+
+ return result.toString();
+ }
+
+ public ClassLoader getClassLoader() {
+ synchronized (this) {
+ if (mClassLoader != null) {
+ return mClassLoader;
+ }
+
+ if (mIncludeCode && !mPackageName.equals("android")) {
+ String zip = mAppDir;
+
+ /*
+ * The following is a bit of a hack to inject
+ * instrumentation into the system: If the app
+ * being started matches one of the instrumentation names,
+ * then we combine both the "instrumentation" and
+ * "instrumented" app into the path, along with the
+ * concatenation of both apps' shared library lists.
+ */
+
+ String instrumentationAppDir =
+ mActivityThread.mInstrumentationAppDir;
+ String instrumentationAppPackage =
+ mActivityThread.mInstrumentationAppPackage;
+ String instrumentedAppDir =
+ mActivityThread.mInstrumentedAppDir;
+ String[] instrumentationLibs = null;
+
+ if (mAppDir.equals(instrumentationAppDir)
+ || mAppDir.equals(instrumentedAppDir)) {
+ zip = instrumentationAppDir + ":" + instrumentedAppDir;
+ if (! instrumentedAppDir.equals(instrumentationAppDir)) {
+ instrumentationLibs =
+ getLibrariesFor(instrumentationAppPackage);
+ }
+ }
+
+ if ((mSharedLibraries != null) ||
+ (instrumentationLibs != null)) {
+ zip =
+ combineLibs(mSharedLibraries, instrumentationLibs)
+ + ':' + zip;
+ }
+
+ /*
+ * With all the combination done (if necessary, actually
+ * create the class loader.
+ */
+
+ if (ActivityThread.localLOGV)
+ Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + mLibDir);
+
+ mClassLoader =
+ ApplicationLoaders.getDefault().getClassLoader(
+ zip, mLibDir, mBaseClassLoader);
+ initializeJavaContextClassLoader();
+ } else {
+ if (mBaseClassLoader == null) {
+ mClassLoader = ClassLoader.getSystemClassLoader();
+ } else {
+ mClassLoader = mBaseClassLoader;
+ }
+ }
+ return mClassLoader;
+ }
+ }
+
+ /**
+ * Setup value for Thread.getContextClassLoader(). If the
+ * package will not run in in a VM with other packages, we set
+ * the Java context ClassLoader to the
+ * PackageInfo.getClassLoader value. However, if this VM can
+ * contain multiple packages, we intead set the Java context
+ * ClassLoader to a proxy that will warn about the use of Java
+ * context ClassLoaders and then fall through to use the
+ * system ClassLoader.
+ *
+ * <p> Note that this is similar to but not the same as the
+ * android.content.Context.getClassLoader(). While both
+ * context class loaders are typically set to the
+ * PathClassLoader used to load the package archive in the
+ * single application per VM case, a single Android process
+ * may contain several Contexts executing on one thread with
+ * their own logical ClassLoaders while the Java context
+ * ClassLoader is a thread local. This is why in the case when
+ * we have multiple packages per VM we do not set the Java
+ * context ClassLoader to an arbitrary but instead warn the
+ * user to set their own if we detect that they are using a
+ * Java library that expects it to be set.
+ */
+ private void initializeJavaContextClassLoader() {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ android.content.pm.PackageInfo pi;
+ try {
+ pi = pm.getPackageInfo(mPackageName, 0);
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ /*
+ * Two possible indications that this package could be
+ * sharing its virtual machine with other packages:
+ *
+ * 1.) the sharedUserId attribute is set in the manifest,
+ * indicating a request to share a VM with other
+ * packages with the same sharedUserId.
+ *
+ * 2.) the application element of the manifest has an
+ * attribute specifying a non-default process name,
+ * indicating the desire to run in another packages VM.
+ */
+ boolean sharedUserIdSet = (pi.sharedUserId != null);
+ boolean processNameNotDefault =
+ (pi.applicationInfo != null &&
+ !mPackageName.equals(pi.applicationInfo.processName));
+ boolean sharable = (sharedUserIdSet || processNameNotDefault);
+ ClassLoader contextClassLoader =
+ (sharable)
+ ? new WarningContextClassLoader()
+ : mClassLoader;
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+
+ private static class WarningContextClassLoader extends ClassLoader {
+
+ private static boolean warned = false;
+
+ private void warn(String methodName) {
+ if (warned) {
+ return;
+ }
+ warned = true;
+ Thread.currentThread().setContextClassLoader(getParent());
+ Slog.w(ActivityThread.TAG, "ClassLoader." + methodName + ": " +
+ "The class loader returned by " +
+ "Thread.getContextClassLoader() may fail for processes " +
+ "that host multiple applications. You should explicitly " +
+ "specify a context class loader. For example: " +
+ "Thread.setContextClassLoader(getClass().getClassLoader());");
+ }
+
+ @Override public URL getResource(String resName) {
+ warn("getResource");
+ return getParent().getResource(resName);
+ }
+
+ @Override public Enumeration<URL> getResources(String resName) throws IOException {
+ warn("getResources");
+ return getParent().getResources(resName);
+ }
+
+ @Override public InputStream getResourceAsStream(String resName) {
+ warn("getResourceAsStream");
+ return getParent().getResourceAsStream(resName);
+ }
+
+ @Override public Class<?> loadClass(String className) throws ClassNotFoundException {
+ warn("loadClass");
+ return getParent().loadClass(className);
+ }
+
+ @Override public void setClassAssertionStatus(String cname, boolean enable) {
+ warn("setClassAssertionStatus");
+ getParent().setClassAssertionStatus(cname, enable);
+ }
+
+ @Override public void setPackageAssertionStatus(String pname, boolean enable) {
+ warn("setPackageAssertionStatus");
+ getParent().setPackageAssertionStatus(pname, enable);
+ }
+
+ @Override public void setDefaultAssertionStatus(boolean enable) {
+ warn("setDefaultAssertionStatus");
+ getParent().setDefaultAssertionStatus(enable);
+ }
+
+ @Override public void clearAssertionStatus() {
+ warn("clearAssertionStatus");
+ getParent().clearAssertionStatus();
+ }
+ }
+
+ public String getAppDir() {
+ return mAppDir;
+ }
+
+ public String getResDir() {
+ return mResDir;
+ }
+
+ public String getDataDir() {
+ return mDataDir;
+ }
+
+ public File getDataDirFile() {
+ return mDataDirFile;
+ }
+
+ public AssetManager getAssets(ActivityThread mainThread) {
+ return getResources(mainThread).getAssets();
+ }
+
+ public Resources getResources(ActivityThread mainThread) {
+ if (mResources == null) {
+ mResources = mainThread.getTopLevelResources(mResDir, this);
+ }
+ return mResources;
+ }
+
+ public Application makeApplication(boolean forceDefaultAppClass,
+ Instrumentation instrumentation) {
+ if (mApplication != null) {
+ return mApplication;
+ }
+
+ Application app = null;
+
+ String appClass = mApplicationInfo.className;
+ if (forceDefaultAppClass || (appClass == null)) {
+ appClass = "android.app.Application";
+ }
+
+ try {
+ java.lang.ClassLoader cl = getClassLoader();
+ ContextImpl appContext = new ContextImpl();
+ appContext.init(this, null, mActivityThread);
+ app = mActivityThread.mInstrumentation.newApplication(
+ cl, appClass, appContext);
+ appContext.setOuterContext(app);
+ } catch (Exception e) {
+ if (!mActivityThread.mInstrumentation.onException(app, e)) {
+ throw new RuntimeException(
+ "Unable to instantiate application " + appClass
+ + ": " + e.toString(), e);
+ }
+ }
+ mActivityThread.mAllApplications.add(app);
+ mApplication = app;
+
+ if (instrumentation != null) {
+ try {
+ instrumentation.callApplicationOnCreate(app);
+ } catch (Exception e) {
+ if (!instrumentation.onException(app, e)) {
+ throw new RuntimeException(
+ "Unable to create application " + app.getClass().getName()
+ + ": " + e.toString(), e);
+ }
+ }
+ }
+
+ return app;
+ }
+
+ public void removeContextRegistrations(Context context,
+ String who, String what) {
+ HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap =
+ mReceivers.remove(context);
+ if (rmap != null) {
+ Iterator<LoadedApk.ReceiverDispatcher> it = rmap.values().iterator();
+ while (it.hasNext()) {
+ LoadedApk.ReceiverDispatcher rd = it.next();
+ IntentReceiverLeaked leak = new IntentReceiverLeaked(
+ what + " " + who + " has leaked IntentReceiver "
+ + rd.getIntentReceiver() + " that was " +
+ "originally registered here. Are you missing a " +
+ "call to unregisterReceiver()?");
+ leak.setStackTrace(rd.getLocation().getStackTrace());
+ Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
+ try {
+ ActivityManagerNative.getDefault().unregisterReceiver(
+ rd.getIIntentReceiver());
+ } catch (RemoteException e) {
+ // system crashed, nothing we can do
+ }
+ }
+ }
+ mUnregisteredReceivers.remove(context);
+ //Slog.i(TAG, "Receiver registrations: " + mReceivers);
+ HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap =
+ mServices.remove(context);
+ if (smap != null) {
+ Iterator<LoadedApk.ServiceDispatcher> it = smap.values().iterator();
+ while (it.hasNext()) {
+ LoadedApk.ServiceDispatcher sd = it.next();
+ ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
+ what + " " + who + " has leaked ServiceConnection "
+ + sd.getServiceConnection() + " that was originally bound here");
+ leak.setStackTrace(sd.getLocation().getStackTrace());
+ Slog.e(ActivityThread.TAG, leak.getMessage(), leak);
+ try {
+ ActivityManagerNative.getDefault().unbindService(
+ sd.getIServiceConnection());
+ } catch (RemoteException e) {
+ // system crashed, nothing we can do
+ }
+ sd.doForget();
+ }
+ }
+ mUnboundServices.remove(context);
+ //Slog.i(TAG, "Service registrations: " + mServices);
+ }
+
+ public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
+ Context context, Handler handler,
+ Instrumentation instrumentation, boolean registered) {
+ synchronized (mReceivers) {
+ LoadedApk.ReceiverDispatcher rd = null;
+ HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
+ if (registered) {
+ map = mReceivers.get(context);
+ if (map != null) {
+ rd = map.get(r);
+ }
+ }
+ if (rd == null) {
+ rd = new ReceiverDispatcher(r, context, handler,
+ instrumentation, registered);
+ if (registered) {
+ if (map == null) {
+ map = new HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
+ mReceivers.put(context, map);
+ }
+ map.put(r, rd);
+ }
+ } else {
+ rd.validate(context, handler);
+ }
+ return rd.getIIntentReceiver();
+ }
+ }
+
+ public IIntentReceiver forgetReceiverDispatcher(Context context,
+ BroadcastReceiver r) {
+ synchronized (mReceivers) {
+ HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = mReceivers.get(context);
+ LoadedApk.ReceiverDispatcher rd = null;
+ if (map != null) {
+ rd = map.get(r);
+ if (rd != null) {
+ map.remove(r);
+ if (map.size() == 0) {
+ mReceivers.remove(context);
+ }
+ if (r.getDebugUnregister()) {
+ HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
+ = mUnregisteredReceivers.get(context);
+ if (holder == null) {
+ holder = new HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
+ mUnregisteredReceivers.put(context, holder);
+ }
+ RuntimeException ex = new IllegalArgumentException(
+ "Originally unregistered here:");
+ ex.fillInStackTrace();
+ rd.setUnregisterLocation(ex);
+ holder.put(r, rd);
+ }
+ return rd.getIIntentReceiver();
+ }
+ }
+ HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder
+ = mUnregisteredReceivers.get(context);
+ if (holder != null) {
+ rd = holder.get(r);
+ if (rd != null) {
+ RuntimeException ex = rd.getUnregisterLocation();
+ throw new IllegalArgumentException(
+ "Unregistering Receiver " + r
+ + " that was already unregistered", ex);
+ }
+ }
+ if (context == null) {
+ throw new IllegalStateException("Unbinding Receiver " + r
+ + " from Context that is no longer in use: " + context);
+ } else {
+ throw new IllegalArgumentException("Receiver not registered: " + r);
+ }
+
+ }
+ }
+
+ static final class ReceiverDispatcher {
+
+ final static class InnerReceiver extends IIntentReceiver.Stub {
+ final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
+ final LoadedApk.ReceiverDispatcher mStrongRef;
+
+ InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
+ mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
+ mStrongRef = strong ? rd : null;
+ }
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered, boolean sticky) {
+ LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
+ if (ActivityThread.DEBUG_BROADCAST) {
+ int seq = intent.getIntExtra("seq", -1);
+ Slog.i(ActivityThread.TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq
+ + " to " + (rd != null ? rd.mReceiver : null));
+ }
+ if (rd != null) {
+ rd.performReceive(intent, resultCode, data, extras,
+ ordered, sticky);
+ } else {
+ // The activity manager dispatched a broadcast to a registered
+ // receiver in this process, but before it could be delivered the
+ // receiver was unregistered. Acknowledge the broadcast on its
+ // behalf so that the system's broadcast sequence can continue.
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing broadcast to unregistered receiver");
+ IActivityManager mgr = ActivityManagerNative.getDefault();
+ try {
+ mgr.finishReceiver(this, resultCode, data, extras, false);
+ } catch (RemoteException e) {
+ Slog.w(ActivityThread.TAG, "Couldn't finish broadcast to unregistered receiver");
+ }
+ }
+ }
+ }
+
+ final IIntentReceiver.Stub mIIntentReceiver;
+ final BroadcastReceiver mReceiver;
+ final Context mContext;
+ final Handler mActivityThread;
+ final Instrumentation mInstrumentation;
+ final boolean mRegistered;
+ final IntentReceiverLeaked mLocation;
+ RuntimeException mUnregisterLocation;
+
+ final class Args implements Runnable {
+ private Intent mCurIntent;
+ private int mCurCode;
+ private String mCurData;
+ private Bundle mCurMap;
+ private boolean mCurOrdered;
+ private boolean mCurSticky;
+
+ public void run() {
+ BroadcastReceiver receiver = mReceiver;
+ if (ActivityThread.DEBUG_BROADCAST) {
+ int seq = mCurIntent.getIntExtra("seq", -1);
+ Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
+ + " seq=" + seq + " to " + mReceiver);
+ Slog.i(ActivityThread.TAG, " mRegistered=" + mRegistered
+ + " mCurOrdered=" + mCurOrdered);
+ }
+
+ IActivityManager mgr = ActivityManagerNative.getDefault();
+ Intent intent = mCurIntent;
+ mCurIntent = null;
+
+ if (receiver == null) {
+ if (mRegistered && mCurOrdered) {
+ try {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing null broadcast to " + mReceiver);
+ mgr.finishReceiver(mIIntentReceiver,
+ mCurCode, mCurData, mCurMap, false);
+ } catch (RemoteException ex) {
+ }
+ }
+ return;
+ }
+
+ try {
+ ClassLoader cl = mReceiver.getClass().getClassLoader();
+ intent.setExtrasClassLoader(cl);
+ if (mCurMap != null) {
+ mCurMap.setClassLoader(cl);
+ }
+ receiver.setOrderedHint(true);
+ receiver.setResult(mCurCode, mCurData, mCurMap);
+ receiver.clearAbortBroadcast();
+ receiver.setOrderedHint(mCurOrdered);
+ receiver.setInitialStickyHint(mCurSticky);
+ receiver.onReceive(mContext, intent);
+ } catch (Exception e) {
+ if (mRegistered && mCurOrdered) {
+ try {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing failed broadcast to " + mReceiver);
+ mgr.finishReceiver(mIIntentReceiver,
+ mCurCode, mCurData, mCurMap, false);
+ } catch (RemoteException ex) {
+ }
+ }
+ if (mInstrumentation == null ||
+ !mInstrumentation.onException(mReceiver, e)) {
+ throw new RuntimeException(
+ "Error receiving broadcast " + intent
+ + " in " + mReceiver, e);
+ }
+ }
+ if (mRegistered && mCurOrdered) {
+ try {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing broadcast to " + mReceiver);
+ mgr.finishReceiver(mIIntentReceiver,
+ receiver.getResultCode(),
+ receiver.getResultData(),
+ receiver.getResultExtras(false),
+ receiver.getAbortBroadcast());
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ ReceiverDispatcher(BroadcastReceiver receiver, Context context,
+ Handler activityThread, Instrumentation instrumentation,
+ boolean registered) {
+ if (activityThread == null) {
+ throw new NullPointerException("Handler must not be null");
+ }
+
+ mIIntentReceiver = new InnerReceiver(this, !registered);
+ mReceiver = receiver;
+ mContext = context;
+ mActivityThread = activityThread;
+ mInstrumentation = instrumentation;
+ mRegistered = registered;
+ mLocation = new IntentReceiverLeaked(null);
+ mLocation.fillInStackTrace();
+ }
+
+ void validate(Context context, Handler activityThread) {
+ if (mContext != context) {
+ throw new IllegalStateException(
+ "Receiver " + mReceiver +
+ " registered with differing Context (was " +
+ mContext + " now " + context + ")");
+ }
+ if (mActivityThread != activityThread) {
+ throw new IllegalStateException(
+ "Receiver " + mReceiver +
+ " registered with differing handler (was " +
+ mActivityThread + " now " + activityThread + ")");
+ }
+ }
+
+ IntentReceiverLeaked getLocation() {
+ return mLocation;
+ }
+
+ BroadcastReceiver getIntentReceiver() {
+ return mReceiver;
+ }
+
+ IIntentReceiver getIIntentReceiver() {
+ return mIIntentReceiver;
+ }
+
+ void setUnregisterLocation(RuntimeException ex) {
+ mUnregisterLocation = ex;
+ }
+
+ RuntimeException getUnregisterLocation() {
+ return mUnregisterLocation;
+ }
+
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered, boolean sticky) {
+ if (ActivityThread.DEBUG_BROADCAST) {
+ int seq = intent.getIntExtra("seq", -1);
+ Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq
+ + " to " + mReceiver);
+ }
+ Args args = new Args();
+ args.mCurIntent = intent;
+ args.mCurCode = resultCode;
+ args.mCurData = data;
+ args.mCurMap = extras;
+ args.mCurOrdered = ordered;
+ args.mCurSticky = sticky;
+ if (!mActivityThread.post(args)) {
+ if (mRegistered && ordered) {
+ IActivityManager mgr = ActivityManagerNative.getDefault();
+ try {
+ if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
+ "Finishing sync broadcast to " + mReceiver);
+ mgr.finishReceiver(mIIntentReceiver, args.mCurCode,
+ args.mCurData, args.mCurMap, false);
+ } catch (RemoteException ex) {
+ }
+ }
+ }
+ }
+
+ }
+
+ public final IServiceConnection getServiceDispatcher(ServiceConnection c,
+ Context context, Handler handler, int flags) {
+ synchronized (mServices) {
+ LoadedApk.ServiceDispatcher sd = null;
+ HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
+ if (map != null) {
+ sd = map.get(c);
+ }
+ if (sd == null) {
+ sd = new ServiceDispatcher(c, context, handler, flags);
+ if (map == null) {
+ map = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
+ mServices.put(context, map);
+ }
+ map.put(c, sd);
+ } else {
+ sd.validate(context, handler);
+ }
+ return sd.getIServiceConnection();
+ }
+ }
+
+ public final IServiceConnection forgetServiceDispatcher(Context context,
+ ServiceConnection c) {
+ synchronized (mServices) {
+ HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map
+ = mServices.get(context);
+ LoadedApk.ServiceDispatcher sd = null;
+ if (map != null) {
+ sd = map.get(c);
+ if (sd != null) {
+ map.remove(c);
+ sd.doForget();
+ if (map.size() == 0) {
+ mServices.remove(context);
+ }
+ if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) {
+ HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
+ = mUnboundServices.get(context);
+ if (holder == null) {
+ holder = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
+ mUnboundServices.put(context, holder);
+ }
+ RuntimeException ex = new IllegalArgumentException(
+ "Originally unbound here:");
+ ex.fillInStackTrace();
+ sd.setUnbindLocation(ex);
+ holder.put(c, sd);
+ }
+ return sd.getIServiceConnection();
+ }
+ }
+ HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder
+ = mUnboundServices.get(context);
+ if (holder != null) {
+ sd = holder.get(c);
+ if (sd != null) {
+ RuntimeException ex = sd.getUnbindLocation();
+ throw new IllegalArgumentException(
+ "Unbinding Service " + c
+ + " that was already unbound", ex);
+ }
+ }
+ if (context == null) {
+ throw new IllegalStateException("Unbinding Service " + c
+ + " from Context that is no longer in use: " + context);
+ } else {
+ throw new IllegalArgumentException("Service not registered: " + c);
+ }
+ }
+ }
+
+ static final class ServiceDispatcher {
+ private final ServiceDispatcher.InnerConnection mIServiceConnection;
+ private final ServiceConnection mConnection;
+ private final Context mContext;
+ private final Handler mActivityThread;
+ private final ServiceConnectionLeaked mLocation;
+ private final int mFlags;
+
+ private RuntimeException mUnbindLocation;
+
+ private boolean mDied;
+
+ private static class ConnectionInfo {
+ IBinder binder;
+ IBinder.DeathRecipient deathMonitor;
+ }
+
+ private static class InnerConnection extends IServiceConnection.Stub {
+ final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;
+
+ InnerConnection(LoadedApk.ServiceDispatcher sd) {
+ mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
+ }
+
+ public void connected(ComponentName name, IBinder service) throws RemoteException {
+ LoadedApk.ServiceDispatcher sd = mDispatcher.get();
+ if (sd != null) {
+ sd.connected(name, service);
+ }
+ }
+ }
+
+ private final HashMap<ComponentName, ServiceDispatcher.ConnectionInfo> mActiveConnections
+ = new HashMap<ComponentName, ServiceDispatcher.ConnectionInfo>();
+
+ ServiceDispatcher(ServiceConnection conn,
+ Context context, Handler activityThread, int flags) {
+ mIServiceConnection = new InnerConnection(this);
+ mConnection = conn;
+ mContext = context;
+ mActivityThread = activityThread;
+ mLocation = new ServiceConnectionLeaked(null);
+ mLocation.fillInStackTrace();
+ mFlags = flags;
+ }
+
+ void validate(Context context, Handler activityThread) {
+ if (mContext != context) {
+ throw new RuntimeException(
+ "ServiceConnection " + mConnection +
+ " registered with differing Context (was " +
+ mContext + " now " + context + ")");
+ }
+ if (mActivityThread != activityThread) {
+ throw new RuntimeException(
+ "ServiceConnection " + mConnection +
+ " registered with differing handler (was " +
+ mActivityThread + " now " + activityThread + ")");
+ }
+ }
+
+ void doForget() {
+ synchronized(this) {
+ Iterator<ServiceDispatcher.ConnectionInfo> it = mActiveConnections.values().iterator();
+ while (it.hasNext()) {
+ ServiceDispatcher.ConnectionInfo ci = it.next();
+ ci.binder.unlinkToDeath(ci.deathMonitor, 0);
+ }
+ mActiveConnections.clear();
+ }
+ }
+
+ ServiceConnectionLeaked getLocation() {
+ return mLocation;
+ }
+
+ ServiceConnection getServiceConnection() {
+ return mConnection;
+ }
+
+ IServiceConnection getIServiceConnection() {
+ return mIServiceConnection;
+ }
+
+ int getFlags() {
+ return mFlags;
+ }
+
+ void setUnbindLocation(RuntimeException ex) {
+ mUnbindLocation = ex;
+ }
+
+ RuntimeException getUnbindLocation() {
+ return mUnbindLocation;
+ }
+
+ public void connected(ComponentName name, IBinder service) {
+ if (mActivityThread != null) {
+ mActivityThread.post(new RunConnection(name, service, 0));
+ } else {
+ doConnected(name, service);
+ }
+ }
+
+ public void death(ComponentName name, IBinder service) {
+ ServiceDispatcher.ConnectionInfo old;
+
+ synchronized (this) {
+ mDied = true;
+ old = mActiveConnections.remove(name);
+ if (old == null || old.binder != service) {
+ // Death for someone different than who we last
+ // reported... just ignore it.
+ return;
+ }
+ old.binder.unlinkToDeath(old.deathMonitor, 0);
+ }
+
+ if (mActivityThread != null) {
+ mActivityThread.post(new RunConnection(name, service, 1));
+ } else {
+ doDeath(name, service);
+ }
+ }
+
+ public void doConnected(ComponentName name, IBinder service) {
+ ServiceDispatcher.ConnectionInfo old;
+ ServiceDispatcher.ConnectionInfo info;
+
+ synchronized (this) {
+ old = mActiveConnections.get(name);
+ if (old != null && old.binder == service) {
+ // Huh, already have this one. Oh well!
+ return;
+ }
+
+ if (service != null) {
+ // A new service is being connected... set it all up.
+ mDied = false;
+ info = new ConnectionInfo();
+ info.binder = service;
+ info.deathMonitor = new DeathMonitor(name, service);
+ try {
+ service.linkToDeath(info.deathMonitor, 0);
+ mActiveConnections.put(name, info);
+ } catch (RemoteException e) {
+ // This service was dead before we got it... just
+ // don't do anything with it.
+ mActiveConnections.remove(name);
+ return;
+ }
+
+ } else {
+ // The named service is being disconnected... clean up.
+ mActiveConnections.remove(name);
+ }
+
+ if (old != null) {
+ old.binder.unlinkToDeath(old.deathMonitor, 0);
+ }
+ }
+
+ // If there was an old service, it is not disconnected.
+ if (old != null) {
+ mConnection.onServiceDisconnected(name);
+ }
+ // If there is a new service, it is now connected.
+ if (service != null) {
+ mConnection.onServiceConnected(name, service);
+ }
+ }
+
+ public void doDeath(ComponentName name, IBinder service) {
+ mConnection.onServiceDisconnected(name);
+ }
+
+ private final class RunConnection implements Runnable {
+ RunConnection(ComponentName name, IBinder service, int command) {
+ mName = name;
+ mService = service;
+ mCommand = command;
+ }
+
+ public void run() {
+ if (mCommand == 0) {
+ doConnected(mName, mService);
+ } else if (mCommand == 1) {
+ doDeath(mName, mService);
+ }
+ }
+
+ final ComponentName mName;
+ final IBinder mService;
+ final int mCommand;
+ }
+
+ private final class DeathMonitor implements IBinder.DeathRecipient
+ {
+ DeathMonitor(ComponentName name, IBinder service) {
+ mName = name;
+ mService = service;
+ }
+
+ public void binderDied() {
+ death(mName, mService);
+ }
+
+ final ComponentName mName;
+ final IBinder mService;
+ }
+ }
+}
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
new file mode 100644
index 000000000000..d7a041243acb
--- /dev/null
+++ b/core/java/android/app/NativeActivity.java
@@ -0,0 +1,369 @@
+package android.app;
+
+import com.android.internal.view.IInputMethodCallback;
+import com.android.internal.view.IInputMethodSession;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.AttributeSet;
+import android.view.InputChannel;
+import android.view.InputQueue;
+import android.view.KeyEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+
+/**
+ * Convenience for implementing an activity that will be implemented
+ * purely in native code. That is, a game (or game-like thing). There
+ * is no need to derive from this class; you can simply declare it in your
+ * manifest, and use the NDK APIs from there.
+ *
+ * <p>A typical manifest would look like:
+ *
+ * {@sample development/ndk/platforms/android-9/samples/native-activity/AndroidManifest.xml
+ * manifest}
+ *
+ * <p>A very simple example of native code that is run by NativeActivity
+ * follows. This reads input events from the user and uses OpenGLES to
+ * draw into the native activity's window.
+ *
+ * {@sample development/ndk/platforms/android-9/samples/native-activity/jni/main.c all}
+ */
+public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
+ InputQueue.Callback, OnGlobalLayoutListener {
+ public static final String META_DATA_LIB_NAME = "android.app.lib_name";
+
+ public static final String KEY_NATIVE_SAVED_STATE = "android:native_state";
+
+ private NativeContentView mNativeContentView;
+ private InputMethodManager mIMM;
+ private InputMethodCallback mInputMethodCallback;
+
+ private int mNativeHandle;
+
+ private InputQueue mCurInputQueue;
+ private SurfaceHolder mCurSurfaceHolder;
+
+ final int[] mLocation = new int[2];
+ int mLastContentX;
+ int mLastContentY;
+ int mLastContentWidth;
+ int mLastContentHeight;
+
+ private boolean mDispatchingUnhandledKey;
+
+ private boolean mDestroyed;
+
+ private native int loadNativeCode(String path, MessageQueue queue,
+ String internalDataPath, String externalDataPath, int sdkVersion,
+ AssetManager assetMgr, byte[] savedState);
+ private native void unloadNativeCode(int handle);
+
+ private native void onStartNative(int handle);
+ private native void onResumeNative(int handle);
+ private native byte[] onSaveInstanceStateNative(int handle);
+ private native void onPauseNative(int handle);
+ private native void onStopNative(int handle);
+ private native void onConfigurationChangedNative(int handle);
+ private native void onLowMemoryNative(int handle);
+ private native void onWindowFocusChangedNative(int handle, boolean focused);
+ private native void onSurfaceCreatedNative(int handle, Surface surface);
+ private native void onSurfaceChangedNative(int handle, Surface surface,
+ int format, int width, int height);
+ private native void onSurfaceRedrawNeededNative(int handle, Surface surface);
+ private native void onSurfaceDestroyedNative(int handle);
+ private native void onInputChannelCreatedNative(int handle, InputChannel channel);
+ private native void onInputChannelDestroyedNative(int handle, InputChannel channel);
+ private native void onContentRectChangedNative(int handle, int x, int y, int w, int h);
+ private native void dispatchKeyEventNative(int handle, KeyEvent event);
+ private native void finishPreDispatchKeyEventNative(int handle, int seq, boolean handled);
+
+ static class NativeContentView extends View {
+ NativeActivity mActivity;
+
+ public NativeContentView(Context context) {
+ super(context);
+ }
+
+ public NativeContentView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+ }
+
+ static class InputMethodCallback extends IInputMethodCallback.Stub {
+ WeakReference<NativeActivity> mNa;
+
+ InputMethodCallback(NativeActivity na) {
+ mNa = new WeakReference<NativeActivity>(na);
+ }
+
+ @Override
+ public void finishedEvent(int seq, boolean handled) {
+ NativeActivity na = mNa.get();
+ if (na != null) {
+ na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled);
+ }
+ }
+
+ @Override
+ public void sessionCreated(IInputMethodSession session) {
+ // Stub -- not for use in the client.
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ String libname = "main";
+ ActivityInfo ai;
+
+ mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+ mInputMethodCallback = new InputMethodCallback(this);
+
+ getWindow().takeSurface(this);
+ getWindow().takeInputQueue(this);
+ getWindow().setFormat(PixelFormat.RGB_565);
+ getWindow().setSoftInputMode(
+ WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED
+ | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
+
+ mNativeContentView = new NativeContentView(this);
+ mNativeContentView.mActivity = this;
+ setContentView(mNativeContentView);
+ mNativeContentView.requestFocus();
+ mNativeContentView.getViewTreeObserver().addOnGlobalLayoutListener(this);
+
+ try {
+ ai = getPackageManager().getActivityInfo(
+ getIntent().getComponent(), PackageManager.GET_META_DATA);
+ if (ai.metaData != null) {
+ String ln = ai.metaData.getString(META_DATA_LIB_NAME);
+ if (ln != null) libname = ln;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException("Error getting activity info", e);
+ }
+
+ String path = null;
+
+ if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) == 0) {
+ // If the application does not have (Java) code, then no ClassLoader
+ // has been set up for it. We will need to do our own search for
+ // the native code.
+ File libraryFile = new File(ai.applicationInfo.nativeLibraryDir,
+ System.mapLibraryName(libname));
+ if (libraryFile.exists()) {
+ path = libraryFile.getPath();
+ }
+ }
+
+ if (path == null) {
+ throw new IllegalArgumentException("Unable to find native library: " + libname);
+ }
+
+ byte[] nativeSavedState = savedInstanceState != null
+ ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;
+
+ mNativeHandle = loadNativeCode(path, Looper.myQueue(),
+ getFilesDir().toString(),
+ Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(),
+ Build.VERSION.SDK_INT, getAssets(), nativeSavedState);
+
+ if (mNativeHandle == 0) {
+ throw new IllegalArgumentException("Unable to load native library: " + path);
+ }
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ mDestroyed = true;
+ if (mCurSurfaceHolder != null) {
+ onSurfaceDestroyedNative(mNativeHandle);
+ mCurSurfaceHolder = null;
+ }
+ if (mCurInputQueue != null) {
+ onInputChannelDestroyedNative(mNativeHandle, mCurInputQueue.getInputChannel());
+ mCurInputQueue = null;
+ }
+ unloadNativeCode(mNativeHandle);
+ super.onDestroy();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ onPauseNative(mNativeHandle);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ onResumeNative(mNativeHandle);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ byte[] state = onSaveInstanceStateNative(mNativeHandle);
+ if (state != null) {
+ outState.putByteArray(KEY_NATIVE_SAVED_STATE, state);
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ onStartNative(mNativeHandle);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ onStopNative(mNativeHandle);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mDestroyed) {
+ onConfigurationChangedNative(mNativeHandle);
+ }
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ if (!mDestroyed) {
+ onLowMemoryNative(mNativeHandle);
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (!mDestroyed) {
+ onWindowFocusChangedNative(mNativeHandle, hasFocus);
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (mDispatchingUnhandledKey) {
+ return super.dispatchKeyEvent(event);
+ } else {
+ // Key events from the IME do not go through the input channel;
+ // we need to intercept them here to hand to the application.
+ dispatchKeyEventNative(mNativeHandle, event);
+ return true;
+ }
+ }
+
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (!mDestroyed) {
+ mCurSurfaceHolder = holder;
+ onSurfaceCreatedNative(mNativeHandle, holder.getSurface());
+ }
+ }
+
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (!mDestroyed) {
+ mCurSurfaceHolder = holder;
+ onSurfaceChangedNative(mNativeHandle, holder.getSurface(), format, width, height);
+ }
+ }
+
+ public void surfaceRedrawNeeded(SurfaceHolder holder) {
+ if (!mDestroyed) {
+ mCurSurfaceHolder = holder;
+ onSurfaceRedrawNeededNative(mNativeHandle, holder.getSurface());
+ }
+ }
+
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mCurSurfaceHolder = null;
+ if (!mDestroyed) {
+ onSurfaceDestroyedNative(mNativeHandle);
+ }
+ }
+
+ public void onInputQueueCreated(InputQueue queue) {
+ if (!mDestroyed) {
+ mCurInputQueue = queue;
+ onInputChannelCreatedNative(mNativeHandle, queue.getInputChannel());
+ }
+ }
+
+ public void onInputQueueDestroyed(InputQueue queue) {
+ mCurInputQueue = null;
+ if (!mDestroyed) {
+ onInputChannelDestroyedNative(mNativeHandle, queue.getInputChannel());
+ }
+ }
+
+ public void onGlobalLayout() {
+ mNativeContentView.getLocationInWindow(mLocation);
+ int w = mNativeContentView.getWidth();
+ int h = mNativeContentView.getHeight();
+ if (mLocation[0] != mLastContentX || mLocation[1] != mLastContentY
+ || w != mLastContentWidth || h != mLastContentHeight) {
+ mLastContentX = mLocation[0];
+ mLastContentY = mLocation[1];
+ mLastContentWidth = w;
+ mLastContentHeight = h;
+ if (!mDestroyed) {
+ onContentRectChangedNative(mNativeHandle, mLastContentX,
+ mLastContentY, mLastContentWidth, mLastContentHeight);
+ }
+ }
+ }
+
+ void dispatchUnhandledKeyEvent(KeyEvent event) {
+ try {
+ mDispatchingUnhandledKey = true;
+ View decor = getWindow().getDecorView();
+ if (decor != null) {
+ decor.dispatchKeyEvent(event);
+ }
+ } finally {
+ mDispatchingUnhandledKey = false;
+ }
+ }
+
+ void preDispatchKeyEvent(KeyEvent event, int seq) {
+ mIMM.dispatchKeyEvent(this, seq, event,
+ mInputMethodCallback);
+ }
+
+ void setWindowFlags(int flags, int mask) {
+ getWindow().setFlags(flags, mask);
+ }
+
+ void setWindowFormat(int format) {
+ getWindow().setFormat(format);
+ }
+
+ void showIme(int mode) {
+ mIMM.showSoftInput(mNativeContentView, mode);
+ }
+
+ void hideIme(int mode) {
+ mIMM.hideSoftInputFromWindow(mNativeContentView.getWindowToken(), mode);
+ }
+}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4d72f73cd377..856943d5f380 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -83,10 +83,10 @@ public class Notification implements Parcelable
public int icon;
/**
- * The number of events that this notification represents. For example, if this is the
- * new mail notification, this would be the number of unread messages. This number is
- * be superimposed over the icon in the status bar. If the number is 0 or negative, it
- * is not shown in the status bar.
+ * The number of events that this notification represents. For example, in a new mail
+ * notification, this could be the number of unread messages. This number is superimposed over
+ * the icon in the status bar. If the number is 0 or negative, it is not shown in the status
+ * bar.
*/
public int number;
@@ -109,13 +109,24 @@ public class Notification implements Parcelable
public PendingIntent deleteIntent;
/**
+ * An intent to launch instead of posting the notification to the status bar.
+ * Only for use with extremely high-priority notifications demanding the user's
+ * <strong>immediate</strong> attention, such as an incoming phone call or
+ * alarm clock that the user has explicitly set to a particular time.
+ * If this facility is used for something else, please give the user an option
+ * to turn it off and use a normal notification, as this can be extremely
+ * disruptive.
+ */
+ public PendingIntent fullScreenIntent;
+
+ /**
* Text to scroll across the screen when this item is added to
* the status bar.
*/
public CharSequence tickerText;
/**
- * The view that shows when this notification is shown in the expanded status bar.
+ * The view that will represent this notification in the expanded status bar.
*/
public RemoteViews contentView;
@@ -339,6 +350,49 @@ public class Notification implements Parcelable
ledOnMS = parcel.readInt();
ledOffMS = parcel.readInt();
iconLevel = parcel.readInt();
+
+ if (parcel.readInt() != 0) {
+ fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+ }
+ }
+
+ public Notification clone() {
+ Notification that = new Notification();
+
+ that.when = this.when;
+ that.icon = this.icon;
+ that.number = this.number;
+
+ // PendingIntents are global, so there's no reason (or way) to clone them.
+ that.contentIntent = this.contentIntent;
+ that.deleteIntent = this.deleteIntent;
+ that.fullScreenIntent = this.fullScreenIntent;
+
+ if (this.tickerText != null) {
+ that.tickerText = this.tickerText.toString();
+ }
+ if (this.contentView != null) {
+ that.contentView = this.contentView.clone();
+ }
+ that.iconLevel = that.iconLevel;
+ that.sound = this.sound; // android.net.Uri is immutable
+ that.audioStreamType = this.audioStreamType;
+
+ final long[] vibrate = this.vibrate;
+ if (vibrate != null) {
+ final int N = vibrate.length;
+ final long[] vib = that.vibrate = new long[N];
+ System.arraycopy(vibrate, 0, vib, 0, N);
+ }
+
+ that.ledARGB = this.ledARGB;
+ that.ledOnMS = this.ledOnMS;
+ that.ledOffMS = this.ledOffMS;
+ that.defaults = this.defaults;
+
+ that.flags = this.flags;
+
+ return that;
}
public int describeContents() {
@@ -395,6 +449,13 @@ public class Notification implements Parcelable
parcel.writeInt(ledOnMS);
parcel.writeInt(ledOffMS);
parcel.writeInt(iconLevel);
+
+ if (fullScreenIntent != null) {
+ parcel.writeInt(1);
+ fullScreenIntent.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
}
/**
@@ -480,6 +541,8 @@ public class Notification implements Parcelable
}
sb.append(",defaults=0x");
sb.append(Integer.toHexString(this.defaults));
+ sb.append(",flags=0x");
+ sb.append(Integer.toHexString(this.flags));
sb.append(")");
return sb.toString();
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 6fe12fcf0bda..1fae516b060f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.content.Context;
+import android.os.Binder;
import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java
new file mode 100644
index 000000000000..af6bb1ba4dca
--- /dev/null
+++ b/core/java/android/app/QueuedWork.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Internal utility class to keep track of process-global work that's
+ * outstanding and hasn't been finished yet.
+ *
+ * This was created for writing SharedPreference edits out
+ * asynchronously so we'd have a mechanism to wait for the writes in
+ * Activity.onPause and similar places, but we may use this mechanism
+ * for other things in the future.
+ *
+ * @hide
+ */
+public class QueuedWork {
+
+ // The set of Runnables that will finish or wait on any async
+ // activities started by the application.
+ private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
+ new ConcurrentLinkedQueue<Runnable>();
+
+ private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class
+
+ /**
+ * Returns a single-thread Executor shared by the entire process,
+ * creating it if necessary.
+ */
+ public static ExecutorService singleThreadExecutor() {
+ synchronized (QueuedWork.class) {
+ if (sSingleThreadExecutor == null) {
+ // TODO: can we give this single thread a thread name?
+ sSingleThreadExecutor = Executors.newSingleThreadExecutor();
+ }
+ return sSingleThreadExecutor;
+ }
+ }
+
+ /**
+ * Add a runnable to finish (or wait for) a deferred operation
+ * started in this context earlier. Typically finished by e.g.
+ * an Activity#onPause. Used by SharedPreferences$Editor#startCommit().
+ *
+ * Note that this doesn't actually start it running. This is just
+ * a scratch set for callers doing async work to keep updated with
+ * what's in-flight. In the common case, caller code
+ * (e.g. SharedPreferences) will pretty quickly call remove()
+ * after an add(). The only time these Runnables are run is from
+ * waitToFinish(), below.
+ */
+ public static void add(Runnable finisher) {
+ sPendingWorkFinishers.add(finisher);
+ }
+
+ public static void remove(Runnable finisher) {
+ sPendingWorkFinishers.remove(finisher);
+ }
+
+ /**
+ * Finishes or waits for async operations to complete.
+ * (e.g. SharedPreferences$Editor#startCommit writes)
+ *
+ * Is called from the Activity base class's onPause(), after
+ * BroadcastReceiver's onReceive, after Service command handling,
+ * etc. (so async work is never lost)
+ */
+ public static void waitToFinish() {
+ Runnable toFinish;
+ while ((toFinish = sPendingWorkFinishers.poll()) != null) {
+ toFinish.run();
+ }
+ }
+}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 697a987d5f20..00063af78836 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -400,7 +400,15 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
* start_compatibility}
- *
+ *
+ * <p class="caution">Note that the system calls this on your
+ * service's main thread. A service's main thread is the same
+ * thread where UI operations take place for Activities running in the
+ * same process. You should always avoid stalling the main
+ * thread's event loop. When doing long-running operations,
+ * network calls, or heavy disk I/O, you should kick off a new
+ * thread, or use {@link android.os.AsyncTask}.</p>
+ *
* @param intent The Intent supplied to {@link android.content.Context#startService},
* as given. This may be null if the service is being restarted after
* its process has gone away, and it had previously returned anything
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 72ec6168cfa2..de544fb5485f 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -23,6 +23,8 @@ import android.os.RemoteException;
import android.os.IBinder;
import android.os.ServiceManager;
+import com.android.internal.statusbar.IStatusBarService;
+
/**
* Allows an app to control the status bar.
*
@@ -58,12 +60,12 @@ public class StatusBarManager {
public static final int DISABLE_NONE = 0x00000000;
private Context mContext;
- private IStatusBar mService;
+ private IStatusBarService mService;
private IBinder mToken = new Binder();
StatusBarManager(Context context) {
mContext = context;
- mService = IStatusBar.Stub.asInterface(
+ mService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
@@ -85,7 +87,7 @@ public class StatusBarManager {
*/
public void expand() {
try {
- mService.activate();
+ mService.expand();
} catch (RemoteException ex) {
// system process is dead anyway.
throw new RuntimeException(ex);
@@ -97,46 +99,34 @@ public class StatusBarManager {
*/
public void collapse() {
try {
- mService.deactivate();
- } catch (RemoteException ex) {
- // system process is dead anyway.
- throw new RuntimeException(ex);
- }
- }
-
- /**
- * Toggle the status bar.
- */
- public void toggle() {
- try {
- mService.toggle();
+ mService.collapse();
} catch (RemoteException ex) {
// system process is dead anyway.
throw new RuntimeException(ex);
}
}
- public IBinder addIcon(String slot, int iconId, int iconLevel) {
+ public void setIcon(String slot, int iconId, int iconLevel) {
try {
- return mService.addIcon(slot, mContext.getPackageName(), iconId, iconLevel);
+ mService.setIcon(slot, mContext.getPackageName(), iconId, iconLevel);
} catch (RemoteException ex) {
// system process is dead anyway.
throw new RuntimeException(ex);
}
}
- public void updateIcon(IBinder key, String slot, int iconId, int iconLevel) {
+ public void removeIcon(String slot) {
try {
- mService.updateIcon(key, slot, mContext.getPackageName(), iconId, iconLevel);
+ mService.removeIcon(slot);
} catch (RemoteException ex) {
// system process is dead anyway.
throw new RuntimeException(ex);
}
}
- public void removeIcon(IBinder key) {
+ public void setIconVisibility(String slot, boolean visible) {
try {
- mService.removeIcon(key);
+ mService.setIconVisibility(slot, visible);
} catch (RemoteException ex) {
// system process is dead anyway.
throw new RuntimeException(ex);
diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
index 23b170360daa..213bd31698de 100644
--- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.app.QueuedWork;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.ParcelFileDescriptor;
@@ -94,7 +95,11 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
Context context = mContext;
-
+
+ // If a SharedPreference has an outstanding write in flight,
+ // wait for it to finish flushing to disk.
+ QueuedWork.waitToFinish();
+
// make filenames for the prefGroups
String[] prefGroups = mPrefGroups;
final int N = prefGroups.length;
@@ -123,4 +128,3 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen
}
}
}
-
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 88adabd042b3..b2fc13ffba6c 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -237,7 +237,7 @@ public class AppWidgetHost {
v = mViews.get(appWidgetId);
}
if (v != null) {
- v.updateAppWidget(null, AppWidgetHostView.UPDATE_FLAGS_RESET);
+ v.resetAppWidget(appWidget);
}
}
@@ -247,7 +247,7 @@ public class AppWidgetHost {
v = mViews.get(appWidgetId);
}
if (v != null) {
- v.updateAppWidget(views, 0);
+ v.updateAppWidget(views);
}
}
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 5375193d823f..b33b0970d825 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -46,8 +46,6 @@ public class AppWidgetHostView extends FrameLayout {
static final boolean LOGD = false;
static final boolean CROSSFADE = false;
- static final int UPDATE_FLAGS_RESET = 0x00000001;
-
static final int VIEW_MODE_NOINIT = 0;
static final int VIEW_MODE_CONTENT = 1;
static final int VIEW_MODE_ERROR = 2;
@@ -102,7 +100,7 @@ public class AppWidgetHostView extends FrameLayout {
mAppWidgetId = appWidgetId;
mInfo = info;
}
-
+
public int getAppWidgetId() {
return mAppWidgetId;
}
@@ -148,21 +146,22 @@ public class AppWidgetHostView extends FrameLayout {
}
/**
+ * Update the AppWidgetProviderInfo for this view, and reset it to the
+ * initial layout.
+ */
+ void resetAppWidget(AppWidgetProviderInfo info) {
+ mInfo = info;
+ mViewMode = VIEW_MODE_NOINIT;
+ updateAppWidget(null);
+ }
+
+ /**
* Process a set of {@link RemoteViews} coming in as an update from the
* AppWidget provider. Will animate into these new views as needed
*/
public void updateAppWidget(RemoteViews remoteViews) {
- updateAppWidget(remoteViews, 0);
- }
+ if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld);
- void updateAppWidget(RemoteViews remoteViews, int flags) {
- if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld + " flags=0x"
- + Integer.toHexString(flags));
-
- if ((flags & UPDATE_FLAGS_RESET) != 0) {
- mViewMode = VIEW_MODE_NOINIT;
- }
-
boolean recycled = false;
View content = null;
Exception exception = null;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8eda844380e0..16a8c571f41c 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -26,8 +26,10 @@ import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.Pair;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
@@ -468,12 +470,17 @@ public final class BluetoothAdapter {
* <p>Valid Bluetooth names are a maximum of 248 UTF-8 characters, however
* many remote devices can only display the first 40 characters, and some
* may be limited to just 20.
+ * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+ * will return false. After turning on Bluetooth,
+ * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+ * to get the updated value.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
* @param name a valid Bluetooth name
* @return true if the name was set, false otherwise
*/
public boolean setName(String name) {
+ if (getState() != STATE_ON) return false;
try {
return mService.setName(name);
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -488,11 +495,16 @@ public final class BluetoothAdapter {
* {@link #SCAN_MODE_NONE},
* {@link #SCAN_MODE_CONNECTABLE},
* {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
+ * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+ * will return {@link #SCAN_MODE_NONE}. After turning on Bluetooth,
+ * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+ * to get the updated value.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @return scan mode
*/
public int getScanMode() {
+ if (getState() != STATE_ON) return SCAN_MODE_NONE;
try {
return mService.getScanMode();
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -511,6 +523,10 @@ public final class BluetoothAdapter {
* {@link #SCAN_MODE_NONE},
* {@link #SCAN_MODE_CONNECTABLE},
* {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
+ * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+ * will return false. After turning on Bluetooth,
+ * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+ * to get the updated value.
* <p>Requires {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}
* <p>Applications cannot set the scan mode. They should use
* <code>startActivityForResult(
@@ -524,6 +540,7 @@ public final class BluetoothAdapter {
* @hide
*/
public boolean setScanMode(int mode, int duration) {
+ if (getState() != STATE_ON) return false;
try {
return mService.setScanMode(mode, duration);
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -532,11 +549,13 @@ public final class BluetoothAdapter {
/** @hide */
public boolean setScanMode(int mode) {
+ if (getState() != STATE_ON) return false;
return setScanMode(mode, 120);
}
/** @hide */
public int getDiscoverableTimeout() {
+ if (getState() != STATE_ON) return -1;
try {
return mService.getDiscoverableTimeout();
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -545,6 +564,7 @@ public final class BluetoothAdapter {
/** @hide */
public void setDiscoverableTimeout(int timeout) {
+ if (getState() != STATE_ON) return;
try {
mService.setDiscoverableTimeout(timeout);
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -572,11 +592,16 @@ public final class BluetoothAdapter {
* <p>Device discovery will only find remote devices that are currently
* <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are
* not discoverable by default, and need to be entered into a special mode.
+ * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+ * will return false. After turning on Bluetooth,
+ * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+ * to get the updated value.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
* @return true on success, false on error
*/
public boolean startDiscovery() {
+ if (getState() != STATE_ON) return false;
try {
return mService.startDiscovery();
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -593,10 +618,15 @@ public final class BluetoothAdapter {
* the Activity, but is run as a system service, so an application should
* always call cancel discovery even if it did not directly request a
* discovery, just to be sure.
+ * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+ * will return false. After turning on Bluetooth,
+ * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+ * to get the updated value.
*
* @return true on success, false on error
*/
public boolean cancelDiscovery() {
+ if (getState() != STATE_ON) return false;
try {
mService.cancelDiscovery();
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -614,11 +644,16 @@ public final class BluetoothAdapter {
* <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED}
* or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery
* starts or completes.
+ * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+ * will return false. After turning on Bluetooth,
+ * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+ * to get the updated value.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
*
* @return true if discovering
*/
public boolean isDiscovering() {
+ if (getState() != STATE_ON) return false;
try {
return mService.isDiscovering();
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -628,11 +663,18 @@ public final class BluetoothAdapter {
/**
* Return the set of {@link BluetoothDevice} objects that are bonded
* (paired) to the local adapter.
+ * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+ * will return an empty set. After turning on Bluetooth,
+ * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+ * to get the updated value.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
*
* @return unmodifiable set of {@link BluetoothDevice}, or null on error
*/
public Set<BluetoothDevice> getBondedDevices() {
+ if (getState() != STATE_ON) {
+ return toDeviceSet(new String[0]);
+ }
try {
return toDeviceSet(mService.listBonds());
} catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -823,6 +865,37 @@ public final class BluetoothAdapter {
return socket;
}
+ /**
+ * Read the local Out of Band Pairing Data
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+ *
+ * @return Pair<byte[], byte[]> of Hash and Randomizer
+ *
+ * @hide
+ */
+ public Pair<byte[], byte[]> readOutOfBandData() {
+ if (getState() != STATE_ON) return null;
+ try {
+ byte[] hash = new byte[16];
+ byte[] randomizer = new byte[16];
+
+ byte[] ret = mService.readOutOfBandData();
+
+ if (ret == null || ret.length != 32) return null;
+
+ hash = Arrays.copyOfRange(ret, 0, 16);
+ randomizer = Arrays.copyOfRange(ret, 16, 32);
+
+ if (DBG) {
+ Log.d(TAG, "readOutOfBandData:" + Arrays.toString(hash) +
+ ":" + Arrays.toString(randomizer));
+ }
+ return new Pair<byte[], byte[]>(hash, randomizer);
+
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+
private Set<BluetoothDevice> toDeviceSet(String[] addresses) {
Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length);
for (int i = 0; i < addresses.length; i++) {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index e77e76f79b2c..e577ec4f737c 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -325,7 +325,9 @@ public final class BluetoothDevice implements Parcelable {
/** The user will be prompted to enter the passkey displayed on remote device
* @hide */
public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
-
+ /** The user will be prompted to accept or deny the OOB pairing request
+ * @hide */
+ public static final int PAIRING_VARIANT_OOB_CONSENT = 5;
/**
* Used as an extra field in {@link #ACTION_UUID} intents,
* Contains the {@link android.os.ParcelUuid}s of the remote device which
@@ -464,6 +466,52 @@ public final class BluetoothDevice implements Parcelable {
}
/**
+ * Start the bonding (pairing) process with the remote device using the
+ * Out Of Band mechanism.
+ *
+ * <p>This is an asynchronous call, it will return immediately. Register
+ * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
+ * the bonding process completes, and its result.
+ *
+ * <p>Android system services will handle the necessary user interactions
+ * to confirm and complete the bonding process.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+ *
+ * @param hash - Simple Secure pairing hash
+ * @param randomizer - The random key obtained using OOB
+ * @return false on immediate error, true if bonding will begin
+ *
+ * @hide
+ */
+ public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) {
+ try {
+ return sService.createBondOutOfBand(mAddress, hash, randomizer);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Set the Out Of Band data for a remote device to be used later
+ * in the pairing mechanism. Users can obtain this data through other
+ * trusted channels
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+ *
+ * @param hash Simple Secure pairing hash
+ * @param randomizer The random key obtained using OOB
+ * @return false on error; true otherwise
+ *
+ * @hide
+ */
+ public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
+ try {
+ return sService.setDeviceOutOfBandData(mAddress, hash, randomizer);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
* Cancel an in-progress bonding request started with {@link #createBond}.
* <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
@@ -617,6 +665,14 @@ public final class BluetoothDevice implements Parcelable {
}
/** @hide */
+ public boolean setRemoteOutOfBandData() {
+ try {
+ return sService.setRemoteOutOfBandData(mAddress);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /** @hide */
public boolean cancelPairingUserInput() {
try {
return sService.cancelPairingUserInput(mAddress);
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
new file mode 100644
index 000000000000..8e655e213d62
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -0,0 +1,665 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Message;
+import android.server.BluetoothA2dpService;
+import android.server.BluetoothService;
+import android.util.Log;
+
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
+
+/**
+ * This class is the Profile connection state machine associated with a remote
+ * device. When the device bonds an instance of this class is created.
+ * This tracks incoming and outgoing connections of all the profiles. Incoming
+ * connections are preferred over outgoing connections and HFP preferred over
+ * A2DP. When the device is unbonded, the instance is removed.
+ *
+ * States:
+ * {@link BondedDevice}: This state represents a bonded device. When in this
+ * state none of the profiles are in transition states.
+ *
+ * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition
+ * state because of a outgoing Connect or Disconnect.
+ *
+ * {@link IncomingHandsfree}: Handsfree profile connection is in a transition
+ * state because of a incoming Connect or Disconnect.
+ *
+ * {@link IncomingA2dp}: A2dp profile connection is in a transition
+ * state because of a incoming Connect or Disconnect.
+ *
+ * {@link OutgoingA2dp}: A2dp profile connection is in a transition
+ * state because of a outgoing Connect or Disconnect.
+ *
+ * Todo(): Write tests for this class, when the Android Mock support is completed.
+ * @hide
+ */
+public final class BluetoothDeviceProfileState extends HierarchicalStateMachine {
+ private static final String TAG = "BluetoothDeviceProfileState";
+ private static final boolean DBG = true; //STOPSHIP - Change to false
+
+ public static final int CONNECT_HFP_OUTGOING = 1;
+ public static final int CONNECT_HFP_INCOMING = 2;
+ public static final int CONNECT_A2DP_OUTGOING = 3;
+ public static final int CONNECT_A2DP_INCOMING = 4;
+
+ public static final int DISCONNECT_HFP_OUTGOING = 5;
+ private static final int DISCONNECT_HFP_INCOMING = 6;
+ public static final int DISCONNECT_A2DP_OUTGOING = 7;
+ public static final int DISCONNECT_A2DP_INCOMING = 8;
+
+ public static final int UNPAIR = 9;
+ public static final int AUTO_CONNECT_PROFILES = 10;
+ public static final int TRANSITION_TO_STABLE = 11;
+
+ private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs
+
+ private BondedDevice mBondedDevice = new BondedDevice();
+ private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree();
+ private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree();
+ private IncomingA2dp mIncomingA2dp = new IncomingA2dp();
+ private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp();
+
+ private Context mContext;
+ private BluetoothService mService;
+ private BluetoothA2dpService mA2dpService;
+ private BluetoothHeadset mHeadsetService;
+ private boolean mHeadsetServiceConnected;
+
+ private BluetoothDevice mDevice;
+ private int mHeadsetState;
+ private int mA2dpState;
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (!device.equals(mDevice)) return;
+
+ if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
+ int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
+ int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0);
+ int initiator = intent.getIntExtra(
+ BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR,
+ BluetoothHeadset.LOCAL_DISCONNECT);
+ mHeadsetState = newState;
+ if (newState == BluetoothHeadset.STATE_DISCONNECTED &&
+ initiator == BluetoothHeadset.REMOTE_DISCONNECT) {
+ sendMessage(DISCONNECT_HFP_INCOMING);
+ }
+ if (newState == BluetoothHeadset.STATE_CONNECTED ||
+ newState == BluetoothHeadset.STATE_DISCONNECTED) {
+ sendMessage(TRANSITION_TO_STABLE);
+ }
+ } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
+ int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
+ int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0);
+ mA2dpState = newState;
+ if ((oldState == BluetoothA2dp.STATE_CONNECTED ||
+ oldState == BluetoothA2dp.STATE_PLAYING) &&
+ newState == BluetoothA2dp.STATE_DISCONNECTED) {
+ sendMessage(DISCONNECT_A2DP_INCOMING);
+ }
+ if (newState == BluetoothA2dp.STATE_CONNECTED ||
+ newState == BluetoothA2dp.STATE_DISCONNECTED) {
+ sendMessage(TRANSITION_TO_STABLE);
+ }
+ } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
+ if (!getCurrentState().equals(mBondedDevice)) {
+ Log.e(TAG, "State is: " + getCurrentState());
+ return;
+ }
+ Message msg = new Message();
+ msg.what = AUTO_CONNECT_PROFILES;
+ sendMessageDelayed(msg, AUTO_CONNECT_DELAY);
+ }
+ }
+ };
+
+ private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) {
+ // This works only because these broadcast intents are "sticky"
+ Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ if (i != null) {
+ int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (device != null && autoConnectDevice.equals(device)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public BluetoothDeviceProfileState(Context context, String address,
+ BluetoothService service, BluetoothA2dpService a2dpService) {
+ super(address);
+ mContext = context;
+ mDevice = new BluetoothDevice(address);
+ mService = service;
+ mA2dpService = a2dpService;
+
+ addState(mBondedDevice);
+ addState(mOutgoingHandsfree);
+ addState(mIncomingHandsfree);
+ addState(mIncomingA2dp);
+ addState(mOutgoingA2dp);
+ setInitialState(mBondedDevice);
+
+ IntentFilter filter = new IntentFilter();
+ // Fine-grained state broadcasts
+ filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
+
+ mContext.registerReceiver(mBroadcastReceiver, filter);
+
+ HeadsetServiceListener l = new HeadsetServiceListener();
+ }
+
+ private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener {
+ public HeadsetServiceListener() {
+ mHeadsetService = new BluetoothHeadset(mContext, this);
+ }
+ public void onServiceConnected() {
+ synchronized(BluetoothDeviceProfileState.this) {
+ mHeadsetServiceConnected = true;
+ }
+ }
+ public void onServiceDisconnected() {
+ synchronized(BluetoothDeviceProfileState.this) {
+ mHeadsetServiceConnected = false;
+ }
+ }
+ }
+
+ private class BondedDevice extends HierarchicalState {
+ @Override
+ protected void enter() {
+ log("Entering ACL Connected state with: " + getCurrentMessage().what);
+ Message m = new Message();
+ m.copyFrom(getCurrentMessage());
+ sendMessageAtFrontOfQueue(m);
+ }
+ @Override
+ protected boolean processMessage(Message message) {
+ log("ACL Connected State -> Processing Message: " + message.what);
+ switch(message.what) {
+ case CONNECT_HFP_OUTGOING:
+ case DISCONNECT_HFP_OUTGOING:
+ transitionTo(mOutgoingHandsfree);
+ break;
+ case CONNECT_HFP_INCOMING:
+ transitionTo(mIncomingHandsfree);
+ break;
+ case DISCONNECT_HFP_INCOMING:
+ transitionTo(mIncomingHandsfree);
+ break;
+ case CONNECT_A2DP_OUTGOING:
+ case DISCONNECT_A2DP_OUTGOING:
+ transitionTo(mOutgoingA2dp);
+ break;
+ case CONNECT_A2DP_INCOMING:
+ case DISCONNECT_A2DP_INCOMING:
+ transitionTo(mIncomingA2dp);
+ break;
+ case UNPAIR:
+ if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) {
+ sendMessage(DISCONNECT_HFP_OUTGOING);
+ deferMessage(message);
+ break;
+ } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) {
+ sendMessage(DISCONNECT_A2DP_OUTGOING);
+ deferMessage(message);
+ break;
+ }
+ processCommand(UNPAIR);
+ break;
+ case AUTO_CONNECT_PROFILES:
+ if (isPhoneDocked(mDevice)) {
+ // Don't auto connect to docks.
+ break;
+ } else if (!mHeadsetServiceConnected) {
+ deferMessage(message);
+ } else {
+ if (mHeadsetService.getPriority(mDevice) ==
+ BluetoothHeadset.PRIORITY_AUTO_CONNECT &&
+ !mHeadsetService.isConnected(mDevice)) {
+ mHeadsetService.connectHeadset(mDevice);
+ }
+ if (mA2dpService != null &&
+ mA2dpService.getSinkPriority(mDevice) ==
+ BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
+ mA2dpService.getConnectedSinks().length == 0) {
+ mA2dpService.connectSink(mDevice);
+ }
+ }
+ break;
+ case TRANSITION_TO_STABLE:
+ // ignore.
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class OutgoingHandsfree extends HierarchicalState {
+ private boolean mStatus = false;
+ private int mCommand;
+
+ @Override
+ protected void enter() {
+ log("Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
+ mCommand = getCurrentMessage().what;
+ if (mCommand != CONNECT_HFP_OUTGOING &&
+ mCommand != DISCONNECT_HFP_OUTGOING) {
+ Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand);
+ }
+ mStatus = processCommand(mCommand);
+ if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+ }
+
+ @Override
+ protected boolean processMessage(Message message) {
+ log("OutgoingHandsfree State -> Processing Message: " + message.what);
+ Message deferMsg = new Message();
+ int command = message.what;
+ switch(command) {
+ case CONNECT_HFP_OUTGOING:
+ if (command != mCommand) {
+ // Disconnect followed by a connect - defer
+ deferMessage(message);
+ }
+ break;
+ case CONNECT_HFP_INCOMING:
+ if (mCommand == CONNECT_HFP_OUTGOING) {
+ // Cancel outgoing connect, accept incoming
+ cancelCommand(CONNECT_HFP_OUTGOING);
+ transitionTo(mIncomingHandsfree);
+ } else {
+ // We have done the disconnect but we are not
+ // sure which state we are in at this point.
+ deferMessage(message);
+ }
+ break;
+ case CONNECT_A2DP_INCOMING:
+ // accept incoming A2DP, retry HFP_OUTGOING
+ transitionTo(mIncomingA2dp);
+
+ if (mStatus) {
+ deferMsg.what = mCommand;
+ deferMessage(deferMsg);
+ }
+ break;
+ case CONNECT_A2DP_OUTGOING:
+ deferMessage(message);
+ break;
+ case DISCONNECT_HFP_OUTGOING:
+ if (mCommand == CONNECT_HFP_OUTGOING) {
+ // Cancel outgoing connect
+ cancelCommand(CONNECT_HFP_OUTGOING);
+ processCommand(DISCONNECT_HFP_OUTGOING);
+ }
+ // else ignore
+ break;
+ case DISCONNECT_HFP_INCOMING:
+ // When this happens the socket would be closed and the headset
+ // state moved to DISCONNECTED, cancel the outgoing thread.
+ // if it still is in CONNECTING state
+ cancelCommand(CONNECT_HFP_OUTGOING);
+ break;
+ case DISCONNECT_A2DP_OUTGOING:
+ deferMessage(message);
+ break;
+ case DISCONNECT_A2DP_INCOMING:
+ // Bluez will handle the disconnect. If because of this the outgoing
+ // handsfree connection has failed, then retry.
+ if (mStatus) {
+ deferMsg.what = mCommand;
+ deferMessage(deferMsg);
+ }
+ break;
+ case UNPAIR:
+ case AUTO_CONNECT_PROFILES:
+ deferMessage(message);
+ break;
+ case TRANSITION_TO_STABLE:
+ transitionTo(mBondedDevice);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class IncomingHandsfree extends HierarchicalState {
+ private boolean mStatus = false;
+ private int mCommand;
+
+ @Override
+ protected void enter() {
+ log("Entering IncomingHandsfree state with: " + getCurrentMessage().what);
+ mCommand = getCurrentMessage().what;
+ if (mCommand != CONNECT_HFP_INCOMING &&
+ mCommand != DISCONNECT_HFP_INCOMING) {
+ Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand);
+ }
+ mStatus = processCommand(mCommand);
+ if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+ }
+
+ @Override
+ protected boolean processMessage(Message message) {
+ log("IncomingHandsfree State -> Processing Message: " + message.what);
+ switch(message.what) {
+ case CONNECT_HFP_OUTGOING:
+ deferMessage(message);
+ break;
+ case CONNECT_HFP_INCOMING:
+ // Ignore
+ Log.e(TAG, "Error: Incoming connection with a pending incoming connection");
+ break;
+ case CONNECT_A2DP_INCOMING:
+ // Serialize the commands.
+ deferMessage(message);
+ break;
+ case CONNECT_A2DP_OUTGOING:
+ deferMessage(message);
+ break;
+ case DISCONNECT_HFP_OUTGOING:
+ // We don't know at what state we are in the incoming HFP connection state.
+ // We can be changing from DISCONNECTED to CONNECTING, or
+ // from CONNECTING to CONNECTED, so serializing this command is
+ // the safest option.
+ deferMessage(message);
+ break;
+ case DISCONNECT_HFP_INCOMING:
+ // Nothing to do here, we will already be DISCONNECTED
+ // by this point.
+ break;
+ case DISCONNECT_A2DP_OUTGOING:
+ deferMessage(message);
+ break;
+ case DISCONNECT_A2DP_INCOMING:
+ // Bluez handles incoming A2DP disconnect.
+ // If this causes incoming HFP to fail, it is more of a headset problem
+ // since both connections are incoming ones.
+ break;
+ case UNPAIR:
+ case AUTO_CONNECT_PROFILES:
+ deferMessage(message);
+ break;
+ case TRANSITION_TO_STABLE:
+ transitionTo(mBondedDevice);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class OutgoingA2dp extends HierarchicalState {
+ private boolean mStatus = false;
+ private int mCommand;
+
+ @Override
+ protected void enter() {
+ log("Entering OutgoingA2dp state with: " + getCurrentMessage().what);
+ mCommand = getCurrentMessage().what;
+ if (mCommand != CONNECT_A2DP_OUTGOING &&
+ mCommand != DISCONNECT_A2DP_OUTGOING) {
+ Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand);
+ }
+ mStatus = processCommand(mCommand);
+ if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+ }
+
+ @Override
+ protected boolean processMessage(Message message) {
+ log("OutgoingA2dp State->Processing Message: " + message.what);
+ Message deferMsg = new Message();
+ switch(message.what) {
+ case CONNECT_HFP_OUTGOING:
+ processCommand(CONNECT_HFP_OUTGOING);
+
+ // Don't cancel A2DP outgoing as there is no guarantee it
+ // will get canceled.
+ // It might already be connected but we might not have got the
+ // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here.
+ // The worst case, the connection will fail, retry.
+ // The same applies to Disconnecting an A2DP connection.
+ if (mStatus) {
+ deferMsg.what = mCommand;
+ deferMessage(deferMsg);
+ }
+ break;
+ case CONNECT_HFP_INCOMING:
+ processCommand(CONNECT_HFP_INCOMING);
+
+ // Don't cancel A2DP outgoing as there is no guarantee
+ // it will get canceled.
+ // The worst case, the connection will fail, retry.
+ if (mStatus) {
+ deferMsg.what = mCommand;
+ deferMessage(deferMsg);
+ }
+ break;
+ case CONNECT_A2DP_INCOMING:
+ // Bluez will take care of conflicts between incoming and outgoing
+ // connections.
+ transitionTo(mIncomingA2dp);
+ break;
+ case CONNECT_A2DP_OUTGOING:
+ // Ignore
+ break;
+ case DISCONNECT_HFP_OUTGOING:
+ deferMessage(message);
+ break;
+ case DISCONNECT_HFP_INCOMING:
+ // At this point, we are already disconnected
+ // with HFP. Sometimes A2DP connection can
+ // fail due to the disconnection of HFP. So add a retry
+ // for the A2DP.
+ if (mStatus) {
+ deferMsg.what = mCommand;
+ deferMessage(deferMsg);
+ }
+ break;
+ case DISCONNECT_A2DP_OUTGOING:
+ processCommand(DISCONNECT_A2DP_OUTGOING);
+ break;
+ case DISCONNECT_A2DP_INCOMING:
+ // Ignore, will be handled by Bluez
+ break;
+ case UNPAIR:
+ case AUTO_CONNECT_PROFILES:
+ deferMessage(message);
+ break;
+ case TRANSITION_TO_STABLE:
+ transitionTo(mBondedDevice);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ private class IncomingA2dp extends HierarchicalState {
+ private boolean mStatus = false;
+ private int mCommand;
+
+ @Override
+ protected void enter() {
+ log("Entering IncomingA2dp state with: " + getCurrentMessage().what);
+ mCommand = getCurrentMessage().what;
+ if (mCommand != CONNECT_A2DP_INCOMING &&
+ mCommand != DISCONNECT_A2DP_INCOMING) {
+ Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand);
+ }
+ mStatus = processCommand(mCommand);
+ if (!mStatus) sendMessage(TRANSITION_TO_STABLE);
+ }
+
+ @Override
+ protected boolean processMessage(Message message) {
+ log("IncomingA2dp State->Processing Message: " + message.what);
+ Message deferMsg = new Message();
+ switch(message.what) {
+ case CONNECT_HFP_OUTGOING:
+ deferMessage(message);
+ break;
+ case CONNECT_HFP_INCOMING:
+ // Shouldn't happen, but serialize the commands.
+ deferMessage(message);
+ break;
+ case CONNECT_A2DP_INCOMING:
+ // ignore
+ break;
+ case CONNECT_A2DP_OUTGOING:
+ // Defer message and retry
+ deferMessage(message);
+ break;
+ case DISCONNECT_HFP_OUTGOING:
+ deferMessage(message);
+ break;
+ case DISCONNECT_HFP_INCOMING:
+ // Shouldn't happen but if does, we can handle it.
+ // Depends if the headset can handle it.
+ // Incoming A2DP will be handled by Bluez, Disconnect HFP
+ // the socket would have already been closed.
+ // ignore
+ break;
+ case DISCONNECT_A2DP_OUTGOING:
+ deferMessage(message);
+ break;
+ case DISCONNECT_A2DP_INCOMING:
+ // Ignore, will be handled by Bluez
+ break;
+ case UNPAIR:
+ case AUTO_CONNECT_PROFILES:
+ deferMessage(message);
+ break;
+ case TRANSITION_TO_STABLE:
+ transitionTo(mBondedDevice);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+
+
+ synchronized void cancelCommand(int command) {
+ if (command == CONNECT_HFP_OUTGOING ) {
+ // Cancel the outgoing thread.
+ if (mHeadsetServiceConnected) {
+ mHeadsetService.cancelConnectThread();
+ }
+ // HeadsetService is down. Phone process most likely crashed.
+ // The thread would have got killed.
+ }
+ }
+
+ synchronized void deferHeadsetMessage(int command) {
+ Message msg = new Message();
+ msg.what = command;
+ deferMessage(msg);
+ }
+
+ synchronized boolean processCommand(int command) {
+ log("Processing command:" + command);
+ switch(command) {
+ case CONNECT_HFP_OUTGOING:
+ if (mHeadsetService != null) {
+ return mHeadsetService.connectHeadsetInternal(mDevice);
+ }
+ break;
+ case CONNECT_HFP_INCOMING:
+ if (!mHeadsetServiceConnected) {
+ deferHeadsetMessage(command);
+ } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) {
+ return mHeadsetService.acceptIncomingConnect(mDevice);
+ } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) {
+ return mHeadsetService.createIncomingConnect(mDevice);
+ }
+ break;
+ case CONNECT_A2DP_OUTGOING:
+ if (mA2dpService != null) {
+ return mA2dpService.connectSinkInternal(mDevice);
+ }
+ break;
+ case CONNECT_A2DP_INCOMING:
+ // ignore, Bluez takes care
+ return true;
+ case DISCONNECT_HFP_OUTGOING:
+ if (!mHeadsetServiceConnected) {
+ deferHeadsetMessage(command);
+ } else {
+ if (mHeadsetService.getPriority(mDevice) ==
+ BluetoothHeadset.PRIORITY_AUTO_CONNECT) {
+ mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
+ }
+ return mHeadsetService.disconnectHeadsetInternal(mDevice);
+ }
+ break;
+ case DISCONNECT_HFP_INCOMING:
+ // ignore
+ return true;
+ case DISCONNECT_A2DP_INCOMING:
+ // ignore
+ return true;
+ case DISCONNECT_A2DP_OUTGOING:
+ if (mA2dpService != null) {
+ if (mA2dpService.getSinkPriority(mDevice) ==
+ BluetoothA2dp.PRIORITY_AUTO_CONNECT) {
+ mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON);
+ }
+ return mA2dpService.disconnectSinkInternal(mDevice);
+ }
+ break;
+ case UNPAIR:
+ return mService.removeBondInternal(mDevice.getAddress());
+ default:
+ Log.e(TAG, "Error: Unknown Command");
+ }
+ return false;
+ }
+
+ /*package*/ BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ private void log(String message) {
+ if (DBG) {
+ Log.i(TAG, "Device:" + mDevice + " Message:" + message);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 95e61b6f6ace..4a91a8c90721 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -189,11 +189,11 @@ public final class BluetoothHeadset {
* @return One of the STATE_ return codes, or STATE_ERROR if this proxy
* object is currently not connected to the Headset service.
*/
- public int getState() {
+ public int getState(BluetoothDevice device) {
if (DBG) log("getState()");
if (mService != null) {
try {
- return mService.getState();
+ return mService.getState(device);
} catch (RemoteException e) {Log.e(TAG, e.toString());}
} else {
Log.w(TAG, "Proxy not attached to service");
@@ -271,11 +271,11 @@ public final class BluetoothHeadset {
* be made asynchornous. Returns false if this proxy object is
* not currently connected to the Headset service.
*/
- public boolean disconnectHeadset() {
+ public boolean disconnectHeadset(BluetoothDevice device) {
if (DBG) log("disconnectHeadset()");
if (mService != null) {
try {
- mService.disconnectHeadset();
+ mService.disconnectHeadset(device);
return true;
} catch (RemoteException e) {Log.e(TAG, e.toString());}
} else {
@@ -395,7 +395,6 @@ public final class BluetoothHeadset {
}
return -1;
}
-
/**
* Indicates if current platform supports voice dialing over bluetooth SCO.
* @return true if voice dialing over bluetooth is supported, false otherwise.
@@ -406,6 +405,92 @@ public final class BluetoothHeadset {
com.android.internal.R.bool.config_bluetooth_sco_off_call);
}
+ /**
+ * Cancel the outgoing connection.
+ * @hide
+ */
+ public boolean cancelConnectThread() {
+ if (DBG) log("cancelConnectThread");
+ if (mService != null) {
+ try {
+ return mService.cancelConnectThread();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Accept the incoming connection.
+ * @hide
+ */
+ public boolean acceptIncomingConnect(BluetoothDevice device) {
+ if (DBG) log("acceptIncomingConnect");
+ if (mService != null) {
+ try {
+ return mService.acceptIncomingConnect(device);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Create the connect thread the incoming connection.
+ * @hide
+ */
+ public boolean createIncomingConnect(BluetoothDevice device) {
+ if (DBG) log("createIncomingConnect");
+ if (mService != null) {
+ try {
+ return mService.createIncomingConnect(device);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Connect to a Bluetooth Headset.
+ * Note: This is an internal function and shouldn't be exposed
+ * @hide
+ */
+ public boolean connectHeadsetInternal(BluetoothDevice device) {
+ if (DBG) log("connectHeadsetInternal");
+ if (mService != null) {
+ try {
+ return mService.connectHeadsetInternal(device);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Disconnect a Bluetooth Headset.
+ * Note: This is an internal function and shouldn't be exposed
+ * @hide
+ */
+ public boolean disconnectHeadsetInternal(BluetoothDevice device) {
+ if (DBG) log("disconnectHeadsetInternal");
+ if (mService != null) {
+ try {
+ return mService.disconnectHeadsetInternal(device);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if (DBG) Log.d(TAG, "Proxy object connected");
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
new file mode 100644
index 000000000000..946dcaa01c86
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothProfileState.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bluetooth;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
+
+/**
+ * This state machine is used to serialize the connections
+ * to a particular profile. Currently, we only allow one device
+ * to be connected to a particular profile.
+ * States:
+ * {@link StableState} : No pending commands. Send the
+ * command to the appropriate remote device specific state machine.
+ *
+ * {@link PendingCommandState} : A profile connection / disconnection
+ * command is being executed. This will result in a profile state
+ * change. Defer all commands.
+ * @hide
+ */
+
+public class BluetoothProfileState extends HierarchicalStateMachine {
+ private static final boolean DBG = true; // STOPSHIP - change to false.
+ private static final String TAG = "BluetoothProfileState";
+
+ public static int HFP = 0;
+ public static int A2DP = 1;
+
+ private static int TRANSITION_TO_STABLE = 100;
+
+ private int mProfile;
+ private BluetoothDevice mPendingDevice;
+ private PendingCommandState mPendingCommandState = new PendingCommandState();
+ private StableState mStableState = new StableState();
+
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) {
+ int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0);
+ if (mProfile == HFP && (newState == BluetoothHeadset.STATE_CONNECTED ||
+ newState == BluetoothHeadset.STATE_DISCONNECTED)) {
+ sendMessage(TRANSITION_TO_STABLE);
+ }
+ } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) {
+ int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0);
+ if (mProfile == A2DP && (newState == BluetoothA2dp.STATE_CONNECTED ||
+ newState == BluetoothA2dp.STATE_DISCONNECTED)) {
+ sendMessage(TRANSITION_TO_STABLE);
+ }
+ }
+ }
+ };
+
+ public BluetoothProfileState(Context context, int profile) {
+ super("BluetoothProfileState:" + profile);
+ mProfile = profile;
+ addState(mStableState);
+ addState(mPendingCommandState);
+ setInitialState(mStableState);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
+ context.registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ private class StableState extends HierarchicalState {
+ @Override
+ protected void enter() {
+ log("Entering Stable State");
+ mPendingDevice = null;
+ }
+
+ @Override
+ protected boolean processMessage(Message msg) {
+ if (msg.what != TRANSITION_TO_STABLE) {
+ transitionTo(mPendingCommandState);
+ }
+ return true;
+ }
+ }
+
+ private class PendingCommandState extends HierarchicalState {
+ @Override
+ protected void enter() {
+ log("Entering PendingCommandState State");
+ dispatchMessage(getCurrentMessage());
+ }
+
+ @Override
+ protected boolean processMessage(Message msg) {
+ if (msg.what == TRANSITION_TO_STABLE) {
+ transitionTo(mStableState);
+ } else {
+ dispatchMessage(msg);
+ }
+ return true;
+ }
+
+ private void dispatchMessage(Message msg) {
+ BluetoothDeviceProfileState deviceProfileMgr =
+ (BluetoothDeviceProfileState)msg.obj;
+ int cmd = msg.arg1;
+ if (mPendingDevice == null || mPendingDevice.equals(deviceProfileMgr.getDevice())) {
+ mPendingDevice = deviceProfileMgr.getDevice();
+ deviceProfileMgr.sendMessage(cmd);
+ } else {
+ Message deferMsg = new Message();
+ deferMsg.arg1 = cmd;
+ deferMsg.obj = deviceProfileMgr;
+ deferMessage(deferMsg);
+ }
+ }
+ }
+
+ private void log(String message) {
+ if (DBG) {
+ Log.i(TAG, "Message:" + message);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 08687795d932..6dd8dd64d844 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -44,12 +44,15 @@ interface IBluetooth
boolean startDiscovery();
boolean cancelDiscovery();
boolean isDiscovering();
+ byte[] readOutOfBandData();
boolean createBond(in String address);
+ boolean createBondOutOfBand(in String address, in byte[] hash, in byte[] randomizer);
boolean cancelBondProcess(in String address);
boolean removeBond(in String address);
String[] listBonds();
int getBondState(in String address);
+ boolean setDeviceOutOfBandData(in String address, in byte[] hash, in byte[] randomizer);
String getRemoteName(in String address);
int getRemoteClass(in String address);
@@ -60,6 +63,7 @@ interface IBluetooth
boolean setPin(in String address, in byte[] pin);
boolean setPasskey(in String address, int passkey);
boolean setPairingConfirmation(in String address, boolean confirm);
+ boolean setRemoteOutOfBandData(in String addres);
boolean cancelPairingUserInput(in String address);
boolean setTrust(in String address, in boolean value);
@@ -68,4 +72,8 @@ interface IBluetooth
int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b);
void removeServiceRecord(int handle);
+
+ boolean connectHeadset(String address);
+ boolean disconnectHeadset(String address);
+ boolean notifyIncomingConnection(String address);
}
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
index 168fe3b252da..40f10583b637 100644
--- a/core/java/android/bluetooth/IBluetoothA2dp.aidl
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -33,4 +33,7 @@ interface IBluetoothA2dp {
int getSinkState(in BluetoothDevice device);
boolean setSinkPriority(in BluetoothDevice device, int priority);
int getSinkPriority(in BluetoothDevice device);
+
+ boolean connectSinkInternal(in BluetoothDevice device);
+ boolean disconnectSinkInternal(in BluetoothDevice device);
}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 6cccd506e269..d96f0ca0de8d 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -24,14 +24,20 @@ import android.bluetooth.BluetoothDevice;
* {@hide}
*/
interface IBluetoothHeadset {
- int getState();
+ int getState(in BluetoothDevice device);
BluetoothDevice getCurrentHeadset();
boolean connectHeadset(in BluetoothDevice device);
- void disconnectHeadset();
+ void disconnectHeadset(in BluetoothDevice device);
boolean isConnected(in BluetoothDevice device);
boolean startVoiceRecognition();
boolean stopVoiceRecognition();
boolean setPriority(in BluetoothDevice device, int priority);
int getPriority(in BluetoothDevice device);
int getBatteryUsageHint();
+
+ boolean createIncomingConnect(in BluetoothDevice device);
+ boolean acceptIncomingConnect(in BluetoothDevice device);
+ boolean cancelConnectThread();
+ boolean connectHeadsetInternal(in BluetoothDevice device);
+ boolean disconnectHeadsetInternal(in BluetoothDevice device);
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 9b9f796a1607..dc4e9c4e6525 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -86,6 +86,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
private String mReadPermission;
private String mWritePermission;
private PathPermission[] mPathPermissions;
+ private boolean mExported;
private Transport mTransport = new Transport();
@@ -257,9 +258,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
final Context context = getContext();
final String rperm = getReadPermission();
final int pid = Binder.getCallingPid();
- if (rperm == null
+ if (mExported && (rperm == null
|| context.checkPermission(rperm, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
+ == PackageManager.PERMISSION_GRANTED)) {
return;
}
@@ -303,9 +304,9 @@ public abstract class ContentProvider implements ComponentCallbacks {
final Context context = getContext();
final String wperm = getWritePermission();
final int pid = Binder.getCallingPid();
- if (wperm == null
+ if (mExported && (wperm == null
|| context.checkPermission(wperm, pid, uid)
- == PackageManager.PERMISSION_GRANTED) {
+ == PackageManager.PERMISSION_GRANTED)) {
return true;
}
@@ -786,6 +787,7 @@ public abstract class ContentProvider implements ComponentCallbacks {
setReadPermission(info.readPermission);
setWritePermission(info.writePermission);
setPathPermissions(info.pathPermissions);
+ mExported = info.exported;
}
ContentProvider.this.onCreate();
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index b4718ab6ef2b..69f7611a4d59 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -18,6 +18,7 @@ package android.content;
import android.accounts.Account;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
@@ -1306,7 +1307,7 @@ public abstract class ContentResolver {
// ActivityThread.currentPackageName() only returns non-null if the
// current thread is an application main thread. This parameter tells
// us whether an event loop is blocked, and if so, which app it is.
- String blockingPackage = ActivityThread.currentPackageName();
+ String blockingPackage = AppGlobals.getInitialPackage();
EventLog.writeEvent(
EventLogTags.CONTENT_QUERY_SAMPLE,
@@ -1329,7 +1330,7 @@ public abstract class ContentResolver {
}
}
}
- String blockingPackage = ActivityThread.currentPackageName();
+ String blockingPackage = AppGlobals.getInitialPackage();
EventLog.writeEvent(
EventLogTags.CONTENT_UPDATE_SAMPLE,
uri.toString(),
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index 377e38373cb6..fc2dfc07b0e1 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -537,6 +537,9 @@ public final class ContentService extends IContentService.Stub {
// Look to see if the proper child already exists
String segment = getUriSegment(uri, index);
+ if (segment == null) {
+ throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
+ }
int N = mChildren.size();
for (int i = 0; i < N; i++) {
ObserverNode node = mChildren.get(i);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 30822d4ade1e..0100550e810d 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1206,6 +1206,8 @@ public abstract class Context {
* for management of input methods.
* <dt> {@link #UI_MODE_SERVICE} ("uimode")
* <dd> An {@link android.app.UiModeManager} for controlling UI modes.
+ * <dt> {@link #DOWNLOAD_SERVICE} ("download")
+ * <dd> A {@link android.net.DownloadManager} for requesting HTTP downloads
* </dl>
*
* <p>Note: System services obtained via this API may be closely associated with
@@ -1253,6 +1255,8 @@ public abstract class Context {
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
* @see android.app.UiModeManager
+ * @see #DOWNLOAD_SERVICE
+ * @see android.net.DownloadManager
*/
public abstract Object getSystemService(String name);
@@ -1372,9 +1376,8 @@ public abstract class Context {
public static final String SENSOR_SERVICE = "sensor";
/**
- * @hide
* Use with {@link #getSystemService} to retrieve a {@link
- * android.os.storage.StorageManager} for accesssing system storage
+ * android.os.storage.StorageManager} for accessing system storage
* functions.
*
* @see #getSystemService
@@ -1535,6 +1538,23 @@ public abstract class Context {
public static final String UI_MODE_SERVICE = "uimode";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.net.DownloadManager} for requesting HTTP downloads.
+ *
+ * @see #getSystemService
+ */
+ public static final String DOWNLOAD_SERVICE = "download";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.net.sip.SipManager} for accessing the SIP related service.
+ *
+ * @see #getSystemService
+ */
+ /** @hide */
+ public static final String SIP_SERVICE = "sip";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2acc4a05792a..7154aeee08d4 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1567,6 +1567,30 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK";
/**
+ * Broadcast Action: A sticky broadcast that indicates a memory full
+ * condition on the device. This is intended for activities that want
+ * to be able to fill the data partition completely, leaving only
+ * enough free space to prevent system-wide SQLite failures.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_STORAGE_FULL = "android.intent.action.DEVICE_STORAGE_FULL";
+ /**
+ * Broadcast Action: Indicates memory full condition on the device
+ * no longer exists.
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * {@hide}
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_DEVICE_STORAGE_NOT_FULL = "android.intent.action.DEVICE_STORAGE_NOT_FULL";
+ /**
* Broadcast Action: Indicates low memory condition notification acknowledged by user
* and package management should be started.
* This is triggered by the user from the ACTION_DEVICE_STORAGE_LOW
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index e182021f8ef1..007a71515214 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -16,6 +16,7 @@
package android.content;
+import android.app.ActivityManagerNative;
import android.content.Context;
import android.content.Intent;
import android.content.IIntentSender;
@@ -170,6 +171,25 @@ public class IntentSender implements Parcelable {
}
/**
+ * Return the package name of the application that created this
+ * IntentSender, that is the identity under which you will actually be
+ * sending the Intent. The returned string is supplied by the system, so
+ * that an application can not spoof its package.
+ *
+ * @return The package name of the PendingIntent, or null if there is
+ * none associated with it.
+ */
+ public String getTargetPackage() {
+ try {
+ return ActivityManagerNative.getDefault()
+ .getPackageForIntentSender(mTarget);
+ } catch (RemoteException e) {
+ // Should never happen.
+ return null;
+ }
+ }
+
+ /**
* Comparison operator on two IntentSender objects, such that true
* is returned then they both represent the same operation from the
* same package.
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index a15e29e754e0..1484204fb5bd 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -40,7 +40,9 @@ public interface SharedPreferences {
/**
* Called when a shared preference is changed, added, or removed. This
* may be called even if a preference is set to its existing value.
- *
+ *
+ * <p>This callback will be run on your main thread.
+ *
* @param sharedPreferences The {@link SharedPreferences} that received
* the change.
* @param key The key of the preference that was changed, added, or
@@ -52,13 +54,13 @@ public interface SharedPreferences {
/**
* Interface used for modifying values in a {@link SharedPreferences}
* object. All changes you make in an editor are batched, and not copied
- * back to the original {@link SharedPreferences} or persistent storage
- * until you call {@link #commit}.
+ * back to the original {@link SharedPreferences} until you call {@link #commit}
+ * or {@link #apply}
*/
public interface Editor {
/**
* Set a String value in the preferences editor, to be written back once
- * {@link #commit} is called.
+ * {@link #commit} or {@link #apply} are called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
@@ -70,7 +72,7 @@ public interface SharedPreferences {
/**
* Set an int value in the preferences editor, to be written back once
- * {@link #commit} is called.
+ * {@link #commit} or {@link #apply} are called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
@@ -82,7 +84,7 @@ public interface SharedPreferences {
/**
* Set a long value in the preferences editor, to be written back once
- * {@link #commit} is called.
+ * {@link #commit} or {@link #apply} are called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
@@ -94,7 +96,7 @@ public interface SharedPreferences {
/**
* Set a float value in the preferences editor, to be written back once
- * {@link #commit} is called.
+ * {@link #commit} or {@link #apply} are called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
@@ -106,7 +108,7 @@ public interface SharedPreferences {
/**
* Set a boolean value in the preferences editor, to be written back
- * once {@link #commit} is called.
+ * once {@link #commit} or {@link #apply} are called.
*
* @param key The name of the preference to modify.
* @param value The new value for the preference.
@@ -151,14 +153,44 @@ public interface SharedPreferences {
* {@link SharedPreferences} object it is editing. This atomically
* performs the requested modifications, replacing whatever is currently
* in the SharedPreferences.
- *
+ *
* <p>Note that when two editors are modifying preferences at the same
* time, the last one to call commit wins.
- *
+ *
+ * <p>If you don't care about the return value and you're
+ * using this from your application's main thread, consider
+ * using {@link #apply} instead.
+ *
* @return Returns true if the new values were successfully written
* to persistent storage.
*/
boolean commit();
+
+ /**
+ * Commit your preferences changes back from this Editor to the
+ * {@link SharedPreferences} object it is editing. This atomically
+ * performs the requested modifications, replacing whatever is currently
+ * in the SharedPreferences.
+ *
+ * <p>Note that when two editors are modifying preferences at the same
+ * time, the last one to call apply wins.
+ *
+ * <p>Unlike {@link #commit}, which writes its preferences out
+ * to persistent storage synchronously, {@link #apply}
+ * commits its changes to the in-memory
+ * {@link SharedPreferences} immediately but starts an
+ * asynchronous commit to disk and you won't be notified of
+ * any failures. If another editor on this
+ * {@link SharedPreferences} does a regular {@link #commit}
+ * while a {@link #apply} is still outstanding, the
+ * {@link #commit} will block until all async commits are
+ * completed as well as the commit itself.
+ *
+ * <p>If you call this from an {@link android.app.Activity},
+ * the base class will wait for any async commits to finish in
+ * its {@link android.app.Activity#onPause}.</p>
+ */
+ void apply();
}
/**
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index d0b67cca417a..26b6ad70b351 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -45,6 +45,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
import android.text.format.Time;
@@ -126,8 +127,8 @@ public class SyncManager implements OnAccountsUpdateListener {
private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
- private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
- private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
+ private static final String SYNC_WAKE_LOCK = "*sync*";
+ private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
private Context mContext;
@@ -1700,10 +1701,12 @@ public class SyncManager implements OnAccountsUpdateListener {
mActiveSyncContext.close();
mActiveSyncContext = null;
mSyncStorageEngine.setActiveSync(mActiveSyncContext);
+ mSyncWakeLock.setWorkSource(null);
runStateIdle();
return;
}
+ mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid));
mSyncWakeLock.acquire();
// no need to schedule an alarm, as that will be done by our caller.
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 98a499303e26..6413cec32459 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -317,7 +317,9 @@ public class SyncStorageEngine extends Handler {
if (sSyncStorageEngine != null) {
return;
}
- File dataDir = Environment.getDataDirectory();
+ // This call will return the correct directory whether Encrypted File Systems is
+ // enabled or not.
+ File dataDir = Environment.getSecureDataDirectory();
sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 238b840ddc12..395c392b12b2 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -151,12 +151,14 @@ public class ActivityInfo extends ComponentInfo
public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 0x0100;
/**
* Options that have been set in the activity declaration in the
- * manifest: {@link #FLAG_MULTIPROCESS},
+ * manifest.
+ * These include:
+ * {@link #FLAG_MULTIPROCESS},
* {@link #FLAG_FINISH_ON_TASK_LAUNCH}, {@link #FLAG_CLEAR_TASK_ON_LAUNCH},
* {@link #FLAG_ALWAYS_RETAIN_TASK_STATE},
* {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS},
* {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY},
- * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS}.
+ * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS},
*/
public int flags;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1577f9e3e28d..0db954db35f0 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -174,7 +174,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* Value for {@link #flags}: true when the application's window can be
* increased in size for larger screens. Corresponds to
* {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens
- * android:smallScreens}.
+ * android:largeScreens}.
*/
public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11;
@@ -252,6 +252,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_RESTORE_ANY_VERSION = 1<<17;
/**
+ * Value for {@link #flags}: Set to true if the application has been
+ * installed using the forward lock option.
+ *
* Value for {@link #flags}: Set to true if the application is
* currently installed on external/removable/unprotected storage. Such
* applications may not be available if their storage is not currently
@@ -262,20 +265,44 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_EXTERNAL_STORAGE = 1<<18;
/**
+ * Value for {@link #flags}: true when the application's window can be
+ * increased in size for extra large screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_xlargeScreens
+ * android:xlargeScreens}.
+ * @hide
+ */
+ public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19;
+
+ /**
+ * Value for {@link #flags}: this is true if the application has set
+ * its android:neverEncrypt to true, false otherwise. It is used to specify
+ * that this package specifically "opts-out" of a secured file system solution,
+ * and will always store its data in-the-clear.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_NEVER_ENCRYPT = 1<<30;
+
+ /**
* Value for {@link #flags}: Set to true if the application has been
* installed using the forward lock option.
*
* {@hide}
*/
- public static final int FLAG_FORWARD_LOCK = 1<<20;
+ public static final int FLAG_FORWARD_LOCK = 1<<29;
/**
- * Value for {@link #flags}: Set to true if the application is
- * native-debuggable, i.e. embeds a gdbserver binary in its .apk
+ * Value for {@link #flags}: set to <code>true</code> if the application
+ * has reported that it is heavy-weight, and thus can not participate in
+ * the normal application lifecycle.
+ *
+ * <p>Comes from the
+ * {@link android.R.styleable#AndroidManifestApplication_heavyWeight android:heavyWeight}
+ * attribute of the &lt;application&gt; tag.
*
* {@hide}
*/
- public static final int FLAG_NATIVE_DEBUGGABLE = 1<<21;
+ public static final int CANT_SAVE_STATE = 1<<27;
/**
* Flags associated with the application. Any combination of
@@ -285,7 +312,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP},
* {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
* {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
- * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS},
+ * {@link #FLAG_SUPPORTS_LARGE_SCREENS},
+ * {@link #FLAG_RESIZEABLE_FOR_SCREENS},
* {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE}
*/
public int flags = 0;
@@ -324,7 +352,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* data.
*/
public String dataDir;
-
+
+ /**
+ * Full path to the directory where native JNI libraries are stored.
+ */
+ public String nativeLibraryDir;
+
/**
* The kernel user-ID that has been assigned to this application;
* currently this is not a unique ID (multiple applications can have
@@ -356,15 +389,17 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
if (permission != null) {
pw.println(prefix + "permission=" + permission);
}
- pw.println(prefix + "uid=" + uid + " taskAffinity=" + taskAffinity);
- if (theme != 0) {
- pw.println(prefix + "theme=0x" + Integer.toHexString(theme));
- }
- pw.println(prefix + "flags=0x" + Integer.toHexString(flags)
- + " processName=" + processName);
+ pw.println(prefix + "processName=" + processName);
+ pw.println(prefix + "taskAffinity=" + taskAffinity);
+ pw.println(prefix + "uid=" + uid + " flags=0x" + Integer.toHexString(flags)
+ + " theme=0x" + Integer.toHexString(theme));
pw.println(prefix + "sourceDir=" + sourceDir);
- pw.println(prefix + "publicSourceDir=" + publicSourceDir);
- pw.println(prefix + "resourceDirs=" + resourceDirs);
+ if (!sourceDir.equals(publicSourceDir)) {
+ pw.println(prefix + "publicSourceDir=" + publicSourceDir);
+ }
+ if (resourceDirs != null) {
+ pw.println(prefix + "resourceDirs=" + resourceDirs);
+ }
pw.println(prefix + "dataDir=" + dataDir);
if (sharedLibraryFiles != null) {
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
@@ -415,6 +450,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
flags = orig.flags;
sourceDir = orig.sourceDir;
publicSourceDir = orig.publicSourceDir;
+ nativeLibraryDir = orig.nativeLibraryDir;
resourceDirs = orig.resourceDirs;
sharedLibraryFiles = orig.sharedLibraryFiles;
dataDir = orig.dataDir;
@@ -446,6 +482,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(flags);
dest.writeString(sourceDir);
dest.writeString(publicSourceDir);
+ dest.writeString(nativeLibraryDir);
dest.writeStringArray(resourceDirs);
dest.writeStringArray(sharedLibraryFiles);
dest.writeString(dataDir);
@@ -477,6 +514,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
flags = source.readInt();
sourceDir = source.readString();
publicSourceDir = source.readString();
+ nativeLibraryDir = source.readString();
resourceDirs = source.readStringArray();
sharedLibraryFiles = source.readStringArray();
dataDir = source.readString();
@@ -517,7 +555,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public void disableCompatibilityMode() {
flags |= (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS |
FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS |
- FLAG_SUPPORTS_SCREEN_DENSITIES);
+ FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS);
}
/**
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index cafe3725ec0a..f16c4efdfaf4 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -157,6 +157,14 @@ public class ComponentInfo extends PackageItemInfo {
/**
* @hide
*/
+ @Override
+ protected Drawable loadDefaultLogo(PackageManager pm) {
+ return applicationInfo.loadLogo(pm);
+ }
+
+ /**
+ * @hide
+ */
@Override protected ApplicationInfo getApplicationInfo() {
return applicationInfo;
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 9939478b79b5..4cff3bb21538 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -68,6 +68,8 @@ interface IPackageManager {
ServiceInfo getServiceInfo(in ComponentName className, int flags);
+ ProviderInfo getProviderInfo(in ComponentName className, int flags);
+
int checkPermission(String permName, String pkgName);
int checkUidPermission(String permName, int uid);
@@ -319,4 +321,6 @@ interface IPackageManager {
boolean setInstallLocation(int loc);
int getInstallLocation();
+
+ void setPackageObbPath(String packageName, String path);
}
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 3e868a7a7600..ea47e8e6e0d5 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -50,7 +50,14 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
* data.
*/
public String dataDir;
-
+
+ /**
+ * Full path to the directory where the native JNI libraries are stored.
+ *
+ * {@hide}
+ */
+ public String nativeLibraryDir;
+
/**
* Specifies whether or not this instrumentation will handle profiling.
*/
@@ -68,6 +75,7 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
sourceDir = orig.sourceDir;
publicSourceDir = orig.publicSourceDir;
dataDir = orig.dataDir;
+ nativeLibraryDir = orig.nativeLibraryDir;
handleProfiling = orig.handleProfiling;
functionalTest = orig.functionalTest;
}
@@ -88,6 +96,7 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
dest.writeString(sourceDir);
dest.writeString(publicSourceDir);
dest.writeString(dataDir);
+ dest.writeString(nativeLibraryDir);
dest.writeInt((handleProfiling == false) ? 0 : 1);
dest.writeInt((functionalTest == false) ? 0 : 1);
}
@@ -108,6 +117,7 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
sourceDir = source.readString();
publicSourceDir = source.readString();
dataDir = source.readString();
+ nativeLibraryDir = source.readString();
handleProfiling = source.readInt() != 0;
functionalTest = source.readInt() != 0;
}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 14c068006c11..d73aaf6a2e98 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -67,6 +67,14 @@ public class PackageItemInfo {
public int icon;
/**
+ * A drawable resource identifier (in the package's resources) of this
+ * component's logo. Logos may be larger/wider than icons and are
+ * displayed by certain UI elements in place of a name or name/icon
+ * combination. From the "logo" attribute or, if not set, 0.
+ */
+ public int logo;
+
+ /**
* Additional meta-data associated with this component. This field
* will only be filled in if you set the
* {@link PackageManager#GET_META_DATA} flag when requesting the info.
@@ -84,6 +92,7 @@ public class PackageItemInfo {
nonLocalizedLabel = orig.nonLocalizedLabel;
if (nonLocalizedLabel != null) nonLocalizedLabel = nonLocalizedLabel.toString().trim();
icon = orig.icon;
+ logo = orig.logo;
metaData = orig.metaData;
}
@@ -152,6 +161,42 @@ public class PackageItemInfo {
}
/**
+ * Retrieve the current graphical logo associated with this item. This
+ * will call back on the given PackageManager to load the logo from
+ * the application.
+ *
+ * @param pm A PackageManager from which the logo can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's logo. If the item
+ * does not have a logo, this method will return null.
+ */
+ public Drawable loadLogo(PackageManager pm) {
+ if (logo != 0) {
+ Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo());
+ if (d != null) {
+ return d;
+ }
+ }
+ return loadDefaultLogo(pm);
+ }
+
+ /**
+ * Retrieve the default graphical logo associated with this item.
+ *
+ * @param pm A PackageManager from which the logo can be loaded; usually
+ * the PackageManager from which you originally retrieved this item.
+ *
+ * @return Returns a Drawable containing the item's default logo
+ * or null if no default logo is available.
+ *
+ * @hide
+ */
+ protected Drawable loadDefaultLogo(PackageManager pm) {
+ return null;
+ }
+
+ /**
* Load an XML resource attached to the meta-data of this item. This will
* retrieved the name meta-data entry, and if defined call back on the
* given PackageManager to load its XML file from the application.
@@ -196,6 +241,7 @@ public class PackageItemInfo {
dest.writeInt(labelRes);
TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
dest.writeInt(icon);
+ dest.writeInt(logo);
dest.writeBundle(metaData);
}
@@ -206,6 +252,7 @@ public class PackageItemInfo {
nonLocalizedLabel
= TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
icon = source.readInt();
+ logo = source.readInt();
metaData = source.readBundle();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 68b44e705fce..ef720139c4f6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -610,6 +610,16 @@ public abstract class PackageManager {
public static final int MOVE_FAILED_INTERNAL_ERROR = -6;
/**
+ * Error code that is passed to the {@link IPackageMoveObserver} by
+ * {@link #movePackage(android.net.Uri, IPackageMoveObserver)} if the
+ * specified package already has an operation pending in the
+ * {@link PackageHandler} queue.
+ *
+ * @hide
+ */
+ public static final int MOVE_FAILED_OPERATION_PENDING = -7;
+
+ /**
* Flag parameter for {@link #movePackage} to indicate that
* the package should be moved to internal storage if its
* been installed on external media.
@@ -656,6 +666,13 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device has a front facing camera.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports one or more methods of
* reporting current location.
*/
@@ -688,6 +705,14 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device can communicate using Near-Field
+ * Communications (NFC).
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NFC = "android.hardware.nfc";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes a magnetometer (compass).
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -735,7 +760,21 @@ public abstract class PackageManager {
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
-
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The SIP API is enabled on the device.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SIP = "android.software.sip";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports SIP-based VOIP.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SIP_VOIP = "android.software.sip.voip";
+
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's display has a touch screen.
@@ -762,6 +801,15 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device's touch screen is capable of
+ * tracking a full hand of fingers fully independently -- that is, 5 or
+ * more simultaneous independent pointers.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports live wallpapers.
*/
@SdkConstant(SdkConstantType.FEATURE)
@@ -979,9 +1027,9 @@ public abstract class PackageManager {
* <p>Throws {@link NameNotFoundException} if an activity with the given
* class name can not be found on the system.
*
- * @param className The full name (i.e.
- * com.google.apps.contacts.ContactsList) of an Activity
- * class.
+ * @param component The full component name (i.e.
+ * com.google.apps.contacts/com.google.apps.contacts.ContactsList) of an Activity
+ * class.
* @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data (in ApplicationInfo) returned.
@@ -992,7 +1040,7 @@ public abstract class PackageManager {
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
*/
- public abstract ActivityInfo getActivityInfo(ComponentName className,
+ public abstract ActivityInfo getActivityInfo(ComponentName component,
int flags) throws NameNotFoundException;
/**
@@ -1002,9 +1050,9 @@ public abstract class PackageManager {
* <p>Throws {@link NameNotFoundException} if a receiver with the given
* class name can not be found on the system.
*
- * @param className The full name (i.e.
- * com.google.apps.contacts.CalendarAlarm) of a Receiver
- * class.
+ * @param component The full component name (i.e.
+ * com.google.apps.calendar/com.google.apps.calendar.CalendarAlarm) of a Receiver
+ * class.
* @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data returned.
@@ -1015,7 +1063,7 @@ public abstract class PackageManager {
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
*/
- public abstract ActivityInfo getReceiverInfo(ComponentName className,
+ public abstract ActivityInfo getReceiverInfo(ComponentName component,
int flags) throws NameNotFoundException;
/**
@@ -1025,9 +1073,9 @@ public abstract class PackageManager {
* <p>Throws {@link NameNotFoundException} if a service with the given
* class name can not be found on the system.
*
- * @param className The full name (i.e.
- * com.google.apps.media.BackgroundPlayback) of a Service
- * class.
+ * @param component The full component name (i.e.
+ * com.google.apps.media/com.google.apps.media.BackgroundPlayback) of a Service
+ * class.
* @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* to modify the data returned.
@@ -1037,7 +1085,29 @@ public abstract class PackageManager {
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
*/
- public abstract ServiceInfo getServiceInfo(ComponentName className,
+ public abstract ServiceInfo getServiceInfo(ComponentName component,
+ int flags) throws NameNotFoundException;
+
+ /**
+ * Retrieve all of the information we know about a particular content
+ * provider class.
+ *
+ * <p>Throws {@link NameNotFoundException} if a provider with the given
+ * class name can not be found on the system.
+ *
+ * @param component The full component name (i.e.
+ * com.google.providers.media/com.google.providers.media.MediaProvider) of a
+ * ContentProvider class.
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
+ * to modify the data returned.
+ *
+ * @return ProviderInfo containing information about the service.
+ *
+ * @see #GET_META_DATA
+ * @see #GET_SHARED_LIBRARY_FILES
+ */
+ public abstract ProviderInfo getProviderInfo(ComponentName component,
int flags) throws NameNotFoundException;
/**
@@ -1597,6 +1667,79 @@ public abstract class PackageManager {
throws NameNotFoundException;
/**
+ * Retrieve the logo associated with an activity. Given the full name of
+ * an activity, retrieves the information about it and calls
+ * {@link ComponentInfo#loadLogo ComponentInfo.loadLogo()} to return its logo.
+ * If the activity can not be found, NameNotFoundException is thrown.
+ *
+ * @param activityName Name of the activity whose logo is to be retrieved.
+ *
+ * @return Returns the image of the logo or null if the activity has no
+ * logo specified.
+ *
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * activity could not be loaded.
+ *
+ * @see #getActivityLogo(Intent)
+ */
+ public abstract Drawable getActivityLogo(ComponentName activityName)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the logo associated with an Intent. If intent.getClassName() is
+ * set, this simply returns the result of
+ * getActivityLogo(intent.getClassName()). Otherwise it resolves the intent's
+ * component and returns the logo associated with the resolved component.
+ * If intent.getClassName() can not be found or the Intent can not be resolved
+ * to a component, NameNotFoundException is thrown.
+ *
+ * @param intent The intent for which you would like to retrieve a logo.
+ *
+ * @return Returns the image of the logo, or null if the activity has no
+ * logo specified.
+ *
+ * @throws NameNotFoundException Thrown if the resources for application
+ * matching the given intent could not be loaded.
+ *
+ * @see #getActivityLogo(ComponentName)
+ */
+ public abstract Drawable getActivityLogo(Intent intent)
+ throws NameNotFoundException;
+
+ /**
+ * Retrieve the logo associated with an application. If it has not specified
+ * a logo, this method returns null.
+ *
+ * @param info Information about application being queried.
+ *
+ * @return Returns the image of the logo, or null if no logo is specified
+ * by the application.
+ *
+ * @see #getApplicationLogo(String)
+ */
+ public abstract Drawable getApplicationLogo(ApplicationInfo info);
+
+ /**
+ * Retrieve the logo associated with an application. Given the name of the
+ * application's package, retrieves the information about it and calls
+ * getApplicationLogo() to return its logo. If the application can not be
+ * found, NameNotFoundException is thrown.
+ *
+ * @param packageName Name of the package whose application logo is to be
+ * retrieved.
+ *
+ * @return Returns the image of the logo, or null if no application logo
+ * has been specified.
+ *
+ * @throws NameNotFoundException Thrown if the resources for the given
+ * application could not be loaded.
+ *
+ * @see #getApplicationLogo(ApplicationInfo)
+ */
+ public abstract Drawable getApplicationLogo(String packageName)
+ throws NameNotFoundException;
+
+ /**
* Retrieve text from a package. This is a low-level API used by
* the various package manager info structures (such as
* {@link ComponentInfo} to implement retrieval of their associated
@@ -2113,4 +2256,17 @@ public abstract class PackageManager {
*/
public abstract void movePackage(
String packageName, IPackageMoveObserver observer, int flags);
+
+ /**
+ * Sets the Opaque Binary Blob (OBB) file location.
+ * <p>
+ * NOTE: The existence or format of this file is not currently checked, but
+ * it may be in the future.
+ *
+ * @param packageName Name of the package with which to associate the .obb
+ * file
+ * @param path Path on the filesystem to the .obb file
+ * @hide
+ */
+ public abstract void setPackageObbPath(String packageName, String path);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2a20a2d0e363..e20cb5ee4134 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -105,17 +105,19 @@ public class PackageParser {
final int nameRes;
final int labelRes;
final int iconRes;
+ final int logoRes;
String tag;
TypedArray sa;
ParsePackageItemArgs(Package _owner, String[] _outError,
- int _nameRes, int _labelRes, int _iconRes) {
+ int _nameRes, int _labelRes, int _iconRes, int _logoRes) {
owner = _owner;
outError = _outError;
nameRes = _nameRes;
labelRes = _labelRes;
iconRes = _iconRes;
+ logoRes = _logoRes;
}
}
@@ -127,10 +129,10 @@ public class PackageParser {
int flags;
ParseComponentArgs(Package _owner, String[] _outError,
- int _nameRes, int _labelRes, int _iconRes,
+ int _nameRes, int _labelRes, int _iconRes, int _logoRes,
String[] _sepProcesses, int _processRes,
int _descriptionRes, int _enabledRes) {
- super(_owner, _outError, _nameRes, _labelRes, _iconRes);
+ super(_owner, _outError, _nameRes, _labelRes, _iconRes, _logoRes);
sepProcesses = _sepProcesses;
processRes = _processRes;
descriptionRes = _descriptionRes;
@@ -585,7 +587,7 @@ public class PackageParser {
* location from the apk location at the given file path.
* @param packageFilePath file location of the apk
* @param flags Special parse flags
- * @return PackageLite object with package information.
+ * @return PackageLite object with package information or null on failure.
*/
public static PackageLite parsePackageLite(String packageFilePath, int flags) {
XmlResourceParser parser = null;
@@ -789,6 +791,7 @@ public class PackageParser {
int supportsSmallScreens = 1;
int supportsNormalScreens = 1;
int supportsLargeScreens = 1;
+ int supportsXLargeScreens = 1;
int resizeable = 1;
int anyDensity = 1;
@@ -996,9 +999,12 @@ public class PackageParser {
supportsLargeScreens = sa.getInteger(
com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens,
supportsLargeScreens);
+ supportsXLargeScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_xlargeScreens,
+ supportsXLargeScreens);
resizeable = sa.getInteger(
com.android.internal.R.styleable.AndroidManifestSupportsScreens_resizeable,
- supportsLargeScreens);
+ resizeable);
anyDensity = sa.getInteger(
com.android.internal.R.styleable.AndroidManifestSupportsScreens_anyDensity,
anyDensity);
@@ -1132,6 +1138,11 @@ public class PackageParser {
>= android.os.Build.VERSION_CODES.DONUT)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
}
+ if (supportsXLargeScreens < 0 || (supportsXLargeScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.GINGERBREAD)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS;
+ }
if (resizeable < 0 || (resizeable > 0
&& pkg.applicationInfo.targetSdkVersion
>= android.os.Build.VERSION_CODES.DONUT)) {
@@ -1241,7 +1252,8 @@ public class PackageParser {
"<permission-group>", sa,
com.android.internal.R.styleable.AndroidManifestPermissionGroup_name,
com.android.internal.R.styleable.AndroidManifestPermissionGroup_label,
- com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon)) {
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon,
+ com.android.internal.R.styleable.AndroidManifestPermissionGroup_logo)) {
sa.recycle();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
@@ -1276,7 +1288,8 @@ public class PackageParser {
"<permission>", sa,
com.android.internal.R.styleable.AndroidManifestPermission_name,
com.android.internal.R.styleable.AndroidManifestPermission_label,
- com.android.internal.R.styleable.AndroidManifestPermission_icon)) {
+ com.android.internal.R.styleable.AndroidManifestPermission_icon,
+ com.android.internal.R.styleable.AndroidManifestPermission_logo)) {
sa.recycle();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
@@ -1329,7 +1342,8 @@ public class PackageParser {
"<permission-tree>", sa,
com.android.internal.R.styleable.AndroidManifestPermissionTree_name,
com.android.internal.R.styleable.AndroidManifestPermissionTree_label,
- com.android.internal.R.styleable.AndroidManifestPermissionTree_icon)) {
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_icon,
+ com.android.internal.R.styleable.AndroidManifestPermissionTree_logo)) {
sa.recycle();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
@@ -1373,7 +1387,8 @@ public class PackageParser {
mParseInstrumentationArgs = new ParsePackageItemArgs(owner, outError,
com.android.internal.R.styleable.AndroidManifestInstrumentation_name,
com.android.internal.R.styleable.AndroidManifestInstrumentation_label,
- com.android.internal.R.styleable.AndroidManifestInstrumentation_icon);
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_icon,
+ com.android.internal.R.styleable.AndroidManifestInstrumentation_logo);
mParseInstrumentationArgs.tag = "<instrumentation>";
}
@@ -1485,6 +1500,8 @@ public class PackageParser {
ai.icon = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
+ ai.logo = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestApplication_logo, 0);
ai.theme = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestApplication_theme, 0);
ai.descriptionRes = sa.getResourceId(
@@ -1542,6 +1559,12 @@ public class PackageParser {
ai.flags |= ApplicationInfo.FLAG_TEST_ONLY;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_neverEncrypt,
+ false)) {
+ ai.flags |= ApplicationInfo.FLAG_NEVER_ENCRYPT;
+ }
+
String str;
str = sa.getNonConfigurationString(
com.android.internal.R.styleable.AndroidManifestApplication_permission, 0);
@@ -1577,6 +1600,20 @@ public class PackageParser {
ai.enabled = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestApplication_enabled, true);
+
+ if (false) {
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState,
+ false)) {
+ ai.flags |= ApplicationInfo.CANT_SAVE_STATE;
+
+ // A heavy-weight application can not be in a custom process.
+ // We can do direct compare because we intern all strings.
+ if (ai.processName != null && ai.processName != ai.packageName) {
+ outError[0] = "cantSaveState applications can not use custom processes";
+ }
+ }
+ }
}
sa.recycle();
@@ -1705,7 +1742,7 @@ public class PackageParser {
private boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo,
String[] outError, String tag, TypedArray sa,
- int nameRes, int labelRes, int iconRes) {
+ int nameRes, int labelRes, int iconRes, int logoRes) {
String name = sa.getNonConfigurationString(nameRes, 0);
if (name == null) {
outError[0] = tag + " does not specify android:name";
@@ -1723,6 +1760,11 @@ public class PackageParser {
outInfo.icon = iconVal;
outInfo.nonLocalizedLabel = null;
}
+
+ int logoVal = sa.getResourceId(logoRes, 0);
+ if (logoVal != 0) {
+ outInfo.logo = logoVal;
+ }
TypedValue v = sa.peekValue(labelRes);
if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
@@ -1745,6 +1787,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivity_name,
com.android.internal.R.styleable.AndroidManifestActivity_label,
com.android.internal.R.styleable.AndroidManifestActivity_icon,
+ com.android.internal.R.styleable.AndroidManifestActivity_logo,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestActivity_process,
com.android.internal.R.styleable.AndroidManifestActivity_description,
@@ -1860,6 +1903,14 @@ public class PackageParser {
sa.recycle();
+ if (receiver && (owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ // A heavy-weight application can not have receives in its main process
+ // We can do direct compare because we intern all strings.
+ if (a.info.processName == owner.packageName) {
+ outError[0] = "Heavy-weight applications can not have receivers in main process";
+ }
+ }
+
if (outError[0] != null) {
return null;
}
@@ -1947,6 +1998,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivityAlias_name,
com.android.internal.R.styleable.AndroidManifestActivityAlias_label,
com.android.internal.R.styleable.AndroidManifestActivityAlias_icon,
+ com.android.internal.R.styleable.AndroidManifestActivityAlias_logo,
mSeparateProcesses,
0,
com.android.internal.R.styleable.AndroidManifestActivityAlias_description,
@@ -1980,6 +2032,7 @@ public class PackageParser {
info.configChanges = target.info.configChanges;
info.flags = target.info.flags;
info.icon = target.info.icon;
+ info.logo = target.info.logo;
info.labelRes = target.info.labelRes;
info.nonLocalizedLabel = target.info.nonLocalizedLabel;
info.launchMode = target.info.launchMode;
@@ -2074,6 +2127,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_name,
com.android.internal.R.styleable.AndroidManifestProvider_label,
com.android.internal.R.styleable.AndroidManifestProvider_icon,
+ com.android.internal.R.styleable.AndroidManifestProvider_logo,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestProvider_process,
com.android.internal.R.styleable.AndroidManifestProvider_description,
@@ -2139,6 +2193,15 @@ public class PackageParser {
sa.recycle();
+ if ((owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ // A heavy-weight application can not have providers in its main process
+ // We can do direct compare because we intern all strings.
+ if (p.info.processName == owner.packageName) {
+ outError[0] = "Heavy-weight applications can not have providers in main process";
+ return null;
+ }
+ }
+
if (cpname == null) {
outError[0] = "<provider> does not incude authorities attribute";
return null;
@@ -2337,6 +2400,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestService_name,
com.android.internal.R.styleable.AndroidManifestService_label,
com.android.internal.R.styleable.AndroidManifestService_icon,
+ com.android.internal.R.styleable.AndroidManifestService_logo,
mSeparateProcesses,
com.android.internal.R.styleable.AndroidManifestService_process,
com.android.internal.R.styleable.AndroidManifestService_description,
@@ -2370,6 +2434,15 @@ public class PackageParser {
sa.recycle();
+ if ((owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) {
+ // A heavy-weight application can not have services in its main process
+ // We can do direct compare because we intern all strings.
+ if (s.info.processName == owner.packageName) {
+ outError[0] = "Heavy-weight applications can not have services in main process";
+ return null;
+ }
+ }
+
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2540,6 +2613,9 @@ public class PackageParser {
outInfo.icon = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
+
+ outInfo.logo = sa.getResourceId(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0);
sa.recycle();
@@ -2706,9 +2782,15 @@ public class PackageParser {
// For use by package manager to keep track of where it has done dexopt.
public boolean mDidDexOpt;
+ // User set enabled state.
+ public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+
// Additional data supplied by callers.
public Object mExtras;
-
+
+ // Whether an operation is currently pending on this package
+ public boolean mOperationPending;
+
/*
* Applications hardware preferences
*/
@@ -2801,6 +2883,11 @@ public class PackageParser {
outInfo.icon = iconVal;
outInfo.nonLocalizedLabel = null;
}
+
+ int logoVal = args.sa.getResourceId(args.logoRes, 0);
+ if (logoVal != 0) {
+ outInfo.logo = logoVal;
+ }
TypedValue v = args.sa.peekValue(args.labelRes);
if (v != null && (outInfo.labelRes=v.resourceId) == 0) {
@@ -2927,6 +3014,12 @@ public class PackageParser {
}
private static boolean copyNeeded(int flags, Package p, Bundle metaData) {
+ if (p.mSetEnabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+ boolean enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ if (p.applicationInfo.enabled != enabled) {
+ return true;
+ }
+ }
if ((flags & PackageManager.GET_META_DATA) != 0
&& (metaData != null || p.mAppMetaData != null)) {
return true;
@@ -2960,6 +3053,7 @@ public class PackageParser {
if (!sCompatibilityModeEnabled) {
ai.disableCompatibilityMode();
}
+ ai.enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
return ai;
}
@@ -3135,6 +3229,7 @@ public class PackageParser {
public int labelRes;
public CharSequence nonLocalizedLabel;
public int icon;
+ public int logo;
}
public final static class ActivityIntentInfo extends IntentInfo {
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index 1bb38577fbca..d4e5cc13b1c1 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -20,6 +20,7 @@ import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.ref.SoftReference;
import java.util.Arrays;
/**
@@ -30,7 +31,7 @@ public class Signature implements Parcelable {
private final byte[] mSignature;
private int mHashCode;
private boolean mHaveHashCode;
- private String mString;
+ private SoftReference<String> mStringRef;
/**
* Create Signature from an existing raw byte array.
@@ -96,10 +97,13 @@ public class Signature implements Parcelable {
* cached so future calls will return the same String.
*/
public String toCharsString() {
- if (mString != null) return mString;
- String str = new String(toChars());
- mString = str;
- return mString;
+ String str = mStringRef == null ? null : mStringRef.get();
+ if (str != null) {
+ return str;
+ }
+ str = new String(toChars());
+ mStringRef = new SoftReference<String>(str);
+ return str;
}
/**
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 1070f08776a8..73d9458fbec3 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -70,6 +70,7 @@ public final class AssetManager {
// For communication with native code.
private int mObject;
+ private int mNObject; // used by the NDK
private StringBlock mStringBlocks[] = null;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 11c67cc18110..d0ba590159f4 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -99,7 +99,22 @@ public class CompatibilityInfo {
*/
private static final int CONFIGURED_LARGE_SCREENS = 16;
- private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE | LARGE_SCREENS;
+ /**
+ * A flag mask to indicates that the application supports xlarge screens.
+ * The flag is set to true if
+ * 1) Application declares it supports xlarge screens in manifest file using <supports-screens> or
+ * 2) The screen size is not xlarge
+ * {@see compatibilityFlag}
+ */
+ private static final int XLARGE_SCREENS = 32;
+
+ /**
+ * A flag mask to tell if the application supports xlarge screens. This differs
+ * from XLARGE_SCREENS in that the application that does not support xlarge
+ * screens will be marked as supporting them if the current screen is not
+ * xlarge.
+ */
+ private static final int CONFIGURED_XLARGE_SCREENS = 64;
/**
* The effective screen density we have selected for this application.
@@ -127,6 +142,9 @@ public class CompatibilityInfo {
if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS;
}
+ if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+ mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS;
+ }
if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE;
}
@@ -157,6 +175,7 @@ public class CompatibilityInfo {
this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
| ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS
| ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS
+ | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS
| ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS,
EXPANDABLE | CONFIGURED_EXPANDABLE,
DisplayMetrics.DENSITY_DEVICE,
@@ -196,6 +215,17 @@ public class CompatibilityInfo {
}
/**
+ * Sets large screen bit in the compatibility flag.
+ */
+ public void setXLargeScreens(boolean expandable) {
+ if (expandable) {
+ mCompatibilityFlags |= CompatibilityInfo.XLARGE_SCREENS;
+ } else {
+ mCompatibilityFlags &= ~CompatibilityInfo.XLARGE_SCREENS;
+ }
+ }
+
+ /**
* @return true if the application is configured to be expandable.
*/
public boolean isConfiguredExpandable() {
@@ -210,6 +240,13 @@ public class CompatibilityInfo {
}
/**
+ * @return true if the application is configured to be expandable.
+ */
+ public boolean isConfiguredXLargeScreens() {
+ return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_XLARGE_SCREENS) != 0;
+ }
+
+ /**
* @return true if the scaling is required
*/
public boolean isScalingRequired() {
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 1a0c867edf19..5a3dd415b9fc 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -62,6 +62,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public static final int SCREENLAYOUT_SIZE_SMALL = 0x01;
public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02;
public static final int SCREENLAYOUT_SIZE_LARGE = 0x03;
+ /** @hide */
+ public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04;
public static final int SCREENLAYOUT_LONG_MASK = 0x30;
public static final int SCREENLAYOUT_LONG_UNDEFINED = 0x00;
@@ -82,7 +84,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* <p>The {@link #SCREENLAYOUT_SIZE_MASK} bits define the overall size
* of the screen. They may be one of
* {@link #SCREENLAYOUT_SIZE_SMALL}, {@link #SCREENLAYOUT_SIZE_NORMAL},
- * or {@link #SCREENLAYOUT_SIZE_LARGE}.
+ * {@link #SCREENLAYOUT_SIZE_LARGE}.
*
* <p>The {@link #SCREENLAYOUT_LONG_MASK} defines whether the screen
* is wider/taller than normal. They may be one of
diff --git a/core/java/android/os/storage/IMountShutdownObserver.aidl b/core/java/android/content/res/ObbInfo.aidl
index 0aa8a4592ab6..636ad6ae415e 100644..100755
--- a/core/java/android/os/storage/IMountShutdownObserver.aidl
+++ b/core/java/android/content/res/ObbInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,6 @@
* limitations under the License.
*/
-package android.os.storage;
+package android.content.res;
-/**
- * Callback class for receiving events related
- * to shutdown.
- *
- * @hide - For internal consumption only.
- */
-interface IMountShutdownObserver {
- /**
- * This method is called when the shutdown
- * of MountService completed.
- * @param statusCode indicates success or failure
- * of the shutdown.
- */
- void onShutDownComplete(int statusCode);
-}
+parcelable ObbInfo;
diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java
new file mode 100644
index 000000000000..838c5ff5a764
--- /dev/null
+++ b/core/java/android/content/res/ObbInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Basic information about a Opaque Binary Blob (OBB) that reflects
+ * the info from the footer on the OBB file.
+ * @hide
+ */
+public class ObbInfo implements Parcelable {
+ /** Flag noting that this OBB is an overlay patch for a base OBB. */
+ public static final int OBB_OVERLAY = 1 << 0;
+
+ /**
+ * The name of the package to which the OBB file belongs.
+ */
+ public String packageName;
+
+ /**
+ * The version of the package to which the OBB file belongs.
+ */
+ public int version;
+
+ /**
+ * The flags relating to the OBB.
+ */
+ public int flags;
+
+ public ObbInfo() {
+ }
+
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("ObbInfo{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" packageName=");
+ sb.append(packageName);
+ sb.append(",version=");
+ sb.append(version);
+ sb.append(",flags=");
+ sb.append(flags);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int parcelableFlags) {
+ dest.writeString(packageName);
+ dest.writeInt(version);
+ dest.writeInt(flags);
+ }
+
+ public static final Parcelable.Creator<ObbInfo> CREATOR
+ = new Parcelable.Creator<ObbInfo>() {
+ public ObbInfo createFromParcel(Parcel source) {
+ return new ObbInfo(source);
+ }
+
+ public ObbInfo[] newArray(int size) {
+ return new ObbInfo[size];
+ }
+ };
+
+ private ObbInfo(Parcel source) {
+ packageName = source.readString();
+ version = source.readInt();
+ flags = source.readInt();
+ }
+}
diff --git a/core/java/android/content/res/ObbScanner.java b/core/java/android/content/res/ObbScanner.java
new file mode 100644
index 000000000000..eb383c360308
--- /dev/null
+++ b/core/java/android/content/res/ObbScanner.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+/**
+ * Class to scan Opaque Binary Blob (OBB) files.
+ * @hide
+ */
+public class ObbScanner {
+ // Don't allow others to instantiate this class
+ private ObbScanner() {}
+
+ public static ObbInfo getObbInfo(String filePath) {
+ if (filePath == null) {
+ return null;
+ }
+
+ ObbInfo obbInfo = new ObbInfo();
+ if (!getObbInfo_native(filePath, obbInfo)) {
+ throw new IllegalArgumentException("Could not read OBB file: " + filePath);
+ }
+ return obbInfo;
+ }
+
+ private native static boolean getObbInfo_native(String filePath, ObbInfo obbInfo);
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 0608cc02bf3d..9bb3b75124f3 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1288,7 +1288,7 @@ public class Resources {
height = mMetrics.widthPixels;
}
int keyboardHidden = mConfiguration.keyboardHidden;
- if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+ if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
&& mConfiguration.hardKeyboardHidden
== Configuration.HARDKEYBOARDHIDDEN_YES) {
keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index b0c149d18934..37fdeb652742 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -708,9 +708,7 @@ public class TypedArray {
outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
outValue.changingConfigurations = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS];
outValue.density = data[index+AssetManager.STYLE_DENSITY];
- if (type == TypedValue.TYPE_STRING) {
- outValue.string = loadStringValueAt(index);
- }
+ outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
return true;
}
diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java
index 79178f421453..6539156ee41d 100644
--- a/core/java/android/database/Cursor.java
+++ b/core/java/android/database/Cursor.java
@@ -25,6 +25,9 @@ import java.util.Map;
/**
* This interface provides random read-write access to the result set returned
* by a database query.
+ *
+ * Cursor implementations are not required to be synchronized so code using a Cursor from multiple
+ * threads should perform its own synchronization when using the Cursor.
*/
public interface Cursor {
/**
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 9bfbb74eeb27..66406cac439a 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -108,7 +108,7 @@ public class DatabaseUtils {
* @see Parcel#readException
*/
public static final void readExceptionFromParcel(Parcel reply) {
- int code = reply.readInt();
+ int code = reply.readExceptionCode();
if (code == 0) return;
String msg = reply.readString();
DatabaseUtils.readExceptionFromParcel(reply, msg, code);
@@ -116,7 +116,7 @@ public class DatabaseUtils {
public static void readExceptionWithFileNotFoundExceptionFromParcel(
Parcel reply) throws FileNotFoundException {
- int code = reply.readInt();
+ int code = reply.readExceptionCode();
if (code == 0) return;
String msg = reply.readString();
if (code == 1) {
@@ -128,7 +128,7 @@ public class DatabaseUtils {
public static void readExceptionWithOperationApplicationExceptionFromParcel(
Parcel reply) throws OperationApplicationException {
- int code = reply.readInt();
+ int code = reply.readExceptionCode();
if (code == 0) return;
String msg = reply.readString();
if (code == 10) {
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 6e5b3e168516..c7e58faf26b2 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -36,6 +36,9 @@ import java.util.concurrent.locks.ReentrantLock;
/**
* A Cursor implementation that exposes results from a query on a
* {@link SQLiteDatabase}.
+ *
+ * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple
+ * threads should perform its own synchronization when using the SQLiteCursor.
*/
public class SQLiteCursor extends AbstractWindowedCursor {
static final String TAG = "Cursor";
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index fb5507dc8f83..cdc9bbb6a510 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -19,6 +19,7 @@ package android.database.sqlite;
import com.google.android.collect.Maps;
import android.app.ActivityThread;
+import android.app.AppGlobals;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -33,6 +34,8 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
+import dalvik.system.BlockGuard;
+
import java.io.File;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
@@ -1134,7 +1137,8 @@ public class SQLiteDatabase extends SQLiteClosable {
*
* @param sql The raw SQL statement, may contain ? for unknown values to be
* bound later.
- * @return a pre-compiled statement object.
+ * @return A pre-compiled {@link SQLiteStatement} object. Note that
+ * {@link SQLiteStatement}s are not synchronized, see the documentation for more details.
*/
public SQLiteStatement compileStatement(String sql) throws SQLException {
lock();
@@ -1175,7 +1179,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
- * @return A Cursor object, which is positioned before the first entry
+ * @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.
* @see Cursor
*/
public Cursor query(boolean distinct, String table, String[] columns,
@@ -1213,7 +1218,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
- * @return A Cursor object, which is positioned before the first entry
+ * @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.
* @see Cursor
*/
public Cursor queryWithFactory(CursorFactory cursorFactory,
@@ -1254,7 +1260,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
* (excluding the ORDER BY itself). Passing null will use the
* default sort order, which may be unordered.
- * @return A {@link Cursor} object, which is positioned before the first entry
+ * @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.
* @see Cursor
*/
public Cursor query(String table, String[] columns, String selection,
@@ -1291,7 +1298,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
- * @return A {@link Cursor} object, which is positioned before the first entry
+ * @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.
* @see Cursor
*/
public Cursor query(String table, String[] columns, String selection,
@@ -1309,7 +1317,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* @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.
- * @return A {@link Cursor} object, which is positioned before the first entry
+ * @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.
*/
public Cursor rawQuery(String sql, String[] selectionArgs) {
return rawQueryWithFactory(null, sql, selectionArgs, null);
@@ -1324,7 +1333,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* which will be replaced by the values from selectionArgs. The
* values will be bound as Strings.
* @param editTable the name of the first table, which is editable
- * @return A {@link Cursor} object, which is positioned before the first entry
+ * @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.
*/
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
@@ -1332,6 +1342,7 @@ public class SQLiteDatabase extends SQLiteClosable {
if (!isOpen()) {
throw new IllegalStateException("database not open");
}
+ BlockGuard.getThreadPolicy().onReadFromDisk();
long timeStart = 0;
if (Config.LOGV || mSlowQueryThreshold != -1) {
@@ -1379,7 +1390,8 @@ public class SQLiteDatabase extends SQLiteClosable {
* 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
+ * @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.
@@ -1489,6 +1501,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
if (!isOpen()) {
throw new IllegalStateException("database not open");
}
@@ -1580,6 +1593,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* whereClause.
*/
public int delete(String table, String whereClause, String[] whereArgs) {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
lock();
if (!isOpen()) {
throw new IllegalStateException("database not open");
@@ -1635,6 +1649,7 @@ public class SQLiteDatabase extends SQLiteClosable {
*/
public int updateWithOnConflict(String table, ContentValues values,
String whereClause, String[] whereArgs, int conflictAlgorithm) {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
if (values == null || values.size() == 0) {
throw new IllegalArgumentException("Empty values");
}
@@ -1717,6 +1732,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException If the SQL string is invalid for some reason
*/
public void execSQL(String sql) throws SQLException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
long timeStart = SystemClock.uptimeMillis();
lock();
if (!isOpen()) {
@@ -1752,6 +1768,7 @@ public class SQLiteDatabase extends SQLiteClosable {
* @throws SQLException If the SQL string is invalid for some reason
*/
public void execSQL(String sql, Object[] bindArgs) throws SQLException {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
if (bindArgs == null) {
throw new IllegalArgumentException("Empty bindArgs");
}
@@ -1905,7 +1922,7 @@ public class SQLiteDatabase extends SQLiteClosable {
// main thread, or when we are invoked via Binder (e.g. ContentProvider).
// Hopefully the full path to the database will be informative enough.
- String blockingPackage = ActivityThread.currentPackageName();
+ String blockingPackage = AppGlobals.getInitialPackage();
if (blockingPackage == null) blockingPackage = "";
EventLog.writeEvent(
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 47002b6638a6..b4615eb6b261 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -34,6 +34,12 @@ import android.util.Log;
*
* <p>For an example, see the NotePadProvider class in the NotePad sample application,
* in the <em>samples/</em> directory of the SDK.</p>
+ *
+ * <p class="note"><strong>Note:</strong> this class assumes
+ * monotonically increasing version numbers for upgrades. Also, there
+ * is no concept of a database downgrade; installing a new version of
+ * your app which uses a lower version number than a
+ * previously-installed version will result in undefined behavior.</p>
*/
public abstract class SQLiteOpenHelper {
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
@@ -119,6 +125,10 @@ public abstract class SQLiteOpenHelper {
if (version == 0) {
onCreate(db);
} else {
+ if (version > mNewVersion) {
+ Log.wtf(TAG, "Can't downgrade read-only database from version " +
+ version + " to " + mNewVersion + ": " + db.getPath());
+ }
onUpgrade(db, version, mNewVersion);
}
db.setVersion(mNewVersion);
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 89a5f0d1e983..4d96f12c7946 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -20,6 +20,9 @@ import android.util.Log;
/**
* A base class for compiled SQLite programs.
+ *
+ * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple
+ * threads should perform its own synchronization when using the SQLiteProgram.
*/
public abstract class SQLiteProgram extends SQLiteClosable {
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 43d2fac8b7e6..905b66b78cd6 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -23,6 +23,9 @@ import android.util.Log;
/**
* A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
* This class is used by SQLiteCursor and isn't useful itself.
+ *
+ * SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple
+ * threads should perform its own synchronization when using the SQLiteQuery.
*/
public class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "Cursor";
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 98da41449476..9e425c34c05a 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -18,11 +18,16 @@ package android.database.sqlite;
import android.os.SystemClock;
+import dalvik.system.BlockGuard;
+
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
* The statement cannot return multiple rows, but 1x1 result sets are allowed.
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
+ *
+ * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple
+ * threads should perform its own synchronization when using the SQLiteStatement.
*/
public class SQLiteStatement extends SQLiteProgram
{
@@ -44,6 +49,7 @@ public class SQLiteStatement extends SQLiteProgram
* some reason
*/
public void execute() {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
@@ -70,6 +76,7 @@ public class SQLiteStatement extends SQLiteProgram
* some reason
*/
public long executeInsert() {
+ BlockGuard.getThreadPolicy().onWriteToDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
@@ -96,6 +103,7 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public long simpleQueryForLong() {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
@@ -122,6 +130,7 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public String simpleQueryForString() {
+ BlockGuard.getThreadPolicy().onReadFromDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 59c386d05699..3cc89e5233e1 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -50,7 +50,7 @@ import android.os.Message;
* <p>To take pictures with this class, use the following steps:</p>
*
* <ol>
- * <li>Obtain an instance of Camera from {@link #open()}.
+ * <li>Obtain an instance of Camera from {@link #open(int)}.
*
* <li>Get existing (default) settings with {@link #getParameters()}.
*
@@ -102,7 +102,7 @@ import android.os.Message;
* <p>This class is not thread-safe, and is meant for use from one event thread.
* Most long-running operations (preview, focus, photo capture, etc) happen
* asynchronously and invoke callbacks as necessary. Callbacks will be invoked
- * on the event thread {@link #open()} was called from. This class's methods
+ * on the event thread {@link #open(int)} was called from. This class's methods
* must never be called from multiple threads at once.</p>
*
* <p class="caution"><strong>Caution:</strong> Different Android-powered devices
@@ -114,7 +114,7 @@ import android.os.Message;
public class Camera {
private static final String TAG = "Camera";
- // These match the enums in frameworks/base/include/ui/Camera.h
+ // These match the enums in frameworks/base/include/camera/Camera.h
private static final int CAMERA_MSG_ERROR = 0x001;
private static final int CAMERA_MSG_SHUTTER = 0x002;
private static final int CAMERA_MSG_FOCUS = 0x004;
@@ -140,12 +140,52 @@ public class Camera {
private boolean mWithBuffer;
/**
- * Creates a new Camera object.
+ * Returns the number of physical cameras available on this device.
+ */
+ public native static int getNumberOfCameras();
+
+ /**
+ * Returns the information about a particular camera.
+ * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
+ */
+ public native static void getCameraInfo(int cameraId, CameraInfo cameraInfo);
+
+ /**
+ * Information about a camera
+ */
+ public static class CameraInfo {
+ public static final int CAMERA_FACING_BACK = 0;
+ public static final int CAMERA_FACING_FRONT = 1;
+
+ /**
+ * The direction that the camera faces to. It should be
+ * CAMERA_FACING_BACK or CAMERA_FACING_FRONT.
+ */
+ public int mFacing;
+
+ /**
+ * The orientation of the camera image. The value is the angle that the
+ * camera image needs to be rotated clockwise so it shows correctly on
+ * the display in its natural orientation. It should be 0, 90, 180, or 270.
+ *
+ * For example, suppose a device has a naturally tall screen, but the camera
+ * sensor is mounted in landscape. If the top side of the camera sensor is
+ * aligned with the right edge of the display in natural orientation, the
+ * value should be 90.
+ *
+ * @see #setDisplayOrientation(int)
+ */
+ public int mOrientation;
+ };
+
+ /**
+ * Creates a new Camera object to access a particular hardware camera.
*
* <p>You must call {@link #release()} when you are done using the camera,
* otherwise it will remain locked and be unavailable to other applications.
*
- * <p>Your application should only have one Camera object active at a time.
+ * <p>Your application should only have one Camera object active at a time
+ * for a particular hardware camera.
*
* <p>Callbacks from other methods are delivered to the event loop of the
* thread which called open(). If this thread has no event loop, then
@@ -157,15 +197,31 @@ public class Camera {
* worker thread (possibly using {@link android.os.AsyncTask}) to avoid
* blocking the main application UI thread.
*
+ * @param cameraId the hardware camera to access, between 0 and
+ * {@link #getNumberOfCameras()}-1. Use {@link #CAMERA_ID_DEFAULT}
+ * to access the default camera.
* @return a new Camera object, connected, locked and ready for use.
* @throws RuntimeException if connection to the camera service fails (for
* example, if the camera is in use by another process).
*/
+ public static Camera open(int cameraId) {
+ return new Camera(cameraId);
+ }
+
+ /**
+ * The id for the default camera.
+ */
+ public static int CAMERA_ID_DEFAULT = 0;
+
+ /**
+ * Equivalent to Camera.open(Camera.CAMERA_ID_DEFAULT).
+ * Creates a new Camera object to access the default camera.
+ */
public static Camera open() {
- return new Camera();
+ return new Camera(CAMERA_ID_DEFAULT);
}
- Camera() {
+ Camera(int cameraId) {
mShutterCallback = null;
mRawImageCallback = null;
mJpegCallback = null;
@@ -182,14 +238,14 @@ public class Camera {
mEventHandler = null;
}
- native_setup(new WeakReference<Camera>(this));
+ native_setup(new WeakReference<Camera>(this), cameraId);
}
protected void finalize() {
native_release();
}
- private native final void native_setup(Object camera_this);
+ private native final void native_setup(Object camera_this, int cameraId);
private native final void native_release();
@@ -296,7 +352,7 @@ public class Camera {
{
/**
* Called as preview frames are displayed. This callback is invoked
- * on the event thread {@link #open()} was called from.
+ * on the event thread {@link #open(int)} was called from.
*
* @param data the contents of the preview frame in the format defined
* by {@link android.graphics.ImageFormat}, which can be queried
@@ -713,6 +769,28 @@ public class Camera {
* {@link PreviewCallback#onPreviewFrame}. This method is not allowed to
* be called during preview.
*
+ * If you want to make the camera image show in the same orientation as
+ * the display, you can use the following code.<p>
+ * <pre>
+ * public static void setCameraDisplayOrientation(Activity activity,
+ * int cameraId, android.hardware.Camera camera) {
+ * android.hardware.Camera.CameraInfo info =
+ * new android.hardware.Camera.CameraInfo();
+ * android.hardware.Camera.getCameraInfo(cameraId, info);
+ * int rotation = activity.getWindowManager().getDefaultDisplay()
+ * .getRotation();
+ * int degrees = 0;
+ * switch (rotation) {
+ * case Surface.ROTATION_0: degrees = 0; break;
+ * case Surface.ROTATION_90: degrees = 90; break;
+ * case Surface.ROTATION_180: degrees = 180; break;
+ * case Surface.ROTATION_270: degrees = 270; break;
+ * }
+ *
+ * int result = (info.mOrientation - degrees + 360) % 360;
+ * camera.setDisplayOrientation(result);
+ * }
+ * </pre>
* @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).
@@ -882,6 +960,7 @@ public class Camera {
private static final String KEY_PREVIEW_SIZE = "preview-size";
private static final String KEY_PREVIEW_FORMAT = "preview-format";
private static final String KEY_PREVIEW_FRAME_RATE = "preview-frame-rate";
+ private static final String KEY_PREVIEW_FPS_RANGE = "preview-fps-range";
private static final String KEY_PICTURE_SIZE = "picture-size";
private static final String KEY_PICTURE_FORMAT = "picture-format";
private static final String KEY_JPEG_THUMBNAIL_SIZE = "jpeg-thumbnail-size";
@@ -913,6 +992,8 @@ public class Camera {
private static final String KEY_ZOOM_RATIOS = "zoom-ratios";
private static final String KEY_ZOOM_SUPPORTED = "zoom-supported";
private static final String KEY_SMOOTH_ZOOM_SUPPORTED = "smooth-zoom-supported";
+ private static final String KEY_FOCUS_DISTANCES = "focus-distances";
+
// Parameter key suffix for supported values.
private static final String SUPPORTED_VALUES_SUFFIX = "-values";
@@ -974,21 +1055,81 @@ public class Camera {
*/
public static final String FLASH_MODE_TORCH = "torch";
- // Values for scene mode settings.
+ /**
+ * Scene mode is off.
+ */
public static final String SCENE_MODE_AUTO = "auto";
+
+ /**
+ * Take photos of fast moving objects. Same as {@link
+ * #SCENE_MODE_SPORTS}.
+ */
public static final String SCENE_MODE_ACTION = "action";
+
+ /**
+ * Take people pictures.
+ */
public static final String SCENE_MODE_PORTRAIT = "portrait";
+
+ /**
+ * Take pictures on distant objects.
+ */
public static final String SCENE_MODE_LANDSCAPE = "landscape";
+
+ /**
+ * Take photos at night.
+ */
public static final String SCENE_MODE_NIGHT = "night";
+
+ /**
+ * Take people pictures at night.
+ */
public static final String SCENE_MODE_NIGHT_PORTRAIT = "night-portrait";
+
+ /**
+ * Take photos in a theater. Flash light is off.
+ */
public static final String SCENE_MODE_THEATRE = "theatre";
+
+ /**
+ * Take pictures on the beach.
+ */
public static final String SCENE_MODE_BEACH = "beach";
+
+ /**
+ * Take pictures on the snow.
+ */
public static final String SCENE_MODE_SNOW = "snow";
+
+ /**
+ * Take sunset photos.
+ */
public static final String SCENE_MODE_SUNSET = "sunset";
+
+ /**
+ * Avoid blurry pictures (for example, due to hand shake).
+ */
public static final String SCENE_MODE_STEADYPHOTO = "steadyphoto";
+
+ /**
+ * For shooting firework displays.
+ */
public static final String SCENE_MODE_FIREWORKS = "fireworks";
+
+ /**
+ * Take photos of fast moving objects. Same as {@link
+ * #SCENE_MODE_ACTION}.
+ */
public static final String SCENE_MODE_SPORTS = "sports";
+
+ /**
+ * Take indoor low-light shot.
+ */
public static final String SCENE_MODE_PARTY = "party";
+
+ /**
+ * Capture the naturally warm color of scenes lit by candles.
+ */
public static final String SCENE_MODE_CANDLELIGHT = "candlelight";
/**
@@ -997,9 +1138,9 @@ public class Camera {
*/
public static final String SCENE_MODE_BARCODE = "barcode";
- // Values for focus mode settings.
/**
- * Auto-focus mode.
+ * Auto-focus mode. Applications should call {@link
+ * #autoFocus(AutoFocusCallback)} to start the focus in this mode.
*/
public static final String FOCUS_MODE_AUTO = "auto";
@@ -1008,6 +1149,12 @@ public class Camera {
* {@link #autoFocus(AutoFocusCallback)} in this mode.
*/
public static final String FOCUS_MODE_INFINITY = "infinity";
+
+ /**
+ * Macro (close-up) focus mode. Applications should call
+ * {@link #autoFocus(AutoFocusCallback)} to start the focus in this
+ * mode.
+ */
public static final String FOCUS_MODE_MACRO = "macro";
/**
@@ -1025,6 +1172,49 @@ public class Camera {
*/
public static final String FOCUS_MODE_EDOF = "edof";
+ /**
+ * Continuous auto focus mode. The camera continuously tries to focus.
+ * This is ideal for shooting video or shooting photo of moving object.
+ * 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.
+ */
+ public static final String FOCUS_MODE_CONTINUOUS = "continuous";
+
+ // Indices for focus distance array.
+ /**
+ * The array index of near focus distance for use with
+ * {@link #getFocusDistances(float[])}.
+ */
+ public static final int FOCUS_DISTANCE_NEAR_INDEX = 0;
+
+ /**
+ * The array index of optimal focus distance for use with
+ * {@link #getFocusDistances(float[])}.
+ */
+ public static final int FOCUS_DISTANCE_OPTIMAL_INDEX = 1;
+
+ /**
+ * The array index of far focus distance for use with
+ * {@link #getFocusDistances(float[])}.
+ */
+ public static final int FOCUS_DISTANCE_FAR_INDEX = 2;
+
+ /**
+ * The array index of minimum preview fps for use with {@link
+ * #getPreviewFpsRange(int[])} or {@link
+ * #getSupportedPreviewFpsRange()}.
+ */
+ public static final int PREVIEW_FPS_MIN_INDEX = 0;
+
+ /**
+ * The array index of maximum preview fps for use with {@link
+ * #getPreviewFpsRange(int[])} or {@link
+ * #getSupportedPreviewFpsRange()}.
+ */
+ public static final int PREVIEW_FPS_MAX_INDEX = 1;
+
// Formats for setPreviewFormat and setPictureFormat.
private static final String PIXEL_FORMAT_YUV422SP = "yuv422sp";
private static final String PIXEL_FORMAT_YUV420SP = "yuv420sp";
@@ -1260,7 +1450,9 @@ public class Camera {
* target frame rate. The actual frame rate depends on the driver.
*
* @param fps the frame rate (frames per second)
+ * @deprecated replaced by {@link #setPreviewFpsRange(int,int)}
*/
+ @Deprecated
public void setPreviewFrameRate(int fps) {
set(KEY_PREVIEW_FRAME_RATE, fps);
}
@@ -1271,7 +1463,9 @@ public class Camera {
* depends on the driver.
*
* @return the frame rate setting (frames per second)
+ * @deprecated replaced by {@link #getPreviewFpsRange(int[])}
*/
+ @Deprecated
public int getPreviewFrameRate() {
return getInt(KEY_PREVIEW_FRAME_RATE);
}
@@ -1281,13 +1475,70 @@ public class Camera {
*
* @return a list of supported preview frame rates. null if preview
* frame rate setting is not supported.
+ * @deprecated replaced by {@link #getSupportedPreviewFpsRange()}
*/
+ @Deprecated
public List<Integer> getSupportedPreviewFrameRates() {
String str = get(KEY_PREVIEW_FRAME_RATE + SUPPORTED_VALUES_SUFFIX);
return splitInt(str);
}
/**
+ * Sets the maximum and maximum preview fps. This controls the rate of
+ * preview frames received in {@link PreviewCallback}. The minimum and
+ * maximum preview fps must be one of the elements from {@link
+ * #getSupportedPreviewFpsRange}.
+ *
+ * @param min the minimum preview fps (scaled by 1000).
+ * @param max the maximum preview fps (scaled by 1000).
+ * @throws RuntimeException if fps range is invalid.
+ * @see #setPreviewCallbackWithBuffer(Camera.PreviewCallback)
+ * @see #getSupportedPreviewFpsRange()
+ */
+ public void setPreviewFpsRange(int min, int max) {
+ set(KEY_PREVIEW_FPS_RANGE, "" + min + "," + max);
+ }
+
+ /**
+ * Returns the current minimum and maximum preview fps. The values are
+ * one of the elements returned by {@link #getSupportedPreviewFpsRange}.
+ *
+ * @return range the minimum and maximum preview fps (scaled by 1000).
+ * @see #PREVIEW_FPS_MIN_INDEX
+ * @see #PREVIEW_FPS_MAX_INDEX
+ * @see #getSupportedPreviewFpsRange()
+ */
+ public void getPreviewFpsRange(int[] range) {
+ if (range == null || range.length != 2) {
+ throw new IllegalArgumentException(
+ "range must be an array with two elements.");
+ }
+ splitInt(get(KEY_PREVIEW_FPS_RANGE), range);
+ }
+
+ /**
+ * Gets the supported preview fps (frame-per-second) ranges. Each range
+ * contains a minimum fps and maximum fps. If minimum fps equals to
+ * maximum fps, the camera outputs frames in fixed frame rate. If not,
+ * the camera outputs frames in auto frame rate. The actual frame rate
+ * fluctuates between the minimum and the maximum. The values are
+ * multiplied by 1000 and represented in integers. For example, if frame
+ * rate is 26.623 frames per second, the value is 26623.
+ *
+ * @return a list of supported preview fps ranges. This method returns a
+ * list with at least one element. Every element is an int array
+ * of two values - minimum fps and maximum fps. The list is
+ * sorted from small to large (first by maximum fps and then
+ * minimum fps).
+ * @see #PREVIEW_FPS_MIN_INDEX
+ * @see #PREVIEW_FPS_MAX_INDEX
+ */
+ public List<int[]> getSupportedPreviewFpsRange() {
+ String str = get(KEY_PREVIEW_FPS_RANGE + SUPPORTED_VALUES_SUFFIX);
+ return splitRange(str);
+ }
+
+ /**
* Sets the image format for preview pictures.
* <p>If this is never called, the default format will be
* {@link android.graphics.ImageFormat#NV21}, which
@@ -1754,15 +2005,16 @@ public class Camera {
/**
* Gets the current focus mode setting.
*
- * @return current focus mode. If the camera does not support
- * auto-focus, this should return {@link #FOCUS_MODE_FIXED}. If
- * the focus mode is not FOCUS_MODE_FIXED or {@link
- * #FOCUS_MODE_INFINITY}, applications should call {@link
- * #autoFocus(AutoFocusCallback)} to start the focus.
+ * @return current focus mode. This method will always return a non-null
+ * value. Applications should call {@link
+ * #autoFocus(AutoFocusCallback)} to start the focus if focus
+ * mode is FOCUS_MODE_AUTO or FOCUS_MODE_MACRO.
* @see #FOCUS_MODE_AUTO
* @see #FOCUS_MODE_INFINITY
* @see #FOCUS_MODE_MACRO
* @see #FOCUS_MODE_FIXED
+ * @see #FOCUS_MODE_EDOF
+ * @see #FOCUS_MODE_CONTINUOUS
*/
public String getFocusMode() {
return get(KEY_FOCUS_MODE);
@@ -1955,6 +2207,43 @@ public class Camera {
return TRUE.equals(str);
}
+ /**
+ * Gets the distances from the camera to where an object appears to be
+ * in focus. The object is sharpest at the optimal focus distance. The
+ * depth of field is the far focus distance minus near focus distance.
+ *
+ * Focus distances may change after calling {@link
+ * #autoFocus(AutoFocusCallback)}, {@link #cancelAutoFocus}, or {@link
+ * #startPreview()}. Applications can call {@link #getParameters()}
+ * and this method anytime to get the latest focus distances. If the
+ * focus mode is FOCUS_MODE_CONTINUOUS, focus distances may change from
+ * time to time.
+ *
+ * This method is intended to estimate the distance between the camera
+ * and the subject. After autofocus, the subject distance may be within
+ * near and far focus distance. However, the precision depends on the
+ * camera hardware, autofocus algorithm, the focus area, and the scene.
+ * The error can be large and it should be only used as a reference.
+ *
+ * Far focus distance >= optimal focus distance >= near focus distance.
+ * If the focus distance is infinity, the value will be
+ * Float.POSITIVE_INFINITY.
+ *
+ * @param output focus distances in meters. output must be a float
+ * array with three elements. Near focus distance, optimal focus
+ * distance, and far focus distance will be filled in the array.
+ * @see #FOCUS_DISTANCE_NEAR_INDEX
+ * @see #FOCUS_DISTANCE_OPTIMAL_INDEX
+ * @see #FOCUS_DISTANCE_FAR_INDEX
+ */
+ public void getFocusDistances(float[] output) {
+ if (output == null || output.length != 3) {
+ throw new IllegalArgumentException(
+ "output must be an float array with three elements.");
+ }
+ splitFloat(get(KEY_FOCUS_DISTANCES), output);
+ }
+
// 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) {
@@ -1984,6 +2273,29 @@ public class Camera {
return substrings;
}
+ private void splitInt(String str, int[] output) {
+ if (str == null) return;
+
+ StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ int index = 0;
+ while (tokenizer.hasMoreElements()) {
+ String token = tokenizer.nextToken();
+ output[index++] = Integer.parseInt(token);
+ }
+ }
+
+ // Splits a comma delimited string to an ArrayList of Float.
+ private void splitFloat(String str, float[] output) {
+ if (str == null) return;
+
+ StringTokenizer tokenizer = new StringTokenizer(str, ",");
+ int index = 0;
+ while (tokenizer.hasMoreElements()) {
+ String token = tokenizer.nextToken();
+ output[index++] = Float.parseFloat(token);
+ }
+ }
+
// Returns the value of a float parameter.
private float getFloat(String key, float defaultValue) {
try {
@@ -2032,5 +2344,30 @@ public class Camera {
Log.e(TAG, "Invalid size parameter string=" + str);
return null;
}
+
+ // Splits a comma delimited string to an ArrayList of int array.
+ // Example string: "(10000,26623),(10000,30000)". Return null if the
+ // passing string is null or the size is 0.
+ private ArrayList<int[]> splitRange(String str) {
+ if (str == null || str.charAt(0) != '('
+ || str.charAt(str.length() - 1) != ')') {
+ Log.e(TAG, "Invalid range list string=" + str);
+ return null;
+ }
+
+ ArrayList<int[]> rangeList = new ArrayList<int[]>();
+ int endIndex, fromIndex = 1;
+ do {
+ int[] range = new int[2];
+ endIndex = str.indexOf("),(", fromIndex);
+ if (endIndex == -1) endIndex = str.length() - 1;
+ splitInt(str.substring(fromIndex, endIndex), range);
+ rangeList.add(range);
+ fromIndex = endIndex + 3;
+ } while (endIndex != str.length() - 1);
+
+ if (rangeList.size() == 0) return null;
+ return rangeList;
+ }
};
}
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 317e5473819d..f2b907bfd234 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -17,65 +17,92 @@
package android.hardware;
-/**
- * Class representing a sensor. Use {@link SensorManager#getSensorList}
- * to get the list of available Sensors.
+/**
+ * Class representing a sensor. Use {@link SensorManager#getSensorList} to get
+ * the list of available Sensors.
+ *
+ * @see SensorManager
+ * @see SensorEventListener
+ * @see SensorEvent
+ *
*/
public class Sensor {
- /**
- * A constant describing an accelerometer sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
- * for more details.
+ /**
+ * A constant describing an accelerometer sensor type. See
+ * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
*/
- public static final int TYPE_ACCELEROMETER = 1;
+ public static final int TYPE_ACCELEROMETER = 1;
- /**
- * A constant describing a magnetic field sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
- * for more details.
+ /**
+ * A constant describing a magnetic field sensor type. See
+ * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
*/
public static final int TYPE_MAGNETIC_FIELD = 2;
-
- /**
- * A constant describing an orientation sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
- * for more details.
+
+ /**
+ * A constant describing an orientation sensor type. See
+ * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
+ *
* @deprecated use {@link android.hardware.SensorManager#getOrientation
- * SensorManager.getOrientation()} instead.
+ * SensorManager.getOrientation()} instead.
*/
@Deprecated
- public static final int TYPE_ORIENTATION = 3;
+ public static final int TYPE_ORIENTATION = 3;
/** A constant describing a gyroscope sensor type */
- public static final int TYPE_GYROSCOPE = 4;
+ public static final int TYPE_GYROSCOPE = 4;
+
/**
- * A constant describing an light sensor type.
- * See {@link android.hardware.SensorEvent SensorEvent}
- * for more details.
+ * A constant describing an light sensor type. See
+ * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
*/
- public static final int TYPE_LIGHT = 5;
+ public static final int TYPE_LIGHT = 5;
/** A constant describing a pressure sensor type */
- public static final int TYPE_PRESSURE = 6;
+ public static final int TYPE_PRESSURE = 6;
/** A constant describing a temperature sensor type */
- public static final int TYPE_TEMPERATURE = 7;
+ public static final int TYPE_TEMPERATURE = 7;
/**
- * A constant describing an proximity sensor type.
+ * A constant describing an proximity sensor type. See
+ * {@link android.hardware.SensorEvent#values SensorEvent.values} for more
+ * details.
+ */
+ public static final int TYPE_PROXIMITY = 8;
+
+ /**
+ * A constant describing a gravity sensor type.
* See {@link android.hardware.SensorEvent SensorEvent}
* for more details.
*/
- public static final int TYPE_PROXIMITY = 8;
+ public static final int TYPE_GRAVITY = 9;
+
+ /**
+ * A constant describing a linear acceleration sensor type.
+ * See {@link android.hardware.SensorEvent SensorEvent}
+ * for more details.
+ */
+ public static final int TYPE_LINEAR_ACCELERATION = 10;
+
+ /**
+ * A constant describing a rotation vector sensor type.
+ * See {@link android.hardware.SensorEvent SensorEvent}
+ * for more details.
+ */
+ public static final int TYPE_ROTATION_VECTOR = 11;
-
/**
* A constant describing all sensor types.
*/
- public static final int TYPE_ALL = -1;
+ public static final int TYPE_ALL = -1;
- /* Some of these fields are set only by the native bindings in
+ /* Some of these fields are set only by the native bindings in
* SensorManager.
*/
private String mName;
@@ -86,9 +113,10 @@ public class Sensor {
private float mMaxRange;
private float mResolution;
private float mPower;
+ private int mMinDelay;
private int mLegacyType;
-
-
+
+
Sensor() {
}
@@ -105,51 +133,60 @@ public class Sensor {
public String getVendor() {
return mVendor;
}
-
+
/**
* @return generic type of this sensor.
*/
public int getType() {
return mType;
}
-
+
/**
* @return version of the sensor's module.
*/
public int getVersion() {
return mVersion;
}
-
+
/**
* @return maximum range of the sensor in the sensor's unit.
*/
public float getMaximumRange() {
return mMaxRange;
}
-
+
/**
* @return resolution of the sensor in the sensor's unit.
*/
public float getResolution() {
return mResolution;
}
-
+
/**
* @return the power in mA used by this sensor while in use
*/
public float getPower() {
return mPower;
}
-
+
+ /**
+ * @return the minimum delay allowed between two events in microsecond
+ * or zero if this sensor only returns a value when the data it's measuring
+ * changes.
+ */
+ public int getMinDelay() {
+ return mMinDelay;
+ }
+
int getHandle() {
return mHandle;
}
-
+
void setRange(float max, float res) {
mMaxRange = max;
mResolution = res;
}
-
+
void setLegacyType(int legacyType) {
mLegacyType = legacyType;
}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 9a9f0bfcb198..2c5c9090eb14 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -17,143 +17,243 @@
package android.hardware;
/**
- * This class represents a sensor event and holds informations such as the
- * sensor type (eg: accelerometer, orientation, etc...), the time-stamp,
- * accuracy and of course the sensor's {@link SensorEvent#values data}.
+ * <p>
+ * This class represents a {@link android.hardware.Sensor Sensor} event and
+ * holds informations such as the sensor's type, the time-stamp, accuracy and of
+ * course the sensor's {@link SensorEvent#values data}.
+ * </p>
*
- * <p><u>Definition of the coordinate system used by the SensorEvent API.</u><p>
- *
- * <pre>
- * The coordinate space is defined relative to the screen of the phone
- * in its default orientation. The axes are not swapped when the device's
- * screen orientation changes.
- *
- * The OpenGL ES coordinate system is used. The origin is in the
- * lower-left corner with respect to the screen, with the X axis horizontal
- * and pointing right, the Y axis vertical and pointing up and the Z axis
- * pointing outside the front face of the screen. In this system, coordinates
- * behind the screen have negative Z values.
- *
+ * <p>
+ * <u>Definition of the coordinate system used by the SensorEvent API.</u>
+ * </p>
+ *
+ * <p>
+ * The coordinate-system is defined relative to the screen of the phone in its
+ * default orientation. The axes are not swapped when the device's screen
+ * orientation changes.
+ * </p>
+ *
+ * <p>
+ * The X axis is horizontal and points to the right, the Y axis is vertical and
+ * points up and the Z axis points towards the outside of the front face of the
+ * screen. In this system, coordinates behind the screen have negative Z values.
+ * </p>
+ *
+ * <p>
+ * <center><img src="../../../images/axis_device.png"
+ * alt="Sensors coordinate-system diagram." border="0" /></center>
+ * </p>
+ *
+ * <p>
* <b>Note:</b> This coordinate system is different from the one used in the
- * Android 2D APIs where the origin is in the top-left corner.
+ * Android 2D APIs where the origin is in the top-left corner.
+ * </p>
*
- * x<0 x>0
- * ^
- * |
- * +-----------+--> y>0
- * | |
- * | |
- * | |
- * | | / z<0
- * | | /
- * | | /
- * O-----------+/
- * |[] [ ] []/
- * +----------/+ y<0
- * /
- * /
- * |/ z>0 (toward the sky)
+ * @see SensorManager
+ * @see SensorEvent
+ * @see Sensor
*
- * O: Origin (x=0,y=0,z=0)
- * </pre>
*/
public class SensorEvent {
/**
- * The length and contents of the values array vary depending on which
- * sensor type is being monitored (see also {@link SensorEvent} for a
- * definition of the coordinate system used):
+ * <p>
+ * The length and contents of the {@link #values values} array depends on
+ * which {@link android.hardware.Sensor sensor} type is being monitored (see
+ * also {@link SensorEvent} for a definition of the coordinate system used).
+ * </p>
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_ACCELEROMETER
+ * Sensor.TYPE_ACCELEROMETER}:</h4> All values are in SI units (m/s^2)
*
- * <p>{@link android.hardware.Sensor#TYPE_ORIENTATION Sensor.TYPE_ORIENTATION}:<p>
- * All values are angles in degrees.
+ * <ul>
+ * <p>
+ * values[0]: Acceleration minus Gx on the x-axis
+ * </p>
+ * <p>
+ * values[1]: Acceleration minus Gy on the y-axis
+ * </p>
+ * <p>
+ * values[2]: Acceleration minus Gz on the z-axis
+ * </p>
+ * </ul>
*
- * <p>values[0]: Azimuth, angle between the magnetic north direction and
- * the Y axis, around the Z axis (0 to 359).
- * 0=North, 90=East, 180=South, 270=West
- *
- * <p>values[1]: Pitch, rotation around X axis (-180 to 180),
- * with positive values when the z-axis moves <b>toward</b> the y-axis.
- *
- * <p>values[2]: Roll, rotation around Y axis (-90 to 90), with
- * positive values when the x-axis moves <b>toward</b> the z-axis.
+ * <p>
+ * A sensor of this type measures the acceleration applied to the device
+ * (<b>Ad</b>). Conceptually, it does so by measuring forces applied to the
+ * sensor itself (<b>Fs</b>) using the relation:
+ * </p>
*
- * <p><b>Important note:</b> For historical reasons the roll angle is
- * positive in the clockwise direction (mathematically speaking, it
- * should be positive in the counter-clockwise direction).
- *
- * <p><b>Note:</b> This definition is different from <b>yaw, pitch and
- * roll</b> used in aviation where the X axis is along the long side of
- * the plane (tail to nose).
- *
- * <p><b>Note:</b> This sensor type exists for legacy reasons, please use
- * {@link android.hardware.SensorManager#getRotationMatrix
- * getRotationMatrix()} in conjunction with
- * {@link android.hardware.SensorManager#remapCoordinateSystem
- * remapCoordinateSystem()} and
- * {@link android.hardware.SensorManager#getOrientation getOrientation()}
- * to compute these values instead.
- *
- * <p>{@link android.hardware.Sensor#TYPE_ACCELEROMETER Sensor.TYPE_ACCELEROMETER}:<p>
- * All values are in SI units (m/s^2) and measure the acceleration applied
- * to the phone minus the force of gravity.
- *
- * <p>values[0]: Acceleration minus Gx on the x-axis
- * <p>values[1]: Acceleration minus Gy on the y-axis
- * <p>values[2]: Acceleration minus Gz on the z-axis
- *
- * <p><u>Examples</u>:
- * <li>When the device lies flat on a table and is pushed on its left
- * side toward the right, the x acceleration value is positive.</li>
- *
- * <li>When the device lies flat on a table, the acceleration value is
- * +9.81, which correspond to the acceleration of the device (0 m/s^2)
- * minus the force of gravity (-9.81 m/s^2).</li>
- *
- * <li>When the device lies flat on a table and is pushed toward the sky
- * with an acceleration of A m/s^2, the acceleration value is equal to
- * A+9.81 which correspond to the acceleration of the
- * device (+A m/s^2) minus the force of gravity (-9.81 m/s^2).</li>
- *
- *
- * <p>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD Sensor.TYPE_MAGNETIC_FIELD}:<p>
- * All values are in micro-Tesla (uT) and measure the ambient magnetic
- * field in the X, Y and Z axis.
- *
- * <p>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:<p>
+ * <b><center>Ad = - &#8721;Fs / mass</center></b>
+ *
+ * <p>
+ * In particular, the force of gravity is always influencing the measured
+ * acceleration:
+ * </p>
+ *
+ * <b><center>Ad = -g - &#8721;F / mass</center></b>
+ *
+ * <p>
+ * For this reason, when the device is sitting on a table (and obviously not
+ * accelerating), the accelerometer reads a magnitude of <b>g</b> = 9.81
+ * m/s^2
+ * </p>
+ *
+ * <p>
+ * Similarly, when the device is in free-fall and therefore dangerously
+ * accelerating towards to ground at 9.81 m/s^2, its accelerometer reads a
+ * magnitude of 0 m/s^2.
+ * </p>
+ *
+ * <p>
+ * It should be apparent that in order to measure the real acceleration of
+ * the device, the contribution of the force of gravity must be eliminated.
+ * This can be achieved by applying a <i>high-pass</i> filter. Conversely, a
+ * <i>low-pass</i> filter can be used to isolate the force of gravity.
+ * </p>
+ * <p>
+ * <u>Examples</u>:
+ * <ul>
+ * <li>When the device lies flat on a table and is pushed on its left side
+ * toward the right, the x acceleration value is positive.</li>
+ *
+ * <li>When the device lies flat on a table, the acceleration value is
+ * +9.81, which correspond to the acceleration of the device (0 m/s^2) minus
+ * the force of gravity (-9.81 m/s^2).</li>
+ *
+ * <li>When the device lies flat on a table and is pushed toward the sky
+ * with an acceleration of A m/s^2, the acceleration value is equal to
+ * A+9.81 which correspond to the acceleration of the device (+A m/s^2)
+ * minus the force of gravity (-9.81 m/s^2).</li>
+ * </ul>
+ *
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD
+ * Sensor.TYPE_MAGNETIC_FIELD}:</h4>
+ * All values are in micro-Tesla (uT) and measure the ambient magnetic field
+ * in the X, Y and Z axis.
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:</h4>
+ * All values are in radians/second and measure the rate of rotation
+ * around the X, Y and Z axis. The coordinate system is the same as is
+ * used for the acceleration sensor. Rotation is positive in the counter-clockwise
+ * direction. That is, an observer looking from some positive location on the x, y.
+ * or z axis at a device positioned on the origin would report positive rotation
+ * if the device appeared to be rotating counter clockwise. Note that this is the
+ * standard mathematical definition of positive rotation and does not agree with the
+ * definition of roll given earlier.
*
- * <p>values[0]: Ambient light level in SI lux units
+ * <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4>
+ *
+ * <ul>
+ * <p>
+ * values[0]: Ambient light level in SI lux units
+ * </ul>
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:
+ * </h4>
+ *
+ * <ul>
+ * <p>
+ * values[0]: Proximity sensor distance measured in centimeters
+ * </ul>
+ *
+ * <p>
+ * <b>Note:</b> Some proximity sensors only support a binary <i>near</i> or
+ * <i>far</i> measurement. In this case, the sensor should report its
+ * {@link android.hardware.Sensor#getMaximumRange() maximum range} value in
+ * the <i>far</i> state and a lesser value in the <i>near</i> state.
+ * </p>
+ *
+ * <h4>{@link android.hardware.Sensor#TYPE_GRAVITY Sensor.TYPE_GRAVITY}:</h4>
+ * A three dimensional vector indicating the direction and magnitude of gravity. Units
+ * are m/s^2. The coordinate system is the same as is used by the acceleration sensor.
*
- * <p>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:<p>
+ * <h4>{@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:</h4>
+ * A three dimensional vector indicating acceleration along each device axis, not including
+ * gravity. All values have units of m/s^2. The coordinate system is the same as is used by the
+ * acceleration sensor.
*
- * <p>values[0]: Proximity sensor distance measured in centimeters
+ * <h4>{@link android.hardware.Sensor#TYPE_ROTATION_VECTOR Sensor.TYPE_ROTATION_VECTOR}:</h4>
+ * The rotation vector represents the orientation of the device as a combination of an angle
+ * and an axis, in which the device has rotated through an angle theta around an axis
+ * <x, y, z>. The three elements of the rotation vector are
+ * <x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>, such that the magnitude of the rotation
+ * vector is equal to sin(theta/2), and the direction of the rotation vector is equal to the
+ * direction of the axis of rotation. The three elements of the rotation vector are equal to
+ * the last three components of a unit quaternion
+ * <cos(theta/2), x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>. Elements of the rotation
+ * vector are unitless. The x,y, and z axis are defined in the same way as the acceleration
+ * sensor.
*
- * <p> Note that some proximity sensors only support a binary "close" or "far" measurement.
- * In this case, the sensor should report its maxRange value in the "far" state and a value
- * less than maxRange in the "near" state.
+ * <h4>{@link android.hardware.Sensor#TYPE_ORIENTATION
+ * Sensor.TYPE_ORIENTATION}:</h4> All values are angles in degrees.
+ *
+ * <ul>
+ * <p>
+ * values[0]: Azimuth, angle between the magnetic north direction and the
+ * y-axis, around the z-axis (0 to 359). 0=North, 90=East, 180=South,
+ * 270=West
+ * </p>
+ *
+ * <p>
+ * values[1]: Pitch, rotation around x-axis (-180 to 180), with positive
+ * values when the z-axis moves <b>toward</b> the y-axis.
+ * </p>
+ *
+ * <p>
+ * values[2]: Roll, rotation around y-axis (-90 to 90), with positive values
+ * when the x-axis moves <b>toward</b> the z-axis.
+ * </p>
+ * </ul>
+ *
+ * <p>
+ * <b>Note:</b> This definition is different from <b>yaw, pitch and roll</b>
+ * used in aviation where the X axis is along the long side of the plane
+ * (tail to nose).
+ * </p>
+ *
+ * <p>
+ * <b>Note:</b> This sensor type exists for legacy reasons, please use
+ * {@link android.hardware.SensorManager#getRotationMatrix
+ * getRotationMatrix()} in conjunction with
+ * {@link android.hardware.SensorManager#remapCoordinateSystem
+ * remapCoordinateSystem()} and
+ * {@link android.hardware.SensorManager#getOrientation getOrientation()} to
+ * compute these values instead.
+ * </p>
+ *
+ * <p>
+ * <b>Important note:</b> For historical reasons the roll angle is positive
+ * in the clockwise direction (mathematically speaking, it should be
+ * positive in the counter-clockwise direction).
+ * </p>
+ *
+ * @see SensorEvent
+ * @see GeomagneticField
*/
+
public final float[] values;
/**
- * The sensor that generated this event.
- * See {@link android.hardware.SensorManager SensorManager}
- * for details.
+ * The sensor that generated this event. See
+ * {@link android.hardware.SensorManager SensorManager} for details.
*/
- public Sensor sensor;
-
+ public Sensor sensor;
+
/**
- * The accuracy of this event.
- * See {@link android.hardware.SensorManager SensorManager}
- * for details.
+ * The accuracy of this event. See {@link android.hardware.SensorManager
+ * SensorManager} for details.
*/
public int accuracy;
-
-
+
+
/**
* The time in nanosecond at which the event happened
*/
public long timestamp;
-
+
SensorEvent(int size) {
values = new float[size];
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 98172e61ebf2..006872436e47 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -16,12 +16,7 @@
package android.hardware;
-import android.content.Context;
-import android.os.Binder;
-import android.os.Bundle;
import android.os.Looper;
-import android.os.Parcelable;
-import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.Handler;
@@ -33,17 +28,58 @@ import android.view.IRotationWatcher;
import android.view.IWindowManager;
import android.view.Surface;
-import java.io.FileDescriptor;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
- * Class that lets you access the device's sensors. Get an instance of this
- * class by calling {@link android.content.Context#getSystemService(java.lang.String)
- * Context.getSystemService()} with an argument of {@link android.content.Context#SENSOR_SERVICE}.
+ * <p>
+ * SensorManager lets you access the device's {@link android.hardware.Sensor
+ * sensors}. Get an instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String)
+ * Context.getSystemService()} with the argument
+ * {@link android.content.Context#SENSOR_SERVICE}.
+ * </p>
+ * <p>
+ * Always make sure to disable sensors you don't need, especially when your
+ * activity is paused. Failing to do so can drain the battery in just a few
+ * hours. Note that the system will <i>not</i> disable sensors automatically when
+ * the screen turns off.
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * public class SensorActivity extends Activity, implements SensorEventListener {
+ * private final SensorManager mSensorManager;
+ * private final Sensor mAccelerometer;
+ *
+ * public SensorActivity() {
+ * mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
+ * mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ * }
+ *
+ * protected void onResume() {
+ * super.onResume();
+ * mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
+ * }
+ *
+ * protected void onPause() {
+ * super.onPause();
+ * mSensorManager.unregisterListener(this);
+ * }
+ *
+ * public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ * }
+ *
+ * public void onSensorChanged(SensorEvent event) {
+ * }
+ * }
+ * </pre>
+ *
+ * @see SensorEventListener
+ * @see SensorEvent
+ * @see Sensor
+ *
*/
public class SensorManager
{
@@ -53,172 +89,224 @@ public class SensorManager
/* NOTE: sensor IDs must be a power of 2 */
/**
- * A constant describing an orientation sensor.
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing an orientation sensor. See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_ORIENTATION = 1 << 0;
/**
- * A constant describing an accelerometer.
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing an accelerometer. See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_ACCELEROMETER = 1 << 1;
/**
- * A constant describing a temperature sensor
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing a temperature sensor See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_TEMPERATURE = 1 << 2;
/**
- * A constant describing a magnetic sensor
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing a magnetic sensor See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_MAGNETIC_FIELD = 1 << 3;
/**
- * A constant describing an ambient light sensor
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing an ambient light sensor See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_LIGHT = 1 << 4;
/**
- * A constant describing a proximity sensor
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing a proximity sensor See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_PROXIMITY = 1 << 5;
/**
- * A constant describing a Tricorder
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing a Tricorder See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_TRICORDER = 1 << 6;
/**
- * A constant describing an orientation sensor.
- * See {@link android.hardware.SensorListener SensorListener} for more details.
+ * A constant describing an orientation sensor. See
+ * {@link android.hardware.SensorListener SensorListener} for more details.
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_ORIENTATION_RAW = 1 << 7;
- /** A constant that includes all sensors
+ /**
+ * A constant that includes all sensors
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_ALL = 0x7F;
- /** Smallest sensor ID
+ /**
+ * Smallest sensor ID
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_MIN = SENSOR_ORIENTATION;
- /** Largest sensor ID
+ /**
+ * Largest sensor ID
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1);
- /** Index of the X value in the array returned by
+ /**
+ * Index of the X value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged}
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int DATA_X = 0;
- /** Index of the Y value in the array returned by
+
+ /**
+ * Index of the Y value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged}
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int DATA_Y = 1;
- /** Index of the Z value in the array returned by
+
+ /**
+ * Index of the Z value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged}
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int DATA_Z = 2;
- /** Offset to the untransformed values in the array returned by
+ /**
+ * Offset to the untransformed values in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged}
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int RAW_DATA_INDEX = 3;
- /** Index of the untransformed X value in the array returned by
+ /**
+ * Index of the untransformed X value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged}
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int RAW_DATA_X = 3;
- /** Index of the untransformed Y value in the array returned by
+
+ /**
+ * Index of the untransformed Y value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged}
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int RAW_DATA_Y = 4;
- /** Index of the untransformed Z value in the array returned by
+
+ /**
+ * Index of the untransformed Z value in the array returned by
* {@link android.hardware.SensorListener#onSensorChanged}
+ *
* @deprecated use {@link android.hardware.Sensor Sensor} instead.
*/
@Deprecated
public static final int RAW_DATA_Z = 5;
-
/** Standard gravity (g) on Earth. This value is equivalent to 1G */
public static final float STANDARD_GRAVITY = 9.80665f;
- /** values returned by the accelerometer in various locations in the universe.
- * all values are in SI units (m/s^2) */
+ /** Sun's gravity in SI units (m/s^2) */
public static final float GRAVITY_SUN = 275.0f;
+ /** Mercury's gravity in SI units (m/s^2) */
public static final float GRAVITY_MERCURY = 3.70f;
+ /** Venus' gravity in SI units (m/s^2) */
public static final float GRAVITY_VENUS = 8.87f;
+ /** Earth's gravity in SI units (m/s^2) */
public static final float GRAVITY_EARTH = 9.80665f;
+ /** The Moon's gravity in SI units (m/s^2) */
public static final float GRAVITY_MOON = 1.6f;
+ /** Mars' gravity in SI units (m/s^2) */
public static final float GRAVITY_MARS = 3.71f;
+ /** Jupiter's gravity in SI units (m/s^2) */
public static final float GRAVITY_JUPITER = 23.12f;
+ /** Saturn's gravity in SI units (m/s^2) */
public static final float GRAVITY_SATURN = 8.96f;
+ /** Uranus' gravity in SI units (m/s^2) */
public static final float GRAVITY_URANUS = 8.69f;
+ /** Neptune's gravity in SI units (m/s^2) */
public static final float GRAVITY_NEPTUNE = 11.0f;
+ /** Pluto's gravity in SI units (m/s^2) */
public static final float GRAVITY_PLUTO = 0.6f;
+ /** Gravity (estimate) on the first Death Star in Empire units (m/s^2) */
public static final float GRAVITY_DEATH_STAR_I = 0.000000353036145f;
+ /** Gravity on the island */
public static final float GRAVITY_THE_ISLAND = 4.815162342f;
/** Maximum magnetic field on Earth's surface */
public static final float MAGNETIC_FIELD_EARTH_MAX = 60.0f;
-
/** Minimum magnetic field on Earth's surface */
public static final float MAGNETIC_FIELD_EARTH_MIN = 30.0f;
- /** Various luminance values during the day (lux) */
+ /** Standard atmosphere, or average sea-level pressure in hPa (millibar) */
+ public static final float PRESSURE_STANDARD_ATMOSPHERE = 1013.25f;
+
+
+ /** Maximum luminance of sunlight in lux */
public static final float LIGHT_SUNLIGHT_MAX = 120000.0f;
+ /** luminance of sunlight in lux */
public static final float LIGHT_SUNLIGHT = 110000.0f;
+ /** luminance in shade in lux */
public static final float LIGHT_SHADE = 20000.0f;
+ /** luminance under an overcast sky in lux */
public static final float LIGHT_OVERCAST = 10000.0f;
+ /** luminance at sunrise in lux */
public static final float LIGHT_SUNRISE = 400.0f;
+ /** luminance under a cloudy sky in lux */
public static final float LIGHT_CLOUDY = 100.0f;
- /** Various luminance values during the night (lux) */
+ /** luminance at night with full moon in lux */
public static final float LIGHT_FULLMOON = 0.25f;
+ /** luminance at night with no moon in lux*/
public static final float LIGHT_NO_MOON = 0.001f;
+
/** get sensor data as fast as possible */
public static final int SENSOR_DELAY_FASTEST = 0;
/** rate suitable for games */
@@ -229,16 +317,22 @@ public class SensorManager
public static final int SENSOR_DELAY_NORMAL = 3;
- /** The values returned by this sensor cannot be trusted, calibration
- * is needed or the environment doesn't allow readings */
+ /**
+ * The values returned by this sensor cannot be trusted, calibration is
+ * needed or the environment doesn't allow readings
+ */
public static final int SENSOR_STATUS_UNRELIABLE = 0;
- /** This sensor is reporting data with low accuracy, calibration with the
- * environment is needed */
+ /**
+ * This sensor is reporting data with low accuracy, calibration with the
+ * environment is needed
+ */
public static final int SENSOR_STATUS_ACCURACY_LOW = 1;
- /** This sensor is reporting data with an average level of accuracy,
- * calibration with the environment may improve the readings */
+ /**
+ * This sensor is reporting data with an average level of accuracy,
+ * calibration with the environment may improve the readings
+ */
public static final int SENSOR_STATUS_ACCURACY_MEDIUM = 2;
/** This sensor is reporting data with maximum accuracy */
@@ -259,7 +353,6 @@ public class SensorManager
/*-----------------------------------------------------------------------*/
- private ISensorService mSensorService;
Looper mMainLooper;
@SuppressWarnings("deprecation")
private HashMap<SensorListener, LegacyListener> mLegacyListenersMap =
@@ -276,6 +369,7 @@ public class SensorManager
/* The thread and the sensor list are global to the process
* but the actual thread is spawned on demand */
private static SensorThread sSensorThread;
+ private static int sQueue;
// Used within this module from outside SensorManager, don't make private
static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
@@ -290,80 +384,41 @@ public class SensorManager
boolean mSensorsReady;
SensorThread() {
- // this gets to the sensor module. We can have only one per process.
- sensors_data_init();
}
@Override
protected void finalize() {
- sensors_data_uninit();
}
// must be called with sListeners lock
- boolean startLocked(ISensorService service) {
+ boolean startLocked() {
try {
if (mThread == null) {
- Bundle dataChannel = service.getDataChannel();
- if (dataChannel != null) {
- mSensorsReady = false;
- SensorThreadRunnable runnable = new SensorThreadRunnable(dataChannel);
- Thread thread = new Thread(runnable, SensorThread.class.getName());
- thread.start();
- synchronized (runnable) {
- while (mSensorsReady == false) {
- runnable.wait();
- }
+ mSensorsReady = false;
+ SensorThreadRunnable runnable = new SensorThreadRunnable();
+ Thread thread = new Thread(runnable, SensorThread.class.getName());
+ thread.start();
+ synchronized (runnable) {
+ while (mSensorsReady == false) {
+ runnable.wait();
}
- mThread = thread;
}
+ mThread = thread;
}
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in startLocked: ", e);
} catch (InterruptedException e) {
}
return mThread == null ? false : true;
}
private class SensorThreadRunnable implements Runnable {
- private Bundle mDataChannel;
- SensorThreadRunnable(Bundle dataChannel) {
- mDataChannel = dataChannel;
+ SensorThreadRunnable() {
}
private boolean open() {
// NOTE: this cannot synchronize on sListeners, since
// it's held in the main thread at least until we
// return from here.
-
- // this thread is guaranteed to be unique
- Parcelable[] pfds = mDataChannel.getParcelableArray("fds");
- FileDescriptor[] fds;
- if (pfds != null) {
- int length = pfds.length;
- fds = new FileDescriptor[length];
- for (int i = 0; i < length; i++) {
- ParcelFileDescriptor pfd = (ParcelFileDescriptor)pfds[i];
- fds[i] = pfd.getFileDescriptor();
- }
- } else {
- fds = null;
- }
- int[] ints = mDataChannel.getIntArray("ints");
- sensors_data_open(fds, ints);
- if (pfds != null) {
- try {
- // close our copies of the file descriptors,
- // since we are just passing these to the JNI code and not using them here.
- for (int i = pfds.length - 1; i >= 0; i--) {
- ParcelFileDescriptor pfd = (ParcelFileDescriptor)pfds[i];
- pfd.close();
- }
- } catch (IOException e) {
- // *shrug*
- Log.e(TAG, "IOException: ", e);
- }
- }
- mDataChannel = null;
+ sQueue = sensors_create_queue();
return true;
}
@@ -386,7 +441,7 @@ public class SensorManager
while (true) {
// wait for an event
- final int sensor = sensors_data_poll(values, status, timestamp);
+ final int sensor = sensors_data_poll(sQueue, values, status, timestamp);
int accuracy = status[0];
synchronized (sListeners) {
@@ -398,7 +453,8 @@ public class SensorManager
}
// we have no more listeners or polling failed, terminate the thread
- sensors_data_close();
+ sensors_destroy_queue(sQueue);
+ sQueue = 0;
mThread = null;
break;
}
@@ -426,7 +482,7 @@ public class SensorManager
/*-----------------------------------------------------------------------*/
- private class ListenerDelegate extends Binder {
+ private class ListenerDelegate {
final SensorEventListener mSensorEventListener;
private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
private final Handler mHandler;
@@ -522,8 +578,6 @@ public class SensorManager
* {@hide}
*/
public SensorManager(Looper mainLooper) {
- mSensorService = ISensorService.Stub.asInterface(
- ServiceManager.getService(Context.SENSOR_SERVICE));
mMainLooper = mainLooper;
@@ -540,11 +594,11 @@ public class SensorManager
// which won't get the rotated values
try {
sRotation = sWindowManager.watchRotation(
- new IRotationWatcher.Stub() {
- public void onRotationChanged(int rotation) {
- SensorManager.this.onRotationChanged(rotation);
+ new IRotationWatcher.Stub() {
+ public void onRotationChanged(int rotation) {
+ SensorManager.this.onRotationChanged(rotation);
+ }
}
- }
);
} catch (RemoteException e) {
}
@@ -586,9 +640,10 @@ public class SensorManager
return 0;
}
- /** @return available sensors.
+ /**
+ * @return available sensors.
* @deprecated This method is deprecated, use
- * {@link SensorManager#getSensorList(int)} instead
+ * {@link SensorManager#getSensorList(int)} instead
*/
@Deprecated
public int getSensors() {
@@ -604,7 +659,7 @@ public class SensorManager
break;
case Sensor.TYPE_ORIENTATION:
result |= SensorManager.SENSOR_ORIENTATION |
- SensorManager.SENSOR_ORIENTATION_RAW;
+ SensorManager.SENSOR_ORIENTATION_RAW;
break;
}
}
@@ -612,13 +667,18 @@ public class SensorManager
}
/**
- * Use this method to get the list of available sensors of a certain
- * type. Make multiple calls to get sensors of different types or use
- * {@link android.hardware.Sensor#TYPE_ALL Sensor.TYPE_ALL} to get all
- * the sensors.
+ * Use this method to get the list of available sensors of a certain type.
+ * Make multiple calls to get sensors of different types or use
+ * {@link android.hardware.Sensor#TYPE_ALL Sensor.TYPE_ALL} to get all the
+ * sensors.
+ *
+ * @param type
+ * of sensors requested
*
- * @param type of sensors requested
* @return a list of sensors matching the asked type.
+ *
+ * @see #getDefaultSensor(int)
+ * @see Sensor
*/
public List<Sensor> getSensorList(int type) {
// cache the returned lists the first time
@@ -644,14 +704,18 @@ public class SensorManager
}
/**
- * Use this method to get the default sensor for a given type. Note that
- * the returned sensor could be a composite sensor, and its data could be
+ * Use this method to get the default sensor for a given type. Note that the
+ * returned sensor could be a composite sensor, and its data could be
* averaged or filtered. If you need to access the raw sensors use
* {@link SensorManager#getSensorList(int) getSensorList}.
*
+ * @param type
+ * of sensors requested
*
- * @param type of sensors requested
* @return the default sensors matching the asked type.
+ *
+ * @see #getSensorList(int)
+ * @see Sensor
*/
public Sensor getDefaultSensor(int type) {
// TODO: need to be smarter, for now, just return the 1st sensor
@@ -659,17 +723,21 @@ public class SensorManager
return l.isEmpty() ? null : l.get(0);
}
-
/**
* Registers a listener for given sensors.
+ *
* @deprecated This method is deprecated, use
- * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
- * instead.
+ * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
+ * instead.
*
- * @param listener sensor listener object
- * @param sensors a bit masks of the sensors to register to
+ * @param listener
+ * sensor listener object
*
- * @return true if the sensor is supported and successfully enabled
+ * @param sensors
+ * a bit masks of the sensors to register to
+ *
+ * @return <code>true</code> if the sensor is supported and successfully
+ * enabled
*/
@Deprecated
public boolean registerListener(SensorListener listener, int sensors) {
@@ -678,18 +746,26 @@ public class SensorManager
/**
* Registers a SensorListener for given sensors.
+ *
* @deprecated This method is deprecated, use
- * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
- * instead.
+ * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)}
+ * instead.
+ *
+ * @param listener
+ * sensor listener object
+ *
+ * @param sensors
+ * a bit masks of the sensors to register to
*
- * @param listener sensor listener object
- * @param sensors a bit masks of the sensors to register to
- * @param rate rate of events. This is only a hint to the system. events
- * may be received faster or slower than the specified rate. Usually events
- * are received faster. The value must be one of {@link #SENSOR_DELAY_NORMAL},
- * {@link #SENSOR_DELAY_UI}, {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}.
+ * @param rate
+ * rate of events. This is only a hint to the system. events may be
+ * received faster or slower than the specified rate. Usually events
+ * are received faster. The value must be one of
+ * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
+ * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}.
*
- * @return true if the sensor is supported and successfully enabled
+ * @return <code>true</code> if the sensor is supported and successfully
+ * enabled
*/
@Deprecated
public boolean registerListener(SensorListener listener, int sensors, int rate) {
@@ -747,12 +823,16 @@ public class SensorManager
/**
* Unregisters a listener for the sensors with which it is registered.
+ *
* @deprecated This method is deprecated, use
- * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)}
- * instead.
+ * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)}
+ * instead.
+ *
+ * @param listener
+ * a SensorListener object
*
- * @param listener a SensorListener object
- * @param sensors a bit masks of the sensors to unregister from
+ * @param sensors
+ * a bit masks of the sensors to unregister from
*/
@Deprecated
public void unregisterListener(SensorListener listener, int sensors) {
@@ -815,11 +895,13 @@ public class SensorManager
/**
* Unregisters a listener for all sensors.
+ *
* @deprecated This method is deprecated, use
- * {@link SensorManager#unregisterListener(SensorEventListener)}
- * instead.
+ * {@link SensorManager#unregisterListener(SensorEventListener)}
+ * instead.
*
- * @param listener a SensorListener object
+ * @param listener
+ * a SensorListener object
*/
@Deprecated
public void unregisterListener(SensorListener listener) {
@@ -829,8 +911,14 @@ public class SensorManager
/**
* Unregisters a listener for the sensors with which it is registered.
*
- * @param listener a SensorEventListener object
- * @param sensor the sensor to unregister from
+ * @param listener
+ * a SensorEventListener object
+ *
+ * @param sensor
+ * the sensor to unregister from
+ *
+ * @see #unregisterListener(SensorEventListener)
+ * @see #registerListener(SensorEventListener, Sensor, int)
*
*/
public void unregisterListener(SensorEventListener listener, Sensor sensor) {
@@ -840,27 +928,43 @@ public class SensorManager
/**
* Unregisters a listener for all sensors.
*
- * @param listener a SensorListener object
+ * @param listener
+ * a SensorListener object
+ *
+ * @see #unregisterListener(SensorEventListener, Sensor)
+ * @see #registerListener(SensorEventListener, Sensor, int)
*
*/
public void unregisterListener(SensorEventListener listener) {
unregisterListener((Object)listener);
}
-
/**
- * Registers a {@link android.hardware.SensorEventListener SensorEventListener}
- * for the given sensor.
+ * Registers a {@link android.hardware.SensorEventListener
+ * SensorEventListener} for the given sensor.
*
- * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
- * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
- * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at.
- * This is only a hint to the system. Events may be received faster or
- * slower than the specified rate. Usually events are received faster. The value must be
- * one of {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, {@link #SENSOR_DELAY_GAME},
- * or {@link #SENSOR_DELAY_FASTEST}.
+ * @param listener
+ * A {@link android.hardware.SensorEventListener SensorEventListener}
+ * object.
*
- * @return true if the sensor is supported and successfully enabled.
+ * @param sensor
+ * The {@link android.hardware.Sensor Sensor} to register to.
+ *
+ * @param rate
+ * The rate {@link android.hardware.SensorEvent sensor events} are
+ * delivered at. This is only a hint to the system. Events may be
+ * received faster or slower than the specified rate. Usually events
+ * are received faster. The value must be one of
+ * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
+ * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}
+ * or, the desired delay between events in microsecond.
+ *
+ * @return <code>true</code> if the sensor is supported and successfully
+ * enabled.
+ *
+ * @see #registerListener(SensorEventListener, Sensor, int, Handler)
+ * @see #unregisterListener(SensorEventListener)
+ * @see #unregisterListener(SensorEventListener, Sensor)
*
*/
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) {
@@ -868,21 +972,36 @@ public class SensorManager
}
/**
- * Registers a {@link android.hardware.SensorEventListener SensorEventListener}
- * for the given sensor.
- *
- * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object.
- * @param sensor The {@link android.hardware.Sensor Sensor} to register to.
- * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at.
- * This is only a hint to the system. Events may be received faster or
- * slower than the specified rate. Usually events are received faster. The value must be one
- * of {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, {@link #SENSOR_DELAY_GAME}, or
- * {@link #SENSOR_DELAY_FASTEST}.
- * @param handler The {@link android.os.Handler Handler} the
- * {@link android.hardware.SensorEvent sensor events} will be delivered to.
+ * Registers a {@link android.hardware.SensorEventListener
+ * SensorEventListener} for the given sensor.
+ *
+ * @param listener
+ * A {@link android.hardware.SensorEventListener SensorEventListener}
+ * object.
+ *
+ * @param sensor
+ * The {@link android.hardware.Sensor Sensor} to register to.
+ *
+ * @param rate
+ * The rate {@link android.hardware.SensorEvent sensor events} are
+ * delivered at. This is only a hint to the system. Events may be
+ * received faster or slower than the specified rate. Usually events
+ * are received faster. The value must be one of
+ * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI},
+ * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}.
+ * or, the desired delay between events in microsecond.
+ *
+ * @param handler
+ * The {@link android.os.Handler Handler} the
+ * {@link android.hardware.SensorEvent sensor events} will be
+ * delivered to.
*
* @return true if the sensor is supported and successfully enabled.
*
+ * @see #registerListener(SensorEventListener, Sensor, int)
+ * @see #unregisterListener(SensorEventListener)
+ * @see #unregisterListener(SensorEventListener, Sensor)
+ *
*/
public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate,
Handler handler) {
@@ -896,54 +1015,50 @@ public class SensorManager
delay = 0;
break;
case SENSOR_DELAY_GAME:
- delay = 20;
+ delay = 20000;
break;
case SENSOR_DELAY_UI:
- delay = 60;
+ delay = 60000;
break;
case SENSOR_DELAY_NORMAL:
- delay = 200;
+ delay = 200000;
break;
default:
- return false;
+ delay = rate;
+ break;
}
- try {
- synchronized (sListeners) {
- ListenerDelegate l = null;
- for (ListenerDelegate i : sListeners) {
- if (i.getListener() == listener) {
- l = i;
- break;
- }
+ synchronized (sListeners) {
+ ListenerDelegate l = null;
+ for (ListenerDelegate i : sListeners) {
+ if (i.getListener() == listener) {
+ l = i;
+ break;
}
+ }
- String name = sensor.getName();
- int handle = sensor.getHandle();
- if (l == null) {
- result = false;
- l = new ListenerDelegate(listener, sensor, handler);
- sListeners.add(l);
- if (!sListeners.isEmpty()) {
- result = sSensorThread.startLocked(mSensorService);
- if (result) {
- result = mSensorService.enableSensor(l, name, handle, delay);
- if (!result) {
- // there was an error, remove the listeners
- sListeners.remove(l);
- }
- }
- }
- } else {
- result = mSensorService.enableSensor(l, name, handle, delay);
+ String name = sensor.getName();
+ int handle = sensor.getHandle();
+ if (l == null) {
+ result = false;
+ l = new ListenerDelegate(listener, sensor, handler);
+ sListeners.add(l);
+ if (!sListeners.isEmpty()) {
+ result = sSensorThread.startLocked();
if (result) {
- l.addSensor(sensor);
+ result = sensors_enable_sensor(sQueue, name, handle, delay);
+ if (!result) {
+ // there was an error, remove the listeners
+ sListeners.remove(l);
+ }
}
}
+ } else {
+ result = sensors_enable_sensor(sQueue, name, handle, delay);
+ if (result) {
+ l.addSensor(sensor);
+ }
}
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in registerListener: ", e);
- result = false;
}
return result;
}
@@ -952,27 +1067,23 @@ public class SensorManager
if (listener == null || sensor == null) {
return;
}
- try {
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- // disable these sensors
- String name = sensor.getName();
- int handle = sensor.getHandle();
- mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE);
- // if we have no more sensors enabled on this listener,
- // take it off the list.
- if (l.removeSensor(sensor) == 0) {
- sListeners.remove(i);
- }
- break;
+ synchronized (sListeners) {
+ final int size = sListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate l = sListeners.get(i);
+ if (l.getListener() == listener) {
+ // disable these sensors
+ String name = sensor.getName();
+ int handle = sensor.getHandle();
+ sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
+ // if we have no more sensors enabled on this listener,
+ // take it off the list.
+ if (l.removeSensor(sensor) == 0) {
+ sListeners.remove(i);
}
+ break;
}
}
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in unregisterListener: ", e);
}
}
@@ -980,83 +1091,102 @@ public class SensorManager
if (listener == null) {
return;
}
- try {
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- // disable all sensors for this listener
- for (Sensor sensor : l.getSensors()) {
- String name = sensor.getName();
- int handle = sensor.getHandle();
- mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE);
- }
- sListeners.remove(i);
- break;
+ synchronized (sListeners) {
+ final int size = sListeners.size();
+ for (int i=0 ; i<size ; i++) {
+ ListenerDelegate l = sListeners.get(i);
+ if (l.getListener() == listener) {
+ // disable all sensors for this listener
+ for (Sensor sensor : l.getSensors()) {
+ String name = sensor.getName();
+ int handle = sensor.getHandle();
+ sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
}
+ sListeners.remove(i);
+ break;
}
}
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in unregisterListener: ", e);
}
}
/**
- * Computes the inclination matrix <b>I</b> as well as the rotation
- * matrix <b>R</b> transforming a vector from the
- * device coordinate system to the world's coordinate system which is
- * defined as a direct orthonormal basis, where:
- *
+ * <p>
+ * Computes the inclination matrix <b>I</b> as well as the rotation matrix
+ * <b>R</b> transforming a vector from the device coordinate system to the
+ * world's coordinate system which is defined as a direct orthonormal basis,
+ * where:
+ * </p>
+ *
+ * <ul>
* <li>X is defined as the vector product <b>Y.Z</b> (It is tangential to
* the ground at the device's current location and roughly points East).</li>
* <li>Y is tangential to the ground at the device's current location and
* points towards the magnetic North Pole.</li>
* <li>Z points towards the sky and is perpendicular to the ground.</li>
+ * </ul>
+ *
+ * <p>
+ * <center><img src="../../../images/axis_globe.png"
+ * alt="Sensors coordinate-system diagram." border="0" /></center>
+ * </p>
+ *
* <p>
* <hr>
- * <p>By definition:
- * <p>[0 0 g] = <b>R</b> * <b>gravity</b> (g = magnitude of gravity)
- * <p>[0 m 0] = <b>I</b> * <b>R</b> * <b>geomagnetic</b>
- * (m = magnitude of geomagnetic field)
- * <p><b>R</b> is the identity matrix when the device is aligned with the
+ * <p>
+ * By definition:
+ * <p>
+ * [0 0 g] = <b>R</b> * <b>gravity</b> (g = magnitude of gravity)
+ * <p>
+ * [0 m 0] = <b>I</b> * <b>R</b> * <b>geomagnetic</b> (m = magnitude of
+ * geomagnetic field)
+ * <p>
+ * <b>R</b> is the identity matrix when the device is aligned with the
* world's coordinate system, that is, when the device's X axis points
* toward East, the Y axis points to the North Pole and the device is facing
* the sky.
*
- * <p><b>I</b> is a rotation matrix transforming the geomagnetic
- * vector into the same coordinate space as gravity (the world's coordinate
- * space). <b>I</b> is a simple rotation around the X axis.
- * The inclination angle in radians can be computed with
- * {@link #getInclination}.
+ * <p>
+ * <b>I</b> is a rotation matrix transforming the geomagnetic vector into
+ * the same coordinate space as gravity (the world's coordinate space).
+ * <b>I</b> is a simple rotation around the X axis. The inclination angle in
+ * radians can be computed with {@link #getInclination}.
* <hr>
- *
- * <p> Each matrix is returned either as a 3x3 or 4x4 row-major matrix
- * depending on the length of the passed array:
- * <p><u>If the array length is 16:</u>
+ *
+ * <p>
+ * Each matrix is returned either as a 3x3 or 4x4 row-major matrix depending
+ * on the length of the passed array:
+ * <p>
+ * <u>If the array length is 16:</u>
+ *
* <pre>
* / M[ 0] M[ 1] M[ 2] M[ 3] \
* | M[ 4] M[ 5] M[ 6] M[ 7] |
* | M[ 8] M[ 9] M[10] M[11] |
* \ M[12] M[13] M[14] M[15] /
*</pre>
- * This matrix is ready to be used by OpenGL ES's
- * {@link javax.microedition.khronos.opengles.GL10#glLoadMatrixf(float[], int)
- * glLoadMatrixf(float[], int)}.
- * <p>Note that because OpenGL matrices are column-major matrices you must
- * transpose the matrix before using it. However, since the matrix is a
+ *
+ * This matrix is ready to be used by OpenGL ES's
+ * {@link javax.microedition.khronos.opengles.GL10#glLoadMatrixf(float[], int)
+ * glLoadMatrixf(float[], int)}.
+ * <p>
+ * Note that because OpenGL matrices are column-major matrices you must
+ * transpose the matrix before using it. However, since the matrix is a
* rotation matrix, its transpose is also its inverse, conveniently, it is
* often the inverse of the rotation that is needed for rendering; it can
* therefore be used with OpenGL ES directly.
* <p>
* Also note that the returned matrices always have this form:
+ *
* <pre>
* / M[ 0] M[ 1] M[ 2] 0 \
* | M[ 4] M[ 5] M[ 6] 0 |
* | M[ 8] M[ 9] M[10] 0 |
* \ 0 0 0 1 /
*</pre>
- * <p><u>If the array length is 9:</u>
+ *
+ * <p>
+ * <u>If the array length is 9:</u>
+ *
* <pre>
* / M[ 0] M[ 1] M[ 2] \
* | M[ 3] M[ 4] M[ 5] |
@@ -1064,34 +1194,52 @@ public class SensorManager
*</pre>
*
* <hr>
- * <p>The inverse of each matrix can be computed easily by taking its
+ * <p>
+ * The inverse of each matrix can be computed easily by taking its
* transpose.
*
- * <p>The matrices returned by this function are meaningful only when the
- * device is not free-falling and it is not close to the magnetic north.
- * If the device is accelerating, or placed into a strong magnetic field,
- * the returned matrices may be inaccurate.
- *
- * @param R is an array of 9 floats holding the rotation matrix <b>R</b>
- * when this function returns. R can be null.<p>
- * @param I is an array of 9 floats holding the rotation matrix <b>I</b>
- * when this function returns. I can be null.<p>
- * @param gravity is an array of 3 floats containing the gravity vector
- * expressed in the device's coordinate. You can simply use the
- * {@link android.hardware.SensorEvent#values values}
- * returned by a {@link android.hardware.SensorEvent SensorEvent} of a
- * {@link android.hardware.Sensor Sensor} of type
- * {@link android.hardware.Sensor#TYPE_ACCELEROMETER TYPE_ACCELEROMETER}.<p>
- * @param geomagnetic is an array of 3 floats containing the geomagnetic
- * vector expressed in the device's coordinate. You can simply use the
- * {@link android.hardware.SensorEvent#values values}
- * returned by a {@link android.hardware.SensorEvent SensorEvent} of a
- * {@link android.hardware.Sensor Sensor} of type
- * {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD TYPE_MAGNETIC_FIELD}.
- * @return
- * true on success<p>
- * false on failure (for instance, if the device is in free fall).
- * On failure the output matrices are not modified.
+ * <p>
+ * The matrices returned by this function are meaningful only when the
+ * device is not free-falling and it is not close to the magnetic north. If
+ * the device is accelerating, or placed into a strong magnetic field, the
+ * returned matrices may be inaccurate.
+ *
+ * @param R
+ * is an array of 9 floats holding the rotation matrix <b>R</b> when
+ * this function returns. R can be null.
+ * <p>
+ *
+ * @param I
+ * is an array of 9 floats holding the rotation matrix <b>I</b> when
+ * this function returns. I can be null.
+ * <p>
+ *
+ * @param gravity
+ * is an array of 3 floats containing the gravity vector expressed in
+ * the device's coordinate. You can simply use the
+ * {@link android.hardware.SensorEvent#values values} returned by a
+ * {@link android.hardware.SensorEvent SensorEvent} of a
+ * {@link android.hardware.Sensor Sensor} of type
+ * {@link android.hardware.Sensor#TYPE_ACCELEROMETER
+ * TYPE_ACCELEROMETER}.
+ * <p>
+ *
+ * @param geomagnetic
+ * is an array of 3 floats containing the geomagnetic vector
+ * expressed in the device's coordinate. You can simply use the
+ * {@link android.hardware.SensorEvent#values values} returned by a
+ * {@link android.hardware.SensorEvent SensorEvent} of a
+ * {@link android.hardware.Sensor Sensor} of type
+ * {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD
+ * TYPE_MAGNETIC_FIELD}.
+ *
+ * @return <code>true</code> on success, <code>false</code> on failure (for
+ * instance, if the device is in free fall). On failure the output
+ * matrices are not modified.
+ *
+ * @see #getInclination(float[])
+ * @see #getOrientation(float[], float[])
+ * @see #remapCoordinateSystem(float[], int, int, float[])
*/
public static boolean getRotationMatrix(float[] R, float[] I,
@@ -1160,64 +1308,98 @@ public class SensorManager
/**
* Computes the geomagnetic inclination angle in radians from the
* inclination matrix <b>I</b> returned by {@link #getRotationMatrix}.
- * @param I inclination matrix see {@link #getRotationMatrix}.
+ *
+ * @param I
+ * inclination matrix see {@link #getRotationMatrix}.
+ *
* @return The geomagnetic inclination angle in radians.
+ *
+ * @see #getRotationMatrix(float[], float[], float[], float[])
+ * @see #getOrientation(float[], float[])
+ * @see GeomagneticField
+ *
*/
public static float getInclination(float[] I) {
if (I.length == 9) {
return (float)Math.atan2(I[5], I[4]);
} else {
- return (float)Math.atan2(I[6], I[5]);
+ return (float)Math.atan2(I[6], I[5]);
}
}
/**
- * Rotates the supplied rotation matrix so it is expressed in a
- * different coordinate system. This is typically used when an application
- * needs to compute the three orientation angles of the device (see
+ * <p>
+ * Rotates the supplied rotation matrix so it is expressed in a different
+ * coordinate system. This is typically used when an application needs to
+ * compute the three orientation angles of the device (see
* {@link #getOrientation}) in a different coordinate system.
- *
- * <p>When the rotation matrix is used for drawing (for instance with
- * OpenGL ES), it usually <b>doesn't need</b> to be transformed by this
- * function, unless the screen is physically rotated, in which case you
- * can use {@link android.view.Display#getRotation() Display.getRotation()}
- * to retrieve the current rotation of the screen. Note that because the
- * user is generally free to rotate their screen, you often should
- * consider the rotation in deciding the parameters to use here.
+ * </p>
*
- * <p><u>Examples:</u><p>
+ * <p>
+ * When the rotation matrix is used for drawing (for instance with OpenGL
+ * ES), it usually <b>doesn't need</b> to be transformed by this function,
+ * unless the screen is physically rotated, in which case you can use
+ * {@link android.view.Display#getRotation() Display.getRotation()} to
+ * retrieve the current rotation of the screen. Note that because the user
+ * is generally free to rotate their screen, you often should consider the
+ * rotation in deciding the parameters to use here.
+ * </p>
*
- * <li>Using the camera (Y axis along the camera's axis) for an augmented
- * reality application where the rotation angles are needed: </li><p>
+ * <p>
+ * <u>Examples:</u>
+ * <p>
*
- * <code>remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);</code><p>
+ * <ul>
+ * <li>Using the camera (Y axis along the camera's axis) for an augmented
+ * reality application where the rotation angles are needed:</li>
+ *
+ * <p>
+ * <ul>
+ * <code>remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);</code>
+ * </ul>
+ * </p>
*
* <li>Using the device as a mechanical compass when rotation is
- * {@link android.view.Surface#ROTATION_90 Surface.ROTATION_90}:</li><p>
- *
- * <code>remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);</code><p>
- *
- * Beware of the above example. This call is needed only to account for
- * a rotation from its natural orientation when calculating the
- * rotation angles (see {@link #getOrientation}).
- * If the rotation matrix is also used for rendering, it may not need to
- * be transformed, for instance if your {@link android.app.Activity
- * Activity} is running in landscape mode.
- *
- * <p>Since the resulting coordinate system is orthonormal, only two axes
- * need to be specified.
- *
- * @param inR the rotation matrix to be transformed. Usually it is the
- * matrix returned by {@link #getRotationMatrix}.
- * @param X defines on which world axis and direction the X axis of the
- * device is mapped.
- * @param Y defines on which world axis and direction the Y axis of the
- * device is mapped.
- * @param outR the transformed rotation matrix. inR and outR can be the same
+ * {@link android.view.Surface#ROTATION_90 Surface.ROTATION_90}:</li>
+ *
+ * <p>
+ * <ul>
+ * <code>remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);</code>
+ * </ul>
+ * </p>
+ *
+ * Beware of the above example. This call is needed only to account for a
+ * rotation from its natural orientation when calculating the rotation
+ * angles (see {@link #getOrientation}). If the rotation matrix is also used
+ * for rendering, it may not need to be transformed, for instance if your
+ * {@link android.app.Activity Activity} is running in landscape mode.
+ * </ul>
+ *
+ * <p>
+ * Since the resulting coordinate system is orthonormal, only two axes need
+ * to be specified.
+ *
+ * @param inR
+ * the rotation matrix to be transformed. Usually it is the matrix
+ * returned by {@link #getRotationMatrix}.
+ *
+ * @param X
+ * defines on which world axis and direction the X axis of the device
+ * is mapped.
+ *
+ * @param Y
+ * defines on which world axis and direction the Y axis of the device
+ * is mapped.
+ *
+ * @param outR
+ * the transformed rotation matrix. inR and outR can be the same
* array, but it is not recommended for performance reason.
- * @return true on success. false if the input parameters are incorrect, for
- * instance if X and Y define the same axis. Or if inR and outR don't have
- * the same length.
+ *
+ * @return <code>true</code> on success. <code>false</code> if the input
+ * parameters are incorrect, for instance if X and Y define the same
+ * axis. Or if inR and outR don't have the same length.
+ *
+ * @see #getRotationMatrix(float[], float[], float[], float[])
*/
public static boolean remapCoordinateSystem(float[] inR, int X, int Y,
@@ -1301,17 +1483,31 @@ public class SensorManager
/**
* Computes the device's orientation based on the rotation matrix.
- * <p> When it returns, the array values is filled with the result:
+ * <p>
+ * When it returns, the array values is filled with the result:
+ * <ul>
* <li>values[0]: <i>azimuth</i>, rotation around the Z axis.</li>
* <li>values[1]: <i>pitch</i>, rotation around the X axis.</li>
* <li>values[2]: <i>roll</i>, rotation around the Y axis.</li>
+ * </ul>
+ * <p>
+ * <center><img src="../../../images/axis_device.png"
+ * alt="Sensors coordinate-system diagram." border="0" /></center>
+ * </p>
* <p>
* All three angles above are in <b>radians</b> and <b>positive</b> in the
* <b>counter-clockwise</b> direction.
- *
- * @param R rotation matrix see {@link #getRotationMatrix}.
- * @param values an array of 3 floats to hold the result.
+ *
+ * @param R
+ * rotation matrix see {@link #getRotationMatrix}.
+ *
+ * @param values
+ * an array of 3 floats to hold the result.
+ *
* @return The array values passed as argument.
+ *
+ * @see #getRotationMatrix(float[], float[], float[], float[])
+ * @see GeomagneticField
*/
public static float[] getOrientation(float[] R, float values[]) {
/*
@@ -1320,12 +1516,12 @@ public class SensorManager
* | R[ 4] R[ 5] R[ 6] 0 |
* | R[ 8] R[ 9] R[10] 0 |
* \ 0 0 0 1 /
- *
+ *
* 3x3 (length=9) case:
* / R[ 0] R[ 1] R[ 2] \
* | R[ 3] R[ 4] R[ 5] |
* \ R[ 6] R[ 7] R[ 8] /
- *
+ *
*/
if (R.length == 9) {
values[0] = (float)Math.atan2(R[1], R[4]);
@@ -1339,8 +1535,42 @@ public class SensorManager
return values;
}
-
/**
+ * Computes the Altitude in meters from the atmospheric pressure and the
+ * pressure at sea level.
+ * <p>
+ * Typically the atmospheric pressure is read from a
+ * {@link Sensor#TYPE_PRESSURE} sensor. The pressure at sea level must be
+ * known, usually it can be retrieved from airport databases in the
+ * vicinity. If unknown, you can use {@link #PRESSURE_STANDARD_ATMOSPHERE}
+ * as an approximation, but absolute altitudes won't be accurate.
+ * </p>
+ * <p>
+ * To calculate altitude differences, you must calculate the difference
+ * between the altitudes at both points. If you don't know the altitude
+ * as sea level, you can use {@link #PRESSURE_STANDARD_ATMOSPHERE} instead,
+ * which will give good results considering the range of pressure typically
+ * involved.
+ * </p>
+ * <p>
+ * <code><ul>
+ * float altitude_difference =
+ * getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure_at_point2)
+ * - getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure_at_point1);
+ * </ul></code>
+ * </p>
+ *
+ * @param p0 pressure at sea level
+ * @param p atmospheric pressure
+ * @return Altitude in meters
+ */
+ public static float getAltitude(float p0, float p) {
+ final float coef = 1.0f / 5.255f;
+ return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef));
+ }
+
+
+ /**
* {@hide}
*/
public void onRotationChanged(int rotation) {
@@ -1487,7 +1717,7 @@ public class SensorManager
}
}
}
-
+
class LmsFilter {
private static final int SENSORS_RATE_MS = 20;
private static final int COUNT = 12;
@@ -1555,16 +1785,192 @@ public class SensorManager
}
}
-
+
+ /** Helper function to compute the angle change between two rotation matrices.
+ * Given a current rotation matrix (R) and a previous rotation matrix
+ * (prevR) computes the rotation around the x,y, and z axes which
+ * transforms prevR to R.
+ * outputs a 3 element vector containing the x,y, and z angle
+ * change at indexes 0, 1, and 2 respectively.
+ * <p> Each input matrix is either as a 3x3 or 4x4 row-major matrix
+ * depending on the length of the passed array:
+ * <p>If the array length is 9, then the array elements represent this matrix
+ * <pre>
+ * / R[ 0] R[ 1] R[ 2] \
+ * | R[ 3] R[ 4] R[ 5] |
+ * \ R[ 6] R[ 7] R[ 8] /
+ *</pre>
+ * <p>If the array length is 16, then the array elements represent this matrix
+ * <pre>
+ * / R[ 0] R[ 1] R[ 2] R[ 3] \
+ * | R[ 4] R[ 5] R[ 6] R[ 7] |
+ * | R[ 8] R[ 9] R[10] R[11] |
+ * \ R[12] R[13] R[14] R[15] /
+ *</pre>
+ * @param R current rotation matrix
+ * @param prevR previous rotation matrix
+ * @param angleChange an array of floats in which the angle change is stored
+ */
+
+ public static void getAngleChange( float[] angleChange, float[] R, float[] prevR) {
+ float rd1=0,rd4=0, rd6=0,rd7=0, rd8=0;
+ float ri0=0,ri1=0,ri2=0,ri3=0,ri4=0,ri5=0,ri6=0,ri7=0,ri8=0;
+ float pri0=0, pri1=0, pri2=0, pri3=0, pri4=0, pri5=0, pri6=0, pri7=0, pri8=0;
+ int i, j, k;
+
+ if(R.length == 9) {
+ ri0 = R[0];
+ ri1 = R[1];
+ ri2 = R[2];
+ ri3 = R[3];
+ ri4 = R[4];
+ ri5 = R[5];
+ ri6 = R[6];
+ ri7 = R[7];
+ ri8 = R[8];
+ } else if(R.length == 16) {
+ ri0 = R[0];
+ ri1 = R[1];
+ ri2 = R[2];
+ ri3 = R[4];
+ ri4 = R[5];
+ ri5 = R[6];
+ ri6 = R[8];
+ ri7 = R[9];
+ ri8 = R[10];
+ }
+
+ if(prevR.length == 9) {
+ pri0 = R[0];
+ pri1 = R[1];
+ pri2 = R[2];
+ pri3 = R[3];
+ pri4 = R[4];
+ pri5 = R[5];
+ pri6 = R[6];
+ pri7 = R[7];
+ pri8 = R[8];
+ } else if(prevR.length == 16) {
+ pri0 = R[0];
+ pri1 = R[1];
+ pri2 = R[2];
+ pri3 = R[4];
+ pri4 = R[5];
+ pri5 = R[6];
+ pri6 = R[8];
+ pri7 = R[9];
+ pri8 = R[10];
+ }
+
+ // calculate the parts of the rotation difference matrix we need
+ // rd[i][j] = pri[0][i] * ri[0][j] + pri[1][i] * ri[1][j] + pri[2][i] * ri[2][j];
+
+ rd1 = pri0 * ri1 + pri3 * ri4 + pri6 * ri7; //rd[0][1]
+ rd4 = pri1 * ri1 + pri4 * ri4 + pri7 * ri7; //rd[1][1]
+ rd6 = pri2 * ri0 + pri5 * ri3 + pri8 * ri6; //rd[2][0]
+ rd7 = pri2 * ri1 + pri5 * ri4 + pri8 * ri7; //rd[2][1]
+ rd8 = pri2 * ri2 + pri5 * ri5 + pri8 * ri8; //rd[2][2]
+
+ angleChange[0] = (float)Math.atan2(rd1, rd4);
+ angleChange[1] = (float)Math.asin(-rd7);
+ angleChange[2] = (float)Math.atan2(-rd6, rd8);
+
+ }
+
+ /** Helper function to convert a rotation vector to a rotation matrix.
+ * Given a rotation vector (presumably from a ROTATION_VECTOR sensor), returns a
+ * 9 or 16 element rotation matrix in the array R. R must have length 9 or 16.
+ * If R.length == 9, the following matrix is returned:
+ * <pre>
+ * / R[ 0] R[ 1] R[ 2] \
+ * | R[ 3] R[ 4] R[ 5] |
+ * \ R[ 6] R[ 7] R[ 8] /
+ *</pre>
+ * If R.length == 16, the following matrix is returned:
+ * <pre>
+ * / R[ 0] R[ 1] R[ 2] 0 \
+ * | R[ 4] R[ 5] R[ 6] 0 |
+ * | R[ 8] R[ 9] R[10] 0 |
+ * \ 0 0 0 1 /
+ *</pre>
+ * @param rotationVector the rotation vector to convert
+ * @param R an array of floats in which to store the rotation matrix
+ */
+ public static void getRotationMatrixFromVector(float[] R, float[] rotationVector) {
+ float q0 = (float)Math.sqrt(1 - rotationVector[0]*rotationVector[0] -
+ rotationVector[1]*rotationVector[1] -
+ rotationVector[2]*rotationVector[2]);
+ float q1 = rotationVector[0];
+ float q2 = rotationVector[1];
+ float q3 = rotationVector[2];
+
+ float sq_q1 = 2 * q1 * q1;
+ float sq_q2 = 2 * q2 * q2;
+ float sq_q3 = 2 * q3 * q3;
+ float q1_q2 = 2 * q1 * q2;
+ float q3_q0 = 2 * q3 * q0;
+ float q1_q3 = 2 * q1 * q3;
+ float q2_q0 = 2 * q2 * q0;
+ float q2_q3 = 2 * q2 * q3;
+ float q1_q0 = 2 * q1 * q0;
+
+ if(R.length == 9) {
+ R[0] = 1 - sq_q2 - sq_q3;
+ R[1] = q1_q2 - q3_q0;
+ R[2] = q1_q3 + q2_q0;
+
+ R[3] = q1_q2 + q3_q0;
+ R[4] = 1 - sq_q1 - sq_q3;
+ R[5] = q2_q3 - q1_q0;
+
+ R[6] = q1_q3 - q2_q0;
+ R[7] = q2_q3 + q1_q0;
+ R[8] = 1 - sq_q1 - sq_q2;
+ } else if (R.length == 16) {
+ R[0] = 1 - sq_q2 - sq_q3;
+ R[1] = q1_q2 - q3_q0;
+ R[2] = q1_q3 + q2_q0;
+ R[3] = 0.0f;
+
+ R[4] = q1_q2 + q3_q0;
+ R[5] = 1 - sq_q1 - sq_q3;
+ R[6] = q2_q3 - q1_q0;
+ R[7] = 0.0f;
+
+ R[8] = q1_q3 - q2_q0;
+ R[9] = q2_q3 + q1_q0;
+ R[10] = 1 - sq_q1 - sq_q2;
+ R[11] = 0.0f;
+
+ R[12] = R[13] = R[14] = 0.0f;
+ R[15] = 1.0f;
+ }
+ }
+
+ /** Helper function to convert a rotation vector to a normalized quaternion.
+ * Given a rotation vector (presumably from a ROTATION_VECTOR sensor), returns a normalized
+ * quaternion in the array Q. The quaternion is stored as [w, x, y, z]
+ * @param rv the rotation vector to convert
+ * @param Q an array of floats in which to store the computed quaternion
+ */
+ public static void getQuaternionFromVector(float[] Q, float[] rv) {
+ float w = (float)Math.sqrt(1 - rv[0]*rv[0] - rv[1]*rv[1] - rv[2]*rv[2]);
+ //In this case, the w component of the quaternion is known to be a positive number
+
+ Q[0] = w;
+ Q[1] = rv[0];
+ Q[2] = rv[1];
+ Q[3] = rv[2];
+ }
+
private static native void nativeClassInit();
private static native int sensors_module_init();
private static native int sensors_module_get_next_sensor(Sensor sensor, int next);
// Used within this module from outside SensorManager, don't make private
- static native int sensors_data_init();
- static native int sensors_data_uninit();
- static native int sensors_data_open(FileDescriptor[] fds, int[] ints);
- static native int sensors_data_close();
- static native int sensors_data_poll(float[] values, int[] status, long[] timestamp);
+ static native int sensors_create_queue();
+ static native void sensors_destroy_queue(int queue);
+ static native boolean sensors_enable_sensor(int queue, String name, int sensor, int enable);
+ static native int sensors_data_poll(int queue, float[] values, int[] status, long[] timestamp);
}
diff --git a/core/java/android/hardware/Usb.java b/core/java/android/hardware/Usb.java
new file mode 100644
index 000000000000..57271d4b72f8
--- /dev/null
+++ b/core/java/android/hardware/Usb.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.hardware;
+
+/**
+ * Class for accessing USB state information.
+ * @hide
+ */
+public class Usb {
+ /**
+ * Broadcast Action: A broadcast for USB connected events.
+ *
+ * The extras bundle will name/value pairs with the name of the function
+ * and a value of either {@link #USB_FUNCTION_ENABLED} or {@link #USB_FUNCTION_DISABLED}.
+ * Possible USB function names include {@link #USB_FUNCTION_MASS_STORAGE},
+ * {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS} and {@link #USB_FUNCTION_MTP}.
+ */
+ public static final String ACTION_USB_CONNECTED =
+ "android.hardware.action.USB_CONNECTED";
+
+ /**
+ * Broadcast Action: A broadcast for USB disconnected events.
+ */
+ public static final String ACTION_USB_DISCONNECTED =
+ "android.hardware.action.USB_DISCONNECTED";
+
+ /**
+ * Broadcast Action: A sticky broadcast for USB state change events.
+ *
+ * This is a sticky broadcast for clients that are interested in both USB connect and
+ * disconnect events. If you are only concerned with one or the other, you can use
+ * {@link #ACTION_USB_CONNECTED} or {@link #ACTION_USB_DISCONNECTED} to avoid receiving
+ * unnecessary broadcasts. The boolean {@link #USB_CONNECTED} extra indicates whether
+ * USB is connected or disconnected.
+ * The extras bundle will also contain name/value pairs with the name of the function
+ * and a value of either {@link #USB_FUNCTION_ENABLED} or {@link #USB_FUNCTION_DISABLED}.
+ * Possible USB function names include {@link #USB_FUNCTION_MASS_STORAGE},
+ * {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS} and {@link #USB_FUNCTION_MTP}.
+ */
+ public static final String ACTION_USB_STATE =
+ "android.hardware.action.USB_STATE";
+
+ /**
+ * Boolean extra indicating whether USB is connected or disconnected.
+ * Used in extras for the {@link #ACTION_USB_STATE} broadcast.
+ */
+ public static final String USB_CONNECTED = "connected";
+
+ /**
+ * Name of the USB mass storage USB function.
+ * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast
+ */
+ public static final String USB_FUNCTION_MASS_STORAGE = "mass_storage";
+
+ /**
+ * Name of the adb USB function.
+ * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast
+ */
+ public static final String USB_FUNCTION_ADB = "adb";
+
+ /**
+ * Name of the RNDIS ethernet USB function.
+ * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast
+ */
+ public static final String USB_FUNCTION_RNDIS = "rndis";
+
+ /**
+ * Name of the MTP USB function.
+ * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast
+ */
+ public static final String USB_FUNCTION_MTP = "mtp";
+
+ /**
+ * Value indicating that a USB function is enabled.
+ * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast
+ */
+ public static final String USB_FUNCTION_ENABLED = "enabled";
+
+ /**
+ * Value indicating that a USB function is disabled.
+ * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast
+ */
+ public static final String USB_FUNCTION_DISABLED = "disabled";
+}
diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java
index b7d53e26e543..8a52e4025b72 100644
--- a/core/java/android/inputmethodservice/ExtractEditText.java
+++ b/core/java/android/inputmethodservice/ExtractEditText.java
@@ -18,6 +18,7 @@ package android.inputmethodservice;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.ContextMenu;
import android.view.inputmethod.ExtractedText;
import android.widget.EditText;
@@ -28,6 +29,7 @@ import android.widget.EditText;
public class ExtractEditText extends EditText {
private InputMethodService mIME;
private int mSettingExtractedText;
+ private boolean mContextMenuShouldBeHandledBySuper = false;
public ExtractEditText(Context context) {
super(context, null);
@@ -97,18 +99,26 @@ public class ExtractEditText extends EditText {
return false;
}
+ @Override
+ protected void onCreateContextMenu(ContextMenu menu) {
+ super.onCreateContextMenu(menu);
+ mContextMenuShouldBeHandledBySuper = true;
+ }
+
@Override public boolean onTextContextMenuItem(int id) {
- if (mIME != null) {
+ if (mIME != null && !mContextMenuShouldBeHandledBySuper) {
if (mIME.onExtractTextContextMenuItem(id)) {
return true;
}
}
+ mContextMenuShouldBeHandledBySuper = false;
return super.onTextContextMenuItem(id);
}
/**
* We are always considered to be an input method target.
*/
+ @Override
public boolean isInputMethodTarget() {
return true;
}
@@ -125,7 +135,7 @@ public class ExtractEditText extends EditText {
* highlight and cursor will be displayed.
*/
@Override public boolean hasWindowFocus() {
- return this.isEnabled() ? true : false;
+ return this.isEnabled();
}
/**
@@ -133,7 +143,7 @@ public class ExtractEditText extends EditText {
* highlight and cursor will be displayed.
*/
@Override public boolean isFocused() {
- return this.isEnabled() ? true : false;
+ return this.isEnabled();
}
/**
@@ -141,6 +151,6 @@ public class ExtractEditText extends EditText {
* highlight and cursor will be displayed.
*/
@Override public boolean hasFocus() {
- return this.isEnabled() ? true : false;
+ return this.isEnabled();
}
}
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 80e9865e5a81..44f30f7c5dac 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -47,9 +47,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
private static final int DO_UPDATE_CURSOR = 95;
private static final int DO_APP_PRIVATE_COMMAND = 100;
private static final int DO_TOGGLE_SOFT_INPUT = 105;
-
- final HandlerCaller mCaller;
- final InputMethodSession mInputMethodSession;
+ private static final int DO_FINISH_SESSION = 110;
+
+ HandlerCaller mCaller;
+ InputMethodSession mInputMethodSession;
// NOTE: we should have a cache of these.
static class InputMethodEventCallbackWrapper implements InputMethodSession.EventCallback {
@@ -127,6 +128,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2);
return;
}
+ case DO_FINISH_SESSION: {
+ mInputMethodSession = null;
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -174,4 +179,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
public void toggleSoftInput(int showFlags, int hideFlags) {
mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags));
}
+
+ public void finishSession() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION));
+ }
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index bfa82ee0d13c..35fd46f5a2f2 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -39,6 +39,7 @@ import android.view.inputmethod.InputMethodSession;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -64,9 +65,9 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_SHOW_SOFT_INPUT = 60;
private static final int DO_HIDE_SOFT_INPUT = 70;
- final AbstractInputMethodService mTarget;
+ final WeakReference<AbstractInputMethodService> mTarget;
final HandlerCaller mCaller;
- final InputMethod mInputMethod;
+ final WeakReference<InputMethod> mInputMethod;
static class Notifier {
boolean notified;
@@ -96,21 +97,32 @@ class IInputMethodWrapper extends IInputMethod.Stub
public IInputMethodWrapper(AbstractInputMethodService context,
InputMethod inputMethod) {
- mTarget = context;
- mCaller = new HandlerCaller(context, this);
- mInputMethod = inputMethod;
+ mTarget = new WeakReference<AbstractInputMethodService>(context);
+ mCaller = new HandlerCaller(context.getApplicationContext(), this);
+ mInputMethod = new WeakReference<InputMethod>(inputMethod);
}
public InputMethod getInternalInputMethod() {
- return mInputMethod;
+ return mInputMethod.get();
}
public void executeMessage(Message msg) {
+ InputMethod inputMethod = mInputMethod.get();
+ // Need a valid reference to the inputMethod for everything except a dump.
+ if (inputMethod == null && msg.what != DO_DUMP) {
+ Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what);
+ return;
+ }
+
switch (msg.what) {
case DO_DUMP: {
+ AbstractInputMethodService target = mTarget.get();
+ if (target == null) {
+ return;
+ }
HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
try {
- mTarget.dump((FileDescriptor)args.arg1,
+ target.dump((FileDescriptor)args.arg1,
(PrintWriter)args.arg2, (String[])args.arg3);
} catch (RuntimeException e) {
((PrintWriter)args.arg2).println("Exception: " + e);
@@ -122,22 +134,22 @@ class IInputMethodWrapper extends IInputMethod.Stub
}
case DO_ATTACH_TOKEN: {
- mInputMethod.attachToken((IBinder)msg.obj);
+ inputMethod.attachToken((IBinder)msg.obj);
return;
}
case DO_SET_INPUT_CONTEXT: {
- mInputMethod.bindInput((InputBinding)msg.obj);
+ inputMethod.bindInput((InputBinding)msg.obj);
return;
}
case DO_UNSET_INPUT_CONTEXT:
- mInputMethod.unbindInput();
+ inputMethod.unbindInput();
return;
case DO_START_INPUT: {
HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj;
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext) : null;
- mInputMethod.startInput(ic, (EditorInfo)args.arg2);
+ inputMethod.startInput(ic, (EditorInfo)args.arg2);
return;
}
case DO_RESTART_INPUT: {
@@ -145,33 +157,37 @@ class IInputMethodWrapper extends IInputMethod.Stub
IInputContext inputContext = (IInputContext)args.arg1;
InputConnection ic = inputContext != null
? new InputConnectionWrapper(inputContext) : null;
- mInputMethod.restartInput(ic, (EditorInfo)args.arg2);
+ inputMethod.restartInput(ic, (EditorInfo)args.arg2);
return;
}
case DO_CREATE_SESSION: {
- mInputMethod.createSession(new InputMethodSessionCallbackWrapper(
+ inputMethod.createSession(new InputMethodSessionCallbackWrapper(
mCaller.mContext, (IInputMethodCallback)msg.obj));
return;
}
case DO_SET_SESSION_ENABLED:
- mInputMethod.setSessionEnabled((InputMethodSession)msg.obj,
+ inputMethod.setSessionEnabled((InputMethodSession)msg.obj,
msg.arg1 != 0);
return;
case DO_REVOKE_SESSION:
- mInputMethod.revokeSession((InputMethodSession)msg.obj);
+ inputMethod.revokeSession((InputMethodSession)msg.obj);
return;
case DO_SHOW_SOFT_INPUT:
- mInputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
+ inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj);
return;
case DO_HIDE_SOFT_INPUT:
- mInputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
+ inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj);
return;
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
- if (mTarget.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ AbstractInputMethodService target = mTarget.get();
+ if (target == null) {
+ return;
+ }
+ if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
fout.println("Permission Denial: can't dump InputMethodManager from from pid="
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c8d3e59b8b4..1a261d3fa6ba 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1988,15 +1988,19 @@ public class InputMethodService extends AbstractInputMethodService {
ei.inputType != InputType.TYPE_NULL);
if (hasAction) {
mExtractAccessories.setVisibility(View.VISIBLE);
- if (ei.actionLabel != null) {
- mExtractAction.setText(ei.actionLabel);
- } else {
- mExtractAction.setText(getTextForImeAction(ei.imeOptions));
+ if (mExtractAction != null) {
+ if (ei.actionLabel != null) {
+ mExtractAction.setText(ei.actionLabel);
+ } else {
+ mExtractAction.setText(getTextForImeAction(ei.imeOptions));
+ }
+ mExtractAction.setOnClickListener(mActionClickListener);
}
- mExtractAction.setOnClickListener(mActionClickListener);
} else {
mExtractAccessories.setVisibility(View.GONE);
- mExtractAction.setOnClickListener(null);
+ if (mExtractAction != null) {
+ mExtractAction.setOnClickListener(null);
+ }
}
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 280ded6bcc94..331ce10a64a2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -102,6 +102,14 @@ public class ConnectivityManager
* it with {@link android.content.Intent#getStringExtra(String)}.
*/
public static final String EXTRA_EXTRA_INFO = "extraInfo";
+ /**
+ * The lookup key for an int that provides information about
+ * our connection to the internet at large. 0 indicates no connection,
+ * 100 indicates a great connection. Retrieve it with
+ * {@link android.content.Intent@getIntExtra(String)}.
+ * {@hide}
+ */
+ public static final String EXTRA_INET_CONDITION = "inetCondition";
/**
* Broadcast Action: The setting for background data usage has changed
@@ -524,5 +532,17 @@ public class ConnectivityManager
} catch (RemoteException e) {
return TETHER_ERROR_SERVICE_UNAVAIL;
}
- }
+ }
+
+ /**
+ * @param networkType The type of network you want to report on
+ * @param percentage The quality of the connection 0 is bad, 100 is good
+ * {@hide}
+ */
+ public void reportInetCondition(int networkType, int percentage) {
+ try {
+ mService.reportInetCondition(networkType, percentage);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java
new file mode 100644
index 000000000000..e8237c9d0072
--- /dev/null
+++ b/core/java/android/net/DownloadManager.java
@@ -0,0 +1,901 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.provider.Downloads;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The download manager is a system service that handles long-running HTTP downloads. Clients may
+ * request that a URI be downloaded to a particular destination file. The download manager will
+ * conduct the download in the background, taking care of HTTP interactions and retrying downloads
+ * after failures or across connectivity changes and system reboots.
+ *
+ * Instances of this class should be obtained through
+ * {@link android.content.Context#getSystemService(String)} by passing
+ * {@link android.content.Context#DOWNLOAD_SERVICE}.
+ */
+public class DownloadManager {
+ /**
+ * An identifier for a particular download, unique across the system. Clients use this ID to
+ * make subsequent calls related to the download.
+ */
+ public final static String COLUMN_ID = BaseColumns._ID;
+
+ /**
+ * The client-supplied title for this download. This will be displayed in system notifications.
+ * Defaults to the empty string.
+ */
+ public final static String COLUMN_TITLE = "title";
+
+ /**
+ * The client-supplied description of this download. This will be displayed in system
+ * notifications. Defaults to the empty string.
+ */
+ public final static String COLUMN_DESCRIPTION = "description";
+
+ /**
+ * URI to be downloaded.
+ */
+ public final static String COLUMN_URI = "uri";
+
+ /**
+ * Internet Media Type of the downloaded file. If no value is provided upon creation, this will
+ * initially be null and will be filled in based on the server's response once the download has
+ * started.
+ *
+ * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a>
+ */
+ public final static String COLUMN_MEDIA_TYPE = "media_type";
+
+ /**
+ * Total size of the download in bytes. This will initially be -1 and will be filled in once
+ * the download starts.
+ */
+ public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
+
+ /**
+ * Uri where downloaded file will be stored. If a destination is supplied by client, that URI
+ * will be used here. Otherwise, the value will initially be null and will be filled in with a
+ * generated URI once the download has started.
+ */
+ public final static String COLUMN_LOCAL_URI = "local_uri";
+
+ /**
+ * Current status of the download, as one of the STATUS_* constants.
+ */
+ public final static String COLUMN_STATUS = "status";
+
+ /**
+ * Indicates the type of error that occurred, when {@link #COLUMN_STATUS} is
+ * {@link #STATUS_FAILED}. If an HTTP error occurred, this will hold the HTTP status code as
+ * defined in RFC 2616. Otherwise, it will hold one of the ERROR_* constants.
+ *
+ * If {@link #COLUMN_STATUS} is not {@link #STATUS_FAILED}, this column's value is undefined.
+ *
+ * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
+ * status codes</a>
+ */
+ public final static String COLUMN_ERROR_CODE = "error_code";
+
+ /**
+ * Number of bytes download so far.
+ */
+ public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
+
+ /**
+ * Timestamp when the download was last modified, in {@link System#currentTimeMillis
+ * System.currentTimeMillis()} (wall clock time in UTC).
+ */
+ public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
+
+
+ /**
+ * Value of {@link #COLUMN_STATUS} when the download is waiting to start.
+ */
+ public final static int STATUS_PENDING = 1 << 0;
+
+ /**
+ * Value of {@link #COLUMN_STATUS} when the download is currently running.
+ */
+ public final static int STATUS_RUNNING = 1 << 1;
+
+ /**
+ * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume.
+ */
+ public final static int STATUS_PAUSED = 1 << 2;
+
+ /**
+ * Value of {@link #COLUMN_STATUS} when the download has successfully completed.
+ */
+ public final static int STATUS_SUCCESSFUL = 1 << 3;
+
+ /**
+ * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried).
+ */
+ public final static int STATUS_FAILED = 1 << 4;
+
+
+ /**
+ * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit
+ * under any other error code.
+ */
+ public final static int ERROR_UNKNOWN = 1000;
+
+ /**
+ * Value of {@link #COLUMN_ERROR_CODE} when a storage issue arises which doesn't fit under any
+ * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
+ * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
+ */
+ public final static int ERROR_FILE_ERROR = 1001;
+
+ /**
+ * Value of {@link #COLUMN_ERROR_CODE} when an HTTP code was received that download manager
+ * can't handle.
+ */
+ public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
+
+ /**
+ * Value of {@link #COLUMN_ERROR_CODE} when an error receiving or processing data occurred at
+ * the HTTP level.
+ */
+ public final static int ERROR_HTTP_DATA_ERROR = 1004;
+
+ /**
+ * Value of {@link #COLUMN_ERROR_CODE} when there were too many redirects.
+ */
+ public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
+
+ /**
+ * Value of {@link #COLUMN_ERROR_CODE} when there was insufficient storage space. Typically,
+ * this is because the SD card is full.
+ */
+ public final static int ERROR_INSUFFICIENT_SPACE = 1006;
+
+ /**
+ * Value of {@link #COLUMN_ERROR_CODE} when no external storage device was found. Typically,
+ * this is because the SD card is not mounted.
+ */
+ public final static int ERROR_DEVICE_NOT_FOUND = 1007;
+
+ /**
+ * Value of {@link #COLUMN_ERROR_CODE} when some possibly transient error occurred but we can't
+ * resume the download.
+ */
+ public final static int ERROR_CANNOT_RESUME = 1008;
+
+ /**
+ * Broadcast intent action sent by the download manager when a download completes.
+ */
+ public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
+
+ /**
+ * Broadcast intent action sent by the download manager when a running download notification is
+ * clicked.
+ */
+ public final static String ACTION_NOTIFICATION_CLICKED =
+ "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
+
+ /**
+ * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a
+ * long) of the download that just completed.
+ */
+ public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
+
+ // this array must contain all public columns
+ private static final String[] COLUMNS = new String[] {
+ COLUMN_ID,
+ COLUMN_TITLE,
+ COLUMN_DESCRIPTION,
+ COLUMN_URI,
+ COLUMN_MEDIA_TYPE,
+ COLUMN_TOTAL_SIZE_BYTES,
+ COLUMN_LOCAL_URI,
+ COLUMN_STATUS,
+ COLUMN_ERROR_CODE,
+ COLUMN_BYTES_DOWNLOADED_SO_FAR,
+ COLUMN_LAST_MODIFIED_TIMESTAMP
+ };
+
+ // columns to request from DownloadProvider
+ private static final String[] UNDERLYING_COLUMNS = new String[] {
+ Downloads.Impl._ID,
+ Downloads.COLUMN_TITLE,
+ Downloads.COLUMN_DESCRIPTION,
+ Downloads.COLUMN_URI,
+ Downloads.COLUMN_MIME_TYPE,
+ Downloads.COLUMN_TOTAL_BYTES,
+ Downloads._DATA,
+ Downloads.COLUMN_STATUS,
+ Downloads.COLUMN_CURRENT_BYTES,
+ Downloads.COLUMN_LAST_MODIFICATION,
+ };
+
+ private static final Set<String> LONG_COLUMNS = new HashSet<String>(
+ Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_ERROR_CODE,
+ COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP));
+
+ /**
+ * This class contains all the information necessary to request a new download. The URI is the
+ * only required parameter.
+ */
+ public static class Request {
+ /**
+ * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
+ * {@link ConnectivityManager#TYPE_MOBILE}.
+ */
+ public static final int NETWORK_MOBILE = 1 << 0;
+
+ /**
+ * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
+ * {@link ConnectivityManager#TYPE_WIFI}.
+ */
+ public static final int NETWORK_WIFI = 1 << 1;
+
+ /**
+ * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
+ * {@link ConnectivityManager#TYPE_WIMAX}.
+ */
+ public static final int NETWORK_WIMAX = 1 << 2;
+
+ private Uri mUri;
+ private Uri mDestinationUri;
+ private Map<String, String> mRequestHeaders = new HashMap<String, String>();
+ private String mTitle;
+ private String mDescription;
+ private boolean mShowNotification = true;
+ private String mMediaType;
+ private boolean mRoamingAllowed = true;
+ private int mAllowedNetworkTypes = ~0; // default to all network types allowed
+ private boolean mIsVisibleInDownloadsUi = true;
+
+ /**
+ * @param uri the HTTP URI to download.
+ */
+ public Request(Uri uri) {
+ if (uri == null) {
+ throw new NullPointerException();
+ }
+ String scheme = uri.getScheme();
+ if (scheme == null || !scheme.equals("http")) {
+ throw new IllegalArgumentException("Can only download HTTP URIs: " + uri);
+ }
+ mUri = uri;
+ }
+
+ /**
+ * Set the local destination for the downloaded data. Must be a file URI to a path on
+ * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE
+ * permission.
+ *
+ * By default, downloads are saved to a generated file in the download cache and may be
+ * deleted by the download manager at any time.
+ *
+ * @return this object
+ */
+ public Request setDestinationUri(Uri uri) {
+ mDestinationUri = uri;
+ return this;
+ }
+
+ /**
+ * Set an HTTP header to be included with the download request.
+ * @param header HTTP header name
+ * @param value header value
+ * @return this object
+ */
+ public Request setRequestHeader(String header, String value) {
+ mRequestHeaders.put(header, value);
+ return this;
+ }
+
+ /**
+ * Set the title of this download, to be displayed in notifications (if enabled)
+ * @return this object
+ */
+ public Request setTitle(String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Set a description of this download, to be displayed in notifications (if enabled)
+ * @return this object
+ */
+ public Request setDescription(String description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Set the Internet Media Type of this download. This will override the media type declared
+ * in the server's response.
+ * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a>
+ * @return this object
+ */
+ public Request setMediaType(String mediaType) {
+ mMediaType = mediaType;
+ return this;
+ }
+
+ /**
+ * Control whether a system notification is posted by the download manager while this
+ * download is running. If enabled, the download manager posts notifications about downloads
+ * through the system {@link android.app.NotificationManager}. By default, a notification is
+ * shown.
+ *
+ * If set to false, this requires the permission
+ * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
+ *
+ * @param show whether the download manager should show a notification for this download.
+ * @return this object
+ * @hide
+ */
+ public Request setShowRunningNotification(boolean show) {
+ mShowNotification = show;
+ return this;
+ }
+
+ /**
+ * Restrict the types of networks over which this download may proceed. By default, all
+ * network types are allowed.
+ * @param flags any combination of the NETWORK_* bit flags.
+ * @return this object
+ */
+ public Request setAllowedNetworkTypes(int flags) {
+ mAllowedNetworkTypes = flags;
+ return this;
+ }
+
+ /**
+ * Set whether this download may proceed over a roaming connection. By default, roaming is
+ * allowed.
+ * @param allowed whether to allow a roaming connection to be used
+ * @return this object
+ */
+ public Request setAllowedOverRoaming(boolean allowed) {
+ mRoamingAllowed = allowed;
+ return this;
+ }
+
+ /**
+ * Set whether this download should be displayed in the system's Downloads UI. True by
+ * default.
+ * @param isVisible whether to display this download in the Downloads UI
+ * @return this object
+ */
+ public Request setVisibleInDownloadsUi(boolean isVisible) {
+ mIsVisibleInDownloadsUi = isVisible;
+ return this;
+ }
+
+ /**
+ * @return ContentValues to be passed to DownloadProvider.insert()
+ */
+ ContentValues toContentValues(String packageName) {
+ ContentValues values = new ContentValues();
+ assert mUri != null;
+ values.put(Downloads.COLUMN_URI, mUri.toString());
+ values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
+ values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE, packageName);
+
+ if (mDestinationUri != null) {
+ values.put(Downloads.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
+ values.put(Downloads.COLUMN_FILE_NAME_HINT, mDestinationUri.toString());
+ } else {
+ values.put(Downloads.COLUMN_DESTINATION,
+ Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE);
+ }
+
+ if (!mRequestHeaders.isEmpty()) {
+ encodeHttpHeaders(values);
+ }
+
+ putIfNonNull(values, Downloads.COLUMN_TITLE, mTitle);
+ putIfNonNull(values, Downloads.COLUMN_DESCRIPTION, mDescription);
+ putIfNonNull(values, Downloads.COLUMN_MIME_TYPE, mMediaType);
+
+ values.put(Downloads.COLUMN_VISIBILITY,
+ mShowNotification ? Downloads.VISIBILITY_VISIBLE
+ : Downloads.VISIBILITY_HIDDEN);
+
+ values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
+ values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
+ values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
+
+ return values;
+ }
+
+ private void encodeHttpHeaders(ContentValues values) {
+ int index = 0;
+ for (Map.Entry<String, String> entry : mRequestHeaders.entrySet()) {
+ String headerString = entry.getKey() + ": " + entry.getValue();
+ values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString);
+ index++;
+ }
+ }
+
+ private void putIfNonNull(ContentValues contentValues, String key, String value) {
+ if (value != null) {
+ contentValues.put(key, value);
+ }
+ }
+ }
+
+ /**
+ * This class may be used to filter download manager queries.
+ */
+ public static class Query {
+ /**
+ * Constant for use with {@link #orderBy}
+ * @hide
+ */
+ public static final int ORDER_ASCENDING = 1;
+
+ /**
+ * Constant for use with {@link #orderBy}
+ * @hide
+ */
+ public static final int ORDER_DESCENDING = 2;
+
+ private Long mId = null;
+ private Integer mStatusFlags = null;
+ private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
+ private int mOrderDirection = ORDER_DESCENDING;
+ private boolean mOnlyIncludeVisibleInDownloadsUi = false;
+
+ /**
+ * Include only the download with the given ID.
+ * @return this object
+ */
+ public Query setFilterById(long id) {
+ mId = id;
+ return this;
+ }
+
+ /**
+ * Include only downloads with status matching any the given status flags.
+ * @param flags any combination of the STATUS_* bit flags
+ * @return this object
+ */
+ public Query setFilterByStatus(int flags) {
+ mStatusFlags = flags;
+ return this;
+ }
+
+ /**
+ * Controls whether this query includes downloads not visible in the system's Downloads UI.
+ * @param value if true, this query will only include downloads that should be displayed in
+ * the system's Downloads UI; if false (the default), this query will include
+ * both visible and invisible downloads.
+ * @return this object
+ * @hide
+ */
+ public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
+ mOnlyIncludeVisibleInDownloadsUi = value;
+ return this;
+ }
+
+ /**
+ * Change the sort order of the returned Cursor.
+ *
+ * @param column one of the COLUMN_* constants; currently, only
+ * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
+ * supported.
+ * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
+ * @return this object
+ * @hide
+ */
+ public Query orderBy(String column, int direction) {
+ if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
+ throw new IllegalArgumentException("Invalid direction: " + direction);
+ }
+
+ if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
+ mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION;
+ } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
+ mOrderByColumn = Downloads.COLUMN_TOTAL_BYTES;
+ } else {
+ throw new IllegalArgumentException("Cannot order by " + column);
+ }
+ mOrderDirection = direction;
+ return this;
+ }
+
+ /**
+ * Run this query using the given ContentResolver.
+ * @param projection the projection to pass to ContentResolver.query()
+ * @return the Cursor returned by ContentResolver.query()
+ */
+ Cursor runQuery(ContentResolver resolver, String[] projection) {
+ Uri uri = Downloads.CONTENT_URI;
+ List<String> selectionParts = new ArrayList<String>();
+
+ if (mId != null) {
+ uri = Uri.withAppendedPath(uri, mId.toString());
+ }
+
+ if (mStatusFlags != null) {
+ List<String> parts = new ArrayList<String>();
+ if ((mStatusFlags & STATUS_PENDING) != 0) {
+ parts.add(statusClause("=", Downloads.STATUS_PENDING));
+ }
+ if ((mStatusFlags & STATUS_RUNNING) != 0) {
+ parts.add(statusClause("=", Downloads.STATUS_RUNNING));
+ }
+ if ((mStatusFlags & STATUS_PAUSED) != 0) {
+ parts.add(statusClause("=", Downloads.STATUS_PENDING_PAUSED));
+ parts.add(statusClause("=", Downloads.STATUS_RUNNING_PAUSED));
+ }
+ if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
+ parts.add(statusClause("=", Downloads.STATUS_SUCCESS));
+ }
+ if ((mStatusFlags & STATUS_FAILED) != 0) {
+ parts.add("(" + statusClause(">=", 400)
+ + " AND " + statusClause("<", 600) + ")");
+ }
+ selectionParts.add(joinStrings(" OR ", parts));
+ }
+
+ if (mOnlyIncludeVisibleInDownloadsUi) {
+ selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
+ }
+
+ String selection = joinStrings(" AND ", selectionParts);
+ String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
+ String orderBy = mOrderByColumn + " " + orderDirection;
+
+ return resolver.query(uri, projection, selection, null, orderBy);
+ }
+
+ private String joinStrings(String joiner, Iterable<String> parts) {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String part : parts) {
+ if (!first) {
+ builder.append(joiner);
+ }
+ builder.append(part);
+ first = false;
+ }
+ return builder.toString();
+ }
+
+ private String statusClause(String operator, int value) {
+ return Downloads.COLUMN_STATUS + operator + "'" + value + "'";
+ }
+ }
+
+ private ContentResolver mResolver;
+ private String mPackageName;
+
+ /**
+ * @hide
+ */
+ public DownloadManager(ContentResolver resolver, String packageName) {
+ mResolver = resolver;
+ mPackageName = packageName;
+ }
+
+ /**
+ * Enqueue a new download. The download will start automatically once the download manager is
+ * ready to execute it and connectivity is available.
+ *
+ * @param request the parameters specifying this download
+ * @return an ID for the download, unique across the system. This ID is used to make future
+ * calls related to this download.
+ */
+ public long enqueue(Request request) {
+ ContentValues values = request.toContentValues(mPackageName);
+ Uri downloadUri = mResolver.insert(Downloads.CONTENT_URI, values);
+ long id = Long.parseLong(downloadUri.getLastPathSegment());
+ return id;
+ }
+
+ /**
+ * Cancel a download and remove it from the download manager. The download will be stopped if
+ * it was running, and it will no longer be accessible through the download manager. If a file
+ * was already downloaded, it will not be deleted.
+ *
+ * @param id the ID of the download
+ */
+ public void remove(long id) {
+ int numDeleted = mResolver.delete(getDownloadUri(id), null, null);
+ if (numDeleted == 0) {
+ throw new IllegalArgumentException("Download " + id + " does not exist");
+ }
+ }
+
+ /**
+ * Query the download manager about downloads that have been requested.
+ * @param query parameters specifying filters for this query
+ * @return a Cursor over the result set of downloads, with columns consisting of all the
+ * COLUMN_* constants.
+ */
+ public Cursor query(Query query) {
+ Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS);
+ if (underlyingCursor == null) {
+ return null;
+ }
+ return new CursorTranslator(underlyingCursor);
+ }
+
+ /**
+ * Open a downloaded file for reading. The download must have completed.
+ * @param id the ID of the download
+ * @return a read-only {@link ParcelFileDescriptor}
+ * @throws FileNotFoundException if the destination file does not already exist
+ */
+ public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
+ return mResolver.openFileDescriptor(getDownloadUri(id), "r");
+ }
+
+ /**
+ * Restart the given download, which must have already completed (successfully or not). This
+ * method will only work when called from within the download manager's process.
+ * @param id the ID of the download
+ * @hide
+ */
+ public void restartDownload(long id) {
+ Cursor cursor = query(new Query().setFilterById(id));
+ try {
+ if (!cursor.moveToFirst()) {
+ throw new IllegalArgumentException("No download with id " + id);
+ }
+ int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
+ if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
+ throw new IllegalArgumentException("Cannot restart incomplete download: " + id);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ ContentValues values = new ContentValues();
+ values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
+ values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
+ values.putNull(Downloads.Impl._DATA);
+ values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
+ mResolver.update(getDownloadUri(id), values, null, null);
+ }
+
+ /**
+ * Get the DownloadProvider URI for the download with the given ID.
+ */
+ private Uri getDownloadUri(long id) {
+ Uri downloadUri = Uri.withAppendedPath(Downloads.CONTENT_URI, Long.toString(id));
+ return downloadUri;
+ }
+
+ /**
+ * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
+ * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
+ * Some columns correspond directly to underlying values while others are computed from
+ * underlying data.
+ */
+ private static class CursorTranslator extends CursorWrapper {
+ public CursorTranslator(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ return Arrays.asList(COLUMNS).indexOf(columnName);
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
+ int index = getColumnIndex(columnName);
+ if (index == -1) {
+ throw new IllegalArgumentException("No such column: " + columnName);
+ }
+ return index;
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ int numColumns = COLUMNS.length;
+ if (columnIndex < 0 || columnIndex >= numColumns) {
+ throw new IllegalArgumentException("Invalid column index " + columnIndex + ", "
+ + numColumns + " columns exist");
+ }
+ return COLUMNS[columnIndex];
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ String[] returnColumns = new String[COLUMNS.length];
+ System.arraycopy(COLUMNS, 0, returnColumns, 0, COLUMNS.length);
+ return returnColumns;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return COLUMNS.length;
+ }
+
+ @Override
+ public byte[] getBlob(int columnIndex) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public double getDouble(int columnIndex) {
+ return getLong(columnIndex);
+ }
+
+ private boolean isLongColumn(String column) {
+ return LONG_COLUMNS.contains(column);
+ }
+
+ @Override
+ public float getFloat(int columnIndex) {
+ return (float) getDouble(columnIndex);
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ return (int) getLong(columnIndex);
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ return translateLong(getColumnName(columnIndex));
+ }
+
+ @Override
+ public short getShort(int columnIndex) {
+ return (short) getLong(columnIndex);
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ return translateString(getColumnName(columnIndex));
+ }
+
+ private String translateString(String column) {
+ if (isLongColumn(column)) {
+ return Long.toString(translateLong(column));
+ }
+ if (column.equals(COLUMN_TITLE)) {
+ return getUnderlyingString(Downloads.COLUMN_TITLE);
+ }
+ if (column.equals(COLUMN_DESCRIPTION)) {
+ return getUnderlyingString(Downloads.COLUMN_DESCRIPTION);
+ }
+ if (column.equals(COLUMN_URI)) {
+ return getUnderlyingString(Downloads.COLUMN_URI);
+ }
+ if (column.equals(COLUMN_MEDIA_TYPE)) {
+ return getUnderlyingString(Downloads.COLUMN_MIME_TYPE);
+ }
+
+ assert column.equals(COLUMN_LOCAL_URI);
+ String localUri = getUnderlyingString(Downloads._DATA);
+ if (localUri == null) {
+ return null;
+ }
+ return Uri.fromFile(new File(localUri)).toString();
+ }
+
+ private long translateLong(String column) {
+ if (!isLongColumn(column)) {
+ // mimic behavior of underlying cursor -- most likely, throw NumberFormatException
+ return Long.valueOf(translateString(column));
+ }
+
+ if (column.equals(COLUMN_ID)) {
+ return getUnderlyingLong(Downloads.Impl._ID);
+ }
+ if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
+ return getUnderlyingLong(Downloads.COLUMN_TOTAL_BYTES);
+ }
+ if (column.equals(COLUMN_STATUS)) {
+ return translateStatus((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
+ }
+ if (column.equals(COLUMN_ERROR_CODE)) {
+ return translateErrorCode((int) getUnderlyingLong(Downloads.COLUMN_STATUS));
+ }
+ if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) {
+ return getUnderlyingLong(Downloads.COLUMN_CURRENT_BYTES);
+ }
+ assert column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP);
+ return getUnderlyingLong(Downloads.COLUMN_LAST_MODIFICATION);
+ }
+
+ private long translateErrorCode(int status) {
+ if (translateStatus(status) != STATUS_FAILED) {
+ return 0; // arbitrary value when status is not an error
+ }
+ if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
+ || (500 <= status && status < 600)) {
+ // HTTP status code
+ return status;
+ }
+
+ switch (status) {
+ case Downloads.STATUS_FILE_ERROR:
+ return ERROR_FILE_ERROR;
+
+ case Downloads.STATUS_UNHANDLED_HTTP_CODE:
+ case Downloads.STATUS_UNHANDLED_REDIRECT:
+ return ERROR_UNHANDLED_HTTP_CODE;
+
+ case Downloads.STATUS_HTTP_DATA_ERROR:
+ return ERROR_HTTP_DATA_ERROR;
+
+ case Downloads.STATUS_TOO_MANY_REDIRECTS:
+ return ERROR_TOO_MANY_REDIRECTS;
+
+ case Downloads.STATUS_INSUFFICIENT_SPACE_ERROR:
+ return ERROR_INSUFFICIENT_SPACE;
+
+ case Downloads.STATUS_DEVICE_NOT_FOUND_ERROR:
+ return ERROR_DEVICE_NOT_FOUND;
+
+ case Downloads.Impl.STATUS_CANNOT_RESUME:
+ return ERROR_CANNOT_RESUME;
+
+ default:
+ return ERROR_UNKNOWN;
+ }
+ }
+
+ private long getUnderlyingLong(String column) {
+ return super.getLong(super.getColumnIndex(column));
+ }
+
+ private String getUnderlyingString(String column) {
+ return super.getString(super.getColumnIndex(column));
+ }
+
+ private long translateStatus(int status) {
+ switch (status) {
+ case Downloads.STATUS_PENDING:
+ return STATUS_PENDING;
+
+ case Downloads.STATUS_RUNNING:
+ return STATUS_RUNNING;
+
+ case Downloads.STATUS_PENDING_PAUSED:
+ case Downloads.STATUS_RUNNING_PAUSED:
+ return STATUS_PAUSED;
+
+ case Downloads.STATUS_SUCCESS:
+ return STATUS_SUCCESSFUL;
+
+ default:
+ assert Downloads.isStatusError(status);
+ return STATUS_FAILED;
+ }
+ }
+ }
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index b05c2edd103b..b734ac7ebb9c 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -72,4 +72,6 @@ interface IConnectivityManager
String[] getTetherableUsbRegexs();
String[] getTetherableWifiRegexs();
+
+ void reportInetCondition(int networkType, int percentage);
}
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 98f32b36e99e..eb7117bdee5d 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -53,6 +53,10 @@ public class MobileDataStateTracker extends NetworkStateTracker {
private boolean mEnabled;
private BroadcastReceiver mStateReceiver;
+ // DEFAULT and HIPRI are the same connection. If we're one of these we need to check if
+ // the other is also disconnected before we reset sockets
+ private boolean mIsDefaultOrHipri = false;
+
/**
* Create a new MobileDataStateTracker
* @param context the application context of the caller
@@ -71,6 +75,10 @@ public class MobileDataStateTracker extends NetworkStateTracker {
} else {
mApnTypeToWatchFor = mApnType;
}
+ if (netType == ConnectivityManager.TYPE_MOBILE ||
+ netType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
+ mIsDefaultOrHipri = true;
+ }
mPhoneService = null;
if(netType == ConnectivityManager.TYPE_MOBILE) {
@@ -138,6 +146,7 @@ public class MobileDataStateTracker extends NetworkStateTracker {
}
private class MobileDataStateReceiver extends BroadcastReceiver {
+ ConnectivityManager mConnectivityManager;
public void onReceive(Context context, Intent intent) {
synchronized(this) {
if (intent.getAction().equals(TelephonyIntents.
@@ -190,7 +199,26 @@ public class MobileDataStateTracker extends NetworkStateTracker {
}
setDetailedState(DetailedState.DISCONNECTED, reason, apnName);
- if (mInterfaceName != null) {
+ boolean doReset = true;
+ if (mIsDefaultOrHipri == true) {
+ // both default and hipri must go down before we reset
+ int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ?
+ ConnectivityManager.TYPE_MOBILE_HIPRI :
+ ConnectivityManager.TYPE_MOBILE);
+ if (mConnectivityManager == null) {
+ mConnectivityManager =
+ (ConnectivityManager)context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ }
+ if (mConnectivityManager != null) {
+ NetworkInfo info = mConnectivityManager.getNetworkInfo(
+ typeToCheck);
+ if (info != null && info.isConnected() == true) {
+ doReset = false;
+ }
+ }
+ }
+ if (doReset && mInterfaceName != null) {
NetworkUtils.resetConnections(mInterfaceName);
}
// can't do this here - ConnectivityService needs it to clear stuff
@@ -212,6 +240,7 @@ public class MobileDataStateTracker extends NetworkStateTracker {
if (mInterfaceName == null) {
Log.d(TAG, "CONNECTED event did not supply interface name.");
}
+ mDefaultGatewayAddr = intent.getIntExtra(Phone.DATA_GATEWAY_KEY, 0);
setDetailedState(DetailedState.CONNECTED, reason, apnName);
break;
}
@@ -310,6 +339,9 @@ public class MobileDataStateTracker extends NetworkStateTracker {
case TelephonyManager.NETWORK_TYPE_EVDO_A:
networkTypeStr = "evdo";
break;
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ networkTypeStr = "evdo";
+ break;
}
return "net.tcp.buffersize." + networkTypeStr;
}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 1fb014452f4c..c5a327744bb8 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -45,7 +45,6 @@ public abstract class NetworkStateTracker extends Handler {
protected String[] mDnsPropNames;
private boolean mPrivateDnsRouteSet;
protected int mDefaultGatewayAddr;
- private boolean mDefaultRouteSet;
private boolean mTeardownRequested;
private static boolean DBG = true;
@@ -63,6 +62,15 @@ public abstract class NetworkStateTracker extends Handler {
public static final int EVENT_ROAMING_CHANGED = 5;
public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6;
public static final int EVENT_RESTORE_DEFAULT_NETWORK = 7;
+ /**
+ * arg1: network type
+ * arg2: condition (0 bad, 100 good)
+ */
+ public static final int EVENT_INET_CONDITION_CHANGE = 8;
+ /**
+ * arg1: network type
+ */
+ public static final int EVENT_INET_CONDITION_HOLD_END = 9;
public NetworkStateTracker(Context context,
Handler target,
@@ -153,25 +161,22 @@ public abstract class NetworkStateTracker extends Handler {
}
public void addDefaultRoute() {
- if ((mInterfaceName != null) && (mDefaultGatewayAddr != 0) &&
- mDefaultRouteSet == false) {
+ if ((mInterfaceName != null) && (mDefaultGatewayAddr != 0)) {
if (DBG) {
Log.d(TAG, "addDefaultRoute for " + mNetworkInfo.getTypeName() +
" (" + mInterfaceName + "), GatewayAddr=" + mDefaultGatewayAddr);
}
NetworkUtils.setDefaultRoute(mInterfaceName, mDefaultGatewayAddr);
- mDefaultRouteSet = true;
}
}
public void removeDefaultRoute() {
- if (mInterfaceName != null && mDefaultRouteSet == true) {
+ if (mInterfaceName != null) {
if (DBG) {
Log.d(TAG, "removeDefaultRoute for " + mNetworkInfo.getTypeName() + " (" +
mInterfaceName + ")");
}
NetworkUtils.removeDefaultRoute(mInterfaceName);
- mDefaultRouteSet = false;
}
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index a3ae01b5597b..e4f3d5c0f104 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -128,4 +128,19 @@ public class NetworkUtils {
| (addrBytes[0] & 0xff);
return addr;
}
+
+ public static int v4StringToInt(String str) {
+ int result = 0;
+ String[] array = str.split("\\.");
+ if (array.length != 4) return 0;
+ try {
+ result = Integer.parseInt(array[3]);
+ result = (result << 8) + Integer.parseInt(array[2]);
+ result = (result << 8) + Integer.parseInt(array[1]);
+ result = (result << 8) + Integer.parseInt(array[0]);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ return result;
+ }
}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 31acb5b177e8..9166019fc9cd 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -46,9 +46,9 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
import org.apache.harmony.xnet.provider.jsse.SSLParameters;
/**
@@ -210,7 +210,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
private SSLSocketFactory makeSocketFactory(TrustManager[] trustManagers) {
try {
- SSLContextImpl sslContext = new SSLContextImpl();
+ OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
sslContext.engineInit(null, trustManagers, null, mSessionCache, null);
return sslContext.engineGetSocketFactory();
} catch (KeyManagementException e) {
diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java
index e512a1dfc595..8c9d013f3955 100644
--- a/core/java/android/net/http/HttpsConnection.java
+++ b/core/java/android/net/http/HttpsConnection.java
@@ -19,8 +19,8 @@ package android.net.http;
import android.content.Context;
import android.util.Log;
import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
+import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
-import org.apache.harmony.xnet.provider.jsse.SSLContextImpl;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
@@ -79,7 +79,7 @@ public class HttpsConnection extends Connection {
cache = FileClientSessionCache.usingDirectory(sessionDir);
}
- SSLContextImpl sslContext = new SSLContextImpl();
+ OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
// here, trust managers is a single trust-all manager
TrustManager[] trustManagers = new TrustManager[] {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d114bffe813d..f182a7ad6d71 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -23,6 +23,7 @@ import java.util.Map;
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
+import android.util.TimeUtils;
/**
* A class providing access to battery usage statistics, including information on
@@ -51,57 +52,43 @@ public abstract class BatteryStats implements Parcelable {
/**
* A constant indicating a sensor timer.
- *
- * {@hide}
*/
public static final int SENSOR = 3;
/**
* A constant indicating a a wifi turn on timer
- *
- * {@hide}
*/
public static final int WIFI_TURNED_ON = 4;
/**
* A constant indicating a full wifi lock timer
- *
- * {@hide}
*/
public static final int FULL_WIFI_LOCK = 5;
/**
* A constant indicating a scan wifi lock timer
- *
- * {@hide}
*/
public static final int SCAN_WIFI_LOCK = 6;
/**
* A constant indicating a wifi multicast timer
- *
- * {@hide}
*/
public static final int WIFI_MULTICAST_ENABLED = 7;
/**
* A constant indicating an audio turn on timer
- *
- * {@hide}
*/
public static final int AUDIO_TURNED_ON = 7;
/**
* A constant indicating a video turn on timer
- *
- * {@hide}
*/
public static final int VIDEO_TURNED_ON = 8;
/**
* Include all of the data in the stats, including previously saved data.
*/
- public static final int STATS_TOTAL = 0;
+ public static final int STATS_SINCE_CHARGED = 0;
/**
* Include only the last run in the stats.
@@ -116,11 +103,11 @@ public abstract class BatteryStats implements Parcelable {
/**
* Include only the run since the last time the device was unplugged in the stats.
*/
- public static final int STATS_UNPLUGGED = 3;
+ public static final int STATS_SINCE_UNPLUGGED = 3;
// NOTE: Update this list if you add/change any stats above.
// These characters are supposed to represent "total", "last", "current",
- // and "unplugged". They were shortened for effeciency sake.
+ // and "unplugged". They were shortened for efficiency sake.
private static final String[] STAT_NAMES = { "t", "l", "c", "u" };
/**
@@ -229,6 +216,11 @@ public abstract class BatteryStats implements Parcelable {
public abstract Map<Integer, ? extends Sensor> getSensorStats();
/**
+ * Returns a mapping containing active process data.
+ */
+ public abstract SparseArray<? extends Pid> getPidStats();
+
+ /**
* Returns a mapping containing process statistics.
*
* @return a Map from Strings to Uid.Proc objects.
@@ -299,11 +291,21 @@ public abstract class BatteryStats implements Parcelable {
public abstract Timer getSensorTime();
}
+ public class Pid {
+ public long mWakeSum;
+ public long mWakeStart;
+ }
+
/**
* The statistics associated with a particular process.
*/
public static abstract class Proc {
+ public static class ExcessiveWake {
+ public long overTime;
+ public long usedTime;
+ }
+
/**
* Returns the total time (in 1/100 sec) spent executing in user code.
*
@@ -340,6 +342,10 @@ public abstract class BatteryStats implements Parcelable {
* @see BatteryStats#getCpuSpeedSteps()
*/
public abstract long getTimeAtCpuSpeedStep(int speedStep, int which);
+
+ public abstract int countExcessiveWakes();
+
+ public abstract ExcessiveWake getExcessiveWake(int i);
}
/**
@@ -391,6 +397,145 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ public final class HistoryItem implements Parcelable {
+ public HistoryItem next;
+
+ public long time;
+
+ public static final byte CMD_UPDATE = 0;
+ public static final byte CMD_START = 1;
+ public static final byte CMD_OVERFLOW = 2;
+
+ public byte cmd;
+
+ public byte batteryLevel;
+ public byte batteryStatus;
+ public byte batteryHealth;
+ public byte batteryPlugType;
+
+ public char batteryTemperature;
+ public char batteryVoltage;
+
+ // Constants from SCREEN_BRIGHTNESS_*
+ public static final int STATE_BRIGHTNESS_MASK = 0x000000f;
+ public static final int STATE_BRIGHTNESS_SHIFT = 0;
+ // Constants from SIGNAL_STRENGTH_*
+ public static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0;
+ public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4;
+ // Constants from ServiceState.STATE_*
+ public static final int STATE_PHONE_STATE_MASK = 0x0000f00;
+ public static final int STATE_PHONE_STATE_SHIFT = 8;
+ // Constants from DATA_CONNECTION_*
+ public static final int STATE_DATA_CONNECTION_MASK = 0x000f000;
+ public static final int STATE_DATA_CONNECTION_SHIFT = 12;
+
+ public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30;
+ public static final int STATE_SCREEN_ON_FLAG = 1<<29;
+ public static final int STATE_GPS_ON_FLAG = 1<<28;
+ public static final int STATE_PHONE_IN_CALL_FLAG = 1<<27;
+ public static final int STATE_PHONE_SCANNING_FLAG = 1<<26;
+ public static final int STATE_WIFI_ON_FLAG = 1<<25;
+ public static final int STATE_WIFI_RUNNING_FLAG = 1<<24;
+ public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<23;
+ public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<22;
+ public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<21;
+ public static final int STATE_BLUETOOTH_ON_FLAG = 1<<20;
+ public static final int STATE_AUDIO_ON_FLAG = 1<<19;
+ public static final int STATE_VIDEO_ON_FLAG = 1<<18;
+ public static final int STATE_WAKE_LOCK_FLAG = 1<<17;
+ public static final int STATE_SENSOR_ON_FLAG = 1<<16;
+
+ public int states;
+
+ public HistoryItem() {
+ }
+
+ public HistoryItem(long time, Parcel src) {
+ this.time = time;
+ int bat = src.readInt();
+ cmd = (byte)(bat&0xff);
+ batteryLevel = (byte)((bat>>8)&0xff);
+ batteryStatus = (byte)((bat>>16)&0xf);
+ batteryHealth = (byte)((bat>>20)&0xf);
+ batteryPlugType = (byte)((bat>>24)&0xf);
+ bat = src.readInt();
+ batteryTemperature = (char)(bat&0xffff);
+ batteryVoltage = (char)((bat>>16)&0xffff);
+ states = src.readInt();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(time);
+ int bat = (((int)cmd)&0xff)
+ | ((((int)batteryLevel)<<8)&0xff00)
+ | ((((int)batteryStatus)<<16)&0xf0000)
+ | ((((int)batteryHealth)<<20)&0xf00000)
+ | ((((int)batteryPlugType)<<24)&0xf000000);
+ dest.writeInt(bat);
+ bat = (((int)batteryTemperature)&0xffff)
+ | ((((int)batteryVoltage)<<16)&0xffff0000);
+ dest.writeInt(bat);
+ dest.writeInt(states);
+ }
+
+ public void setTo(long time, byte cmd, HistoryItem o) {
+ this.time = time;
+ this.cmd = cmd;
+ batteryLevel = o.batteryLevel;
+ batteryStatus = o.batteryStatus;
+ batteryHealth = o.batteryHealth;
+ batteryPlugType = o.batteryPlugType;
+ batteryTemperature = o.batteryTemperature;
+ batteryVoltage = o.batteryVoltage;
+ states = o.states;
+ }
+
+ public boolean same(HistoryItem o) {
+ return batteryLevel == o.batteryLevel
+ && batteryStatus == o.batteryStatus
+ && batteryHealth == o.batteryHealth
+ && batteryPlugType == o.batteryPlugType
+ && batteryTemperature == o.batteryTemperature
+ && batteryVoltage == o.batteryVoltage
+ && states == o.states;
+ }
+ }
+
+ public static final class BitDescription {
+ public final int mask;
+ public final int shift;
+ public final String name;
+ public final String[] values;
+
+ public BitDescription(int mask, String name) {
+ this.mask = mask;
+ this.shift = -1;
+ this.name = name;
+ this.values = null;
+ }
+
+ public BitDescription(int mask, int shift, String name, String[] values) {
+ this.mask = mask;
+ this.shift = shift;
+ this.name = name;
+ this.values = values;
+ }
+ }
+
+ /**
+ * Return the current history of battery state changes.
+ */
+ public abstract HistoryItem getHistory();
+
+ /**
+ * Return the base time offset for the battery history.
+ */
+ public abstract long getHistoryBaseTime();
+
/**
* Returns the number of times the device has been started.
*/
@@ -476,13 +621,23 @@ public abstract class BatteryStats implements Parcelable {
public static final int DATA_CONNECTION_GPRS = 1;
public static final int DATA_CONNECTION_EDGE = 2;
public static final int DATA_CONNECTION_UMTS = 3;
- public static final int DATA_CONNECTION_OTHER = 4;
+ public static final int DATA_CONNECTION_CDMA = 4;
+ public static final int DATA_CONNECTION_EVDO_0 = 5;
+ public static final int DATA_CONNECTION_EVDO_A = 6;
+ public static final int DATA_CONNECTION_1xRTT = 7;
+ public static final int DATA_CONNECTION_HSDPA = 8;
+ public static final int DATA_CONNECTION_HSUPA = 9;
+ public static final int DATA_CONNECTION_HSPA = 10;
+ public static final int DATA_CONNECTION_IDEN = 11;
+ public static final int DATA_CONNECTION_EVDO_B = 12;
+ public static final int DATA_CONNECTION_OTHER = 13;
static final String[] DATA_CONNECTION_NAMES = {
- "none", "gprs", "edge", "umts", "other"
+ "none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A",
+ "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "other"
};
- public static final int NUM_DATA_CONNECTION_TYPES = 5;
+ public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1;
/**
* Returns the time in microseconds that the phone has been running with
@@ -500,6 +655,37 @@ public abstract class BatteryStats implements Parcelable {
* {@hide}
*/
public abstract int getPhoneDataConnectionCount(int dataType, int which);
+
+ public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS
+ = new BitDescription[] {
+ new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged"),
+ new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen"),
+ new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps"),
+ new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call"),
+ new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning"),
+ new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi"),
+ new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running"),
+ new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock"),
+ new BitDescription(HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG, "wifi_scan_lock"),
+ new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast"),
+ new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"),
+ new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"),
+ new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video"),
+ new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock"),
+ new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor"),
+ new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK,
+ HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness",
+ SCREEN_BRIGHTNESS_NAMES),
+ new BitDescription(HistoryItem.STATE_SIGNAL_STRENGTH_MASK,
+ HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT, "signal_strength",
+ SIGNAL_STRENGTH_NAMES),
+ new BitDescription(HistoryItem.STATE_PHONE_STATE_MASK,
+ HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state",
+ new String[] {"in", "out", "emergency", "off"}),
+ new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK,
+ HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn",
+ DATA_CONNECTION_NAMES),
+ };
/**
* Returns the time in microseconds that wifi has been on while the device was
@@ -575,6 +761,18 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getDischargeCurrentLevel();
/**
+ * Get the amount the battery has discharged since the stats were
+ * last reset after charging, as a lower-end approximation.
+ */
+ public abstract int getLowDischargeAmountSinceCharge();
+
+ /**
+ * Get the amount the battery has discharged since the stats were
+ * last reset after charging, as an upper-end approximation.
+ */
+ public abstract int getHighDischargeAmountSinceCharge();
+
+ /**
* Returns the total, last, or current battery uptime in microseconds.
*
* @param curTime the elapsed realtime in microseconds.
@@ -791,7 +989,7 @@ public abstract class BatteryStats implements Parcelable {
// Dump "battery" stat
dumpLine(pw, 0 /* uid */, category, BATTERY_DATA,
- which == STATS_TOTAL ? getStartCount() : "N/A",
+ which == STATS_SINCE_CHARGED ? getStartCount() : "N/A",
whichBatteryRealtime / 1000, whichBatteryUptime / 1000,
totalRealtime / 1000, totalUptime / 1000);
@@ -864,7 +1062,7 @@ public abstract class BatteryStats implements Parcelable {
}
dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_COUNT_DATA, args);
- if (which == STATS_UNPLUGGED) {
+ if (which == STATS_SINCE_UNPLUGGED) {
dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(),
getDischargeCurrentLevel());
}
@@ -1095,10 +1293,9 @@ public abstract class BatteryStats implements Parcelable {
linePrefix);
if (!linePrefix.equals(": ")) {
sb.append(" realtime");
- } else {
- sb.append(": (nothing executed)");
+ // Only print out wake locks that were held
+ pw.println(sb.toString());
}
- pw.println(sb.toString());
}
}
}
@@ -1212,7 +1409,7 @@ public abstract class BatteryStats implements Parcelable {
pw.println(" ");
- if (which == STATS_UNPLUGGED) {
+ if (which == STATS_SINCE_UNPLUGGED) {
if (getIsOnBattery()) {
pw.print(prefix); pw.println(" Device is currently unplugged");
pw.print(prefix); pw.print(" Discharge cycle start level: ");
@@ -1227,6 +1424,13 @@ public abstract class BatteryStats implements Parcelable {
pw.println(getDischargeCurrentLevel());
}
pw.println(" ");
+ } else {
+ pw.print(prefix); pw.println(" Device battery use since last full charge");
+ pw.print(prefix); pw.print(" Amount discharged (lower bound): ");
+ pw.println(getLowDischargeAmountSinceCharge());
+ pw.print(prefix); pw.print(" Amount discharged (upper bound): ");
+ pw.println(getHighDischargeAmountSinceCharge());
+ pw.println(" ");
}
@@ -1311,11 +1515,10 @@ public abstract class BatteryStats implements Parcelable {
"window", which, linePrefix);
if (!linePrefix.equals(": ")) {
sb.append(" realtime");
- } else {
- sb.append(": (nothing executed)");
+ // Only print out wake locks that were held
+ pw.println(sb.toString());
+ uidActivity = true;
}
- pw.println(sb.toString());
- uidActivity = true;
}
}
@@ -1368,21 +1571,39 @@ public abstract class BatteryStats implements Parcelable {
long userTime;
long systemTime;
int starts;
+ int numExcessive;
userTime = ps.getUserTime(which);
systemTime = ps.getSystemTime(which);
starts = ps.getStarts(which);
+ numExcessive = which == STATS_SINCE_CHARGED
+ ? ps.countExcessiveWakes() : 0;
- if (userTime != 0 || systemTime != 0 || starts != 0) {
+ if (userTime != 0 || systemTime != 0 || starts != 0
+ || numExcessive != 0) {
sb.setLength(0);
sb.append(prefix); sb.append(" Proc ");
sb.append(ent.getKey()); sb.append(":\n");
sb.append(prefix); sb.append(" CPU: ");
formatTime(sb, userTime); sb.append("usr + ");
- formatTime(sb, systemTime); sb.append("krn\n");
- sb.append(prefix); sb.append(" "); sb.append(starts);
- sb.append(" proc starts");
+ formatTime(sb, systemTime); sb.append("krn");
+ if (starts != 0) {
+ sb.append("\n"); sb.append(prefix); sb.append(" ");
+ sb.append(starts); sb.append(" proc starts");
+ }
pw.println(sb.toString());
+ for (int e=0; e<numExcessive; e++) {
+ Uid.Proc.ExcessiveWake ew = ps.getExcessiveWake(e);
+ if (ew != null) {
+ pw.print(prefix); pw.print(" * Killed for wake lock use: ");
+ TimeUtils.formatDuration(ew.usedTime, pw);
+ pw.print(" over ");
+ TimeUtils.formatDuration(ew.overTime, pw);
+ pw.print(" (");
+ pw.print((ew.usedTime*100)/ew.overTime);
+ pw.println("%)");
+ }
+ }
uidActivity = true;
}
}
@@ -1436,6 +1657,30 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) {
+ int diff = oldval ^ newval;
+ if (diff == 0) return;
+ for (int i=0; i<descriptions.length; i++) {
+ BitDescription bd = descriptions[i];
+ if ((diff&bd.mask) != 0) {
+ if (bd.shift < 0) {
+ pw.print((newval&bd.mask) != 0 ? " +" : " -");
+ pw.print(bd.name);
+ } else {
+ pw.print(" ");
+ pw.print(bd.name);
+ pw.print("=");
+ int val = (newval&bd.mask)>>bd.shift;
+ if (bd.values != null && val >= 0 && val < bd.values.length) {
+ pw.print(bd.values[val]);
+ } else {
+ pw.print(val);
+ }
+ }
+ }
+ }
+ }
+
/**
* Dumps a human-readable summary of the battery statistics to the given PrintWriter.
*
@@ -1443,19 +1688,161 @@ public abstract class BatteryStats implements Parcelable {
*/
@SuppressWarnings("unused")
public void dumpLocked(PrintWriter pw) {
- pw.println("Total Statistics (Current and Historic):");
+ HistoryItem rec = getHistory();
+ if (rec != null) {
+ pw.println("Battery History:");
+ long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
+ int oldState = 0;
+ int oldStatus = -1;
+ int oldHealth = -1;
+ int oldPlug = -1;
+ int oldTemp = -1;
+ int oldVolt = -1;
+ while (rec != null) {
+ pw.print(" ");
+ TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN);
+ pw.print(" ");
+ if (rec.cmd == HistoryItem.CMD_START) {
+ pw.println(" START");
+ } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) {
+ pw.println(" *OVERFLOW*");
+ } else {
+ if (rec.batteryLevel < 10) pw.print("00");
+ else if (rec.batteryLevel < 100) pw.print("0");
+ pw.print(rec.batteryLevel);
+ pw.print(" ");
+ if (rec.states < 0x10) pw.print("0000000");
+ else if (rec.states < 0x100) pw.print("000000");
+ else if (rec.states < 0x1000) pw.print("00000");
+ else if (rec.states < 0x10000) pw.print("0000");
+ else if (rec.states < 0x100000) pw.print("000");
+ else if (rec.states < 0x1000000) pw.print("00");
+ else if (rec.states < 0x10000000) pw.print("0");
+ pw.print(Integer.toHexString(rec.states));
+ if (oldStatus != rec.batteryStatus) {
+ oldStatus = rec.batteryStatus;
+ pw.print(" status=");
+ switch (oldStatus) {
+ case BatteryManager.BATTERY_STATUS_UNKNOWN:
+ pw.print("unknown");
+ break;
+ case BatteryManager.BATTERY_STATUS_CHARGING:
+ pw.print("charging");
+ break;
+ case BatteryManager.BATTERY_STATUS_DISCHARGING:
+ pw.print("discharging");
+ break;
+ case BatteryManager.BATTERY_STATUS_NOT_CHARGING:
+ pw.print("not-charging");
+ break;
+ case BatteryManager.BATTERY_STATUS_FULL:
+ pw.print("full");
+ break;
+ default:
+ pw.print(oldStatus);
+ break;
+ }
+ }
+ if (oldHealth != rec.batteryHealth) {
+ oldHealth = rec.batteryHealth;
+ pw.print(" health=");
+ switch (oldHealth) {
+ case BatteryManager.BATTERY_HEALTH_UNKNOWN:
+ pw.print("unknown");
+ break;
+ case BatteryManager.BATTERY_HEALTH_GOOD:
+ pw.print("good");
+ break;
+ case BatteryManager.BATTERY_HEALTH_OVERHEAT:
+ pw.print("overheat");
+ break;
+ case BatteryManager.BATTERY_HEALTH_DEAD:
+ pw.print("dead");
+ break;
+ case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
+ pw.print("over-voltage");
+ break;
+ case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE:
+ pw.print("failure");
+ break;
+ default:
+ pw.print(oldHealth);
+ break;
+ }
+ }
+ if (oldPlug != rec.batteryPlugType) {
+ oldPlug = rec.batteryPlugType;
+ pw.print(" plug=");
+ switch (oldPlug) {
+ case 0:
+ pw.print("none");
+ break;
+ case BatteryManager.BATTERY_PLUGGED_AC:
+ pw.print("ac");
+ break;
+ case BatteryManager.BATTERY_PLUGGED_USB:
+ pw.print("usb");
+ break;
+ default:
+ pw.print(oldPlug);
+ break;
+ }
+ }
+ if (oldTemp != rec.batteryTemperature) {
+ oldTemp = rec.batteryTemperature;
+ pw.print(" temp=");
+ pw.print(oldTemp);
+ }
+ if (oldVolt != rec.batteryVoltage) {
+ oldVolt = rec.batteryVoltage;
+ pw.print(" volt=");
+ pw.print(oldVolt);
+ }
+ printBitDescriptions(pw, oldState, rec.states,
+ HISTORY_STATE_DESCRIPTIONS);
+ pw.println();
+ }
+ oldState = rec.states;
+ rec = rec.next;
+ }
+ pw.println("");
+ }
+
+ SparseArray<? extends Uid> uidStats = getUidStats();
+ final int NU = uidStats.size();
+ boolean didPid = false;
+ long nowRealtime = SystemClock.elapsedRealtime();
+ StringBuilder sb = new StringBuilder(64);
+ for (int i=0; i<NU; i++) {
+ Uid uid = uidStats.valueAt(i);
+ SparseArray<? extends Uid.Pid> pids = uid.getPidStats();
+ if (pids != null) {
+ for (int j=0; j<pids.size(); j++) {
+ Uid.Pid pid = pids.valueAt(j);
+ if (!didPid) {
+ pw.println("Per-PID Stats:");
+ didPid = true;
+ }
+ long time = pid.mWakeSum + (pid.mWakeStart != 0
+ ? (nowRealtime - pid.mWakeStart) : 0);
+ pw.print(" PID "); pw.print(pids.keyAt(j));
+ pw.print(" wake time: ");
+ TimeUtils.formatDuration(time, pw);
+ pw.println("");
+ }
+ }
+ }
+ if (didPid) {
+ pw.println("");
+ }
+
+ pw.println("Statistics since last charge:");
pw.println(" System starts: " + getStartCount()
+ ", currently on battery: " + getIsOnBattery());
- dumpLocked(pw, "", STATS_TOTAL, -1);
+ dumpLocked(pw, "", STATS_SINCE_CHARGED, -1);
pw.println("");
- pw.println("Last Run Statistics (Previous run of system):");
- dumpLocked(pw, "", STATS_LAST, -1);
- pw.println("");
- pw.println("Current Battery Statistics (Currently running system):");
- dumpLocked(pw, "", STATS_CURRENT, -1);
- pw.println("");
- pw.println("Unplugged Statistics (Since last unplugged from power):");
- dumpLocked(pw, "", STATS_UNPLUGGED, -1);
+ pw.println("Statistics since last unplugged:");
+ dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, -1);
}
@SuppressWarnings("unused")
@@ -1470,14 +1857,11 @@ public abstract class BatteryStats implements Parcelable {
}
if (isUnpluggedOnly) {
- dumpCheckinLocked(pw, STATS_UNPLUGGED, -1);
+ dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1);
}
else {
- dumpCheckinLocked(pw, STATS_TOTAL, -1);
- dumpCheckinLocked(pw, STATS_LAST, -1);
- dumpCheckinLocked(pw, STATS_UNPLUGGED, -1);
- dumpCheckinLocked(pw, STATS_CURRENT, -1);
+ dumpCheckinLocked(pw, STATS_SINCE_CHARGED, -1);
+ dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1);
}
}
-
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index c9df5671ddf2..f8260cababeb 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -101,7 +101,30 @@ public class Binder implements IBinder {
* @see #clearCallingIdentity
*/
public static final native void restoreCallingIdentity(long token);
-
+
+ /**
+ * Sets the native thread-local StrictMode policy mask.
+ *
+ * <p>The StrictMode settings are kept in two places: a Java-level
+ * threadlocal for libcore/Dalvik, and a native threadlocal (set
+ * here) for propagation via Binder calls. This is a little
+ * unfortunate, but necessary to break otherwise more unfortunate
+ * dependencies either of Dalvik on Android, or Android
+ * native-only code on Dalvik.
+ *
+ * @see StrictMode
+ * @hide
+ */
+ public static final native void setThreadStrictModePolicy(int policyMask);
+
+ /**
+ * Gets the current native thread-local StrictMode policy mask.
+ *
+ * @see #setThreadStrictModePolicy
+ * @hide
+ */
+ public static final native int getThreadStrictModePolicy();
+
/**
* Flush any Binder commands pending in the current thread to the kernel
* driver. This can be
@@ -203,9 +226,16 @@ public class Binder implements IBinder {
try {
fd.close();
} catch (IOException e) {
+ // swallowed, not propagated back to the caller
}
}
}
+ // Write the StrictMode header.
+ if (reply != null) {
+ reply.writeNoException();
+ } else {
+ StrictMode.clearGatheredViolations();
+ }
return true;
}
return false;
@@ -276,6 +306,8 @@ public class Binder implements IBinder {
private native final void init();
private native final void destroy();
+
+ // Entry point from android_util_Binder.cpp's onTransact
private boolean execTransact(int code, int dataObj, int replyObj,
int flags) {
Parcel data = Parcel.obtain(dataObj);
@@ -316,12 +348,15 @@ final class BinderProxy implements IBinder {
public void dump(FileDescriptor fd, String[] args) throws RemoteException {
Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
data.writeFileDescriptor(fd);
data.writeStringArray(args);
try {
- transact(DUMP_TRANSACTION, data, null, 0);
+ transact(DUMP_TRANSACTION, data, reply, 0);
+ reply.readException();
} finally {
data.recycle();
+ reply.recycle();
}
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 3e9fd420a3fe..8925bd05cd83 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -62,6 +62,9 @@ public class Build {
/** The name of the hardware (from the kernel command line or /proc). */
public static final String HARDWARE = getString("ro.hardware");
+ /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */
+ public static final String SERIAL = getString("ro.serialno");
+
/** Various version strings. */
public static class VERSION {
/**
@@ -179,7 +182,23 @@ public class Build {
*/
public static final int ECLAIR_MR1 = 7;
+ /**
+ * June 2010: Android 2.2
+ */
public static final int FROYO = 8;
+
+ /**
+ * Next version of Android.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li> The status bar is now dark. Targeting this version allows
+ * the platform to perform performing compatibility on status bar
+ * graphics to ensure they look okay on a dark background.
+ * </ul>
+ */
+ public static final int GINGERBREAD = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index a9f9bd74de63..23fdb0bd7045 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -267,7 +267,7 @@ public class DropBoxManager {
if (file == null) throw new NullPointerException("file == null");
Entry entry = new Entry(tag, 0, file, flags);
try {
- mService.add(new Entry(tag, 0, file, flags));
+ mService.add(entry);
} catch (RemoteException e) {
// ignore
} finally {
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 812391c48e5f..c7cbed6e4ced 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -28,6 +28,8 @@ public class Environment {
private static final File ROOT_DIRECTORY
= getDirectory("ANDROID_ROOT", "/system");
+ private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
+
private static IMountService mMntSvc = null;
/**
@@ -37,9 +39,55 @@ public class Environment {
return ROOT_DIRECTORY;
}
+ /**
+ * Gets the system directory available for secure storage.
+ * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system).
+ * Otherwise, it returns the unencrypted /data/system directory.
+ * @return File object representing the secure storage system directory.
+ * @hide
+ */
+ public static File getSystemSecureDirectory() {
+ if (isEncryptedFilesystemEnabled()) {
+ return new File(SECURE_DATA_DIRECTORY, "system");
+ } else {
+ return new File(DATA_DIRECTORY, "system");
+ }
+ }
+
+ /**
+ * Gets the data directory for secure storage.
+ * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure).
+ * Otherwise, it returns the unencrypted /data directory.
+ * @return File object representing the data directory for secure storage.
+ * @hide
+ */
+ public static File getSecureDataDirectory() {
+ if (isEncryptedFilesystemEnabled()) {
+ return SECURE_DATA_DIRECTORY;
+ } else {
+ return DATA_DIRECTORY;
+ }
+ }
+
+ /**
+ * Returns whether the Encrypted File System feature is enabled on the device or not.
+ * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code>
+ * if disabled.
+ * @hide
+ */
+ public static boolean isEncryptedFilesystemEnabled() {
+ return SystemProperties.getBoolean(SYSTEM_PROPERTY_EFS_ENABLED, false);
+ }
+
private static final File DATA_DIRECTORY
= getDirectory("ANDROID_DATA", "/data");
+ /**
+ * @hide
+ */
+ private static final File SECURE_DATA_DIRECTORY
+ = getDirectory("ANDROID_SECURE_DATA", "/data/secure");
+
private static final File EXTERNAL_STORAGE_DIRECTORY
= getDirectory("EXTERNAL_STORAGE", "/sdcard");
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 2a32e543f19f..3b2bf1e81c80 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -563,13 +563,13 @@ public class Handler {
return mMessenger;
}
}
-
+
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
Handler.this.sendMessage(msg);
}
}
-
+
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 01cc408074d0..0067e9407777 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -17,10 +17,13 @@
package android.os;
+import android.os.WorkSource;
+
/** @hide */
interface IPowerManager
{
- void acquireWakeLock(int flags, IBinder lock, String tag);
+ void acquireWakeLock(int flags, IBinder lock, String tag, in WorkSource ws);
+ void updateWakeLockWorkSource(IBinder lock, in WorkSource ws);
void goToSleep(long time);
void goToSleepWithReason(long time, int reason);
void releaseWakeLock(IBinder lock, int flags);
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 69b35404a61b..d360140d0c10 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -180,6 +180,11 @@ public class Looper {
return mThread;
}
+ /** @hide */
+ public MessageQueue getQueue() {
+ return mQueue;
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + this);
pw.println(prefix + "mRun=" + mRun);
@@ -187,10 +192,11 @@ public class Looper {
pw.println(prefix + "mQueue=" + ((mQueue != null) ? mQueue : "(null"));
if (mQueue != null) {
synchronized (mQueue) {
+ long now = SystemClock.uptimeMillis();
Message msg = mQueue.mMessages;
int n = 0;
while (msg != null) {
- pw.println(prefix + " Message " + n + ": " + msg);
+ pw.println(prefix + " Message " + n + ": " + msg.toString(now));
n++;
msg = msg.next;
}
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 476da1df32db..49b72fee22d3 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -19,6 +19,7 @@ package android.os;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.TimeUtils;
/**
*
@@ -366,13 +367,17 @@ public final class Message implements Parcelable {
}
public String toString() {
+ return toString(SystemClock.uptimeMillis());
+ }
+
+ String toString(long now) {
StringBuilder b = new StringBuilder();
b.append("{ what=");
b.append(what);
b.append(" when=");
- b.append(when);
+ TimeUtils.formatDuration(when-now, b);
if (arg1 != 0) {
b.append(" arg1=");
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index bc653d64864d..281386305311 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,13 +16,11 @@
package android.os;
-import java.util.ArrayList;
-
import android.util.AndroidRuntimeException;
import android.util.Config;
import android.util.Log;
-import com.android.internal.os.RuntimeInit;
+import java.util.ArrayList;
/**
* Low-level class holding the list of messages to be dispatched by a
@@ -34,10 +32,18 @@ import com.android.internal.os.RuntimeInit;
*/
public class MessageQueue {
Message mMessages;
- private final ArrayList mIdleHandlers = new ArrayList();
+ private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private boolean mQuiting = false;
boolean mQuitAllowed = true;
+
+ @SuppressWarnings("unused")
+ private int mPtr; // used by native code
+ private native void nativeInit();
+ private native void nativeDestroy();
+ private native boolean nativePollOnce(int timeoutMillis);
+ private native void nativeWake();
+
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
@@ -84,22 +90,39 @@ public class MessageQueue {
mIdleHandlers.remove(handler);
}
}
-
+
MessageQueue() {
+ nativeInit();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nativeDestroy();
+ } finally {
+ super.finalize();
+ }
}
final Message next() {
boolean tryIdle = true;
+ // when we start out, we'll just touch the input pipes and then go from there
+ int timeToNextEventMillis = 0;
while (true) {
long now;
Object[] idlers = null;
-
+
+ boolean dispatched = nativePollOnce(timeToNextEventMillis);
+
// Try to retrieve the next message, returning if found.
synchronized (this) {
now = SystemClock.uptimeMillis();
Message msg = pullNextLocked(now);
- if (msg != null) return msg;
+ if (msg != null) {
+ return msg;
+ }
+
if (tryIdle && mIdleHandlers.size() > 0) {
idlers = mIdleHandlers.toArray();
}
@@ -135,20 +158,22 @@ public class MessageQueue {
synchronized (this) {
// No messages, nobody to tell about it... time to wait!
- try {
- if (mMessages != null) {
- if (mMessages.when-now > 0) {
- Binder.flushPendingCommands();
- this.wait(mMessages.when-now);
- }
- } else {
+ if (mMessages != null) {
+ long longTimeToNextEventMillis = mMessages.when - now;
+
+ if (longTimeToNextEventMillis > 0) {
Binder.flushPendingCommands();
- this.wait();
+ timeToNextEventMillis = (int) Math.min(longTimeToNextEventMillis,
+ Integer.MAX_VALUE);
+ } else {
+ timeToNextEventMillis = 0;
}
- }
- catch (InterruptedException e) {
+ } else {
+ Binder.flushPendingCommands();
+ timeToNextEventMillis = -1;
}
}
+ // loop to the while(true) and do the appropriate nativeWait(when)
}
}
@@ -190,7 +215,6 @@ public class MessageQueue {
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
- this.notify();
} else {
Message prev = null;
while (p != null && p.when <= when) {
@@ -199,9 +223,9 @@ public class MessageQueue {
}
msg.next = prev.next;
prev.next = msg;
- this.notify();
}
}
+ nativeWake();
return true;
}
@@ -317,11 +341,4 @@ public class MessageQueue {
}
}
*/
-
- void poke()
- {
- synchronized (this) {
- this.notify();
- }
- }
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index bb6357e55b19..31f87198878a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -176,6 +176,7 @@ import java.util.Set;
*/
public final class Parcel {
private static final boolean DEBUG_RECYCLE = false;
+ private static final String TAG = "Parcel";
@SuppressWarnings({"UnusedDeclaration"})
private int mObject; // used by native code
@@ -214,12 +215,14 @@ public final class Parcel {
private static final int VAL_BOOLEANARRAY = 23;
private static final int VAL_CHARSEQUENCEARRAY = 24;
+ // The initial int32 in a Binder call's reply Parcel header:
private static final int EX_SECURITY = -1;
private static final int EX_BAD_PARCELABLE = -2;
private static final int EX_ILLEGAL_ARGUMENT = -3;
private static final int EX_NULL_POINTER = -4;
private static final int EX_ILLEGAL_STATE = -5;
-
+ private static final int EX_HAS_REPLY_HEADER = -128; // special; see below
+
public final static Parcelable.Creator<String> STRING_CREATOR
= new Parcelable.Creator<String>() {
public String createFromParcel(Parcel source) {
@@ -1212,6 +1215,7 @@ public final class Parcel {
code = EX_ILLEGAL_STATE;
}
writeInt(code);
+ StrictMode.clearGatheredViolations();
if (code == 0) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
@@ -1229,7 +1233,31 @@ public final class Parcel {
* @see #readException
*/
public final void writeNoException() {
- writeInt(0);
+ // Despite the name of this function ("write no exception"),
+ // it should instead be thought of as "write the RPC response
+ // header", but because this function name is written out by
+ // the AIDL compiler, we're not going to rename it.
+ //
+ // The response header, in the non-exception case (see also
+ // writeException above, also called by the AIDL compiler), is
+ // either a 0 (the default case), or EX_HAS_REPLY_HEADER if
+ // StrictMode has gathered up violations that have occurred
+ // during a Binder call, in which case we write out the number
+ // of violations and their details, serialized, before the
+ // actual RPC respons data. The receiving end of this is
+ // readException(), below.
+ if (StrictMode.hasGatheredViolations()) {
+ writeInt(EX_HAS_REPLY_HEADER);
+ final int sizePosition = dataPosition();
+ writeInt(0); // total size of fat header, to be filled in later
+ StrictMode.writeGatheredViolationsToParcel(this);
+ final int payloadPosition = dataPosition();
+ setDataPosition(sizePosition);
+ writeInt(payloadPosition - sizePosition); // header size
+ setDataPosition(payloadPosition);
+ } else {
+ writeInt(0);
+ }
}
/**
@@ -1242,10 +1270,44 @@ public final class Parcel {
* @see #writeNoException
*/
public final void readException() {
+ int code = readExceptionCode();
+ if (code != 0) {
+ String msg = readString();
+ readException(code, msg);
+ }
+ }
+
+ /**
+ * Parses the header of a Binder call's response Parcel and
+ * returns the exception code. Deals with lite or fat headers.
+ * In the common successful case, this header is generally zero.
+ * In less common cases, it's a small negative number and will be
+ * followed by an error string.
+ *
+ * This exists purely for android.database.DatabaseUtils and
+ * insulating it from having to handle fat headers as returned by
+ * e.g. StrictMode-induced RPC responses.
+ *
+ * @hide
+ */
+ public final int readExceptionCode() {
int code = readInt();
- if (code == 0) return;
- String msg = readString();
- readException(code, msg);
+ if (code == EX_HAS_REPLY_HEADER) {
+ int headerSize = readInt();
+ if (headerSize == 0) {
+ Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
+ } else {
+ // Currently the only thing in the header is StrictMode stacks,
+ // but discussions around event/RPC tracing suggest we might
+ // put that here too. If so, switch on sub-header tags here.
+ // But for now, just parse out the StrictMode stuff.
+ StrictMode.readAndHandleBinderCallViolations(this);
+ }
+ // And fat response headers are currently only used when
+ // there are no exceptions, so return no error:
+ return 0;
+ }
+ return code;
}
/**
@@ -1885,13 +1947,13 @@ public final class Parcel {
creator = (Parcelable.Creator)f.get(null);
}
catch (IllegalAccessException e) {
- Log.e("Parcel", "Class not found when unmarshalling: "
+ Log.e(TAG, "Class not found when unmarshalling: "
+ name + ", e: " + e);
throw new BadParcelableException(
"IllegalAccessException when unmarshalling: " + name);
}
catch (ClassNotFoundException e) {
- Log.e("Parcel", "Class not found when unmarshalling: "
+ Log.e(TAG, "Class not found when unmarshalling: "
+ name + ", e: " + e);
throw new BadParcelableException(
"ClassNotFoundException when unmarshalling: " + name);
@@ -1996,7 +2058,7 @@ public final class Parcel {
if (DEBUG_RECYCLE) {
mStack = new RuntimeException();
}
- //Log.i("Parcel", "Initializing obj=0x" + Integer.toHexString(obj), mStack);
+ //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
init(obj);
}
@@ -2004,7 +2066,7 @@ public final class Parcel {
protected void finalize() throws Throwable {
if (DEBUG_RECYCLE) {
if (mStack != null) {
- Log.w("Parcel", "Client did not call Parcel.recycle()", mStack);
+ Log.w(TAG, "Client did not call Parcel.recycle()", mStack);
}
}
destroy();
@@ -2028,7 +2090,7 @@ public final class Parcel {
ClassLoader loader) {
while (N > 0) {
Object value = readValue(loader);
- //Log.d("Parcel", "Unmarshalling value=" + value);
+ //Log.d(TAG, "Unmarshalling value=" + value);
outVal.add(value);
N--;
}
@@ -2038,7 +2100,7 @@ public final class Parcel {
ClassLoader loader) {
for (int i = 0; i < N; i++) {
Object value = readValue(loader);
- //Log.d("Parcel", "Unmarshalling value=" + value);
+ //Log.d(TAG, "Unmarshalling value=" + value);
outVal[i] = value;
}
}
@@ -2048,7 +2110,7 @@ public final class Parcel {
while (N > 0) {
int key = readInt();
Object value = readValue(loader);
- //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value);
+ //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
outVal.append(key, value);
N--;
}
@@ -2059,7 +2121,7 @@ public final class Parcel {
while (N > 0) {
int key = readInt();
boolean value = this.readByte() == 1;
- //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value);
+ //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value);
outVal.append(key, value);
N--;
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index e8fe302e28d3..9d213b344dc8 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -179,7 +179,7 @@ public class ParcelFileDescriptor implements Parcelable {
/**
* An InputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
- * ParcelFileDescritor.close()} for you when the stream is closed.
+ * ParcelFileDescriptor.close()} for you when the stream is closed.
*/
public static class AutoCloseInputStream extends FileInputStream {
private final ParcelFileDescriptor mFd;
@@ -198,7 +198,7 @@ public class ParcelFileDescriptor implements Parcelable {
/**
* An OutputStream you can create on a ParcelFileDescriptor, which will
* take care of calling {@link ParcelFileDescriptor#close
- * ParcelFileDescritor.close()} for you when the stream is closed.
+ * ParcelFileDescriptor.close()} for you when the stream is closed.
*/
public static class AutoCloseOutputStream extends FileOutputStream {
private final ParcelFileDescriptor mFd;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index f4ca8bc85469..3876a3e55873 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -209,6 +209,7 @@ public class PowerManager
int mCount = 0;
boolean mRefCounted = true;
boolean mHeld = false;
+ WorkSource mWorkSource;
WakeLock(int flags, String tag)
{
@@ -247,7 +248,7 @@ public class PowerManager
synchronized (mToken) {
if (!mRefCounted || mCount++ == 0) {
try {
- mService.acquireWakeLock(mFlags, mToken, mTag);
+ mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource);
} catch (RemoteException e) {
}
mHeld = true;
@@ -313,6 +314,32 @@ public class PowerManager
}
}
+ public void setWorkSource(WorkSource ws) {
+ synchronized (mToken) {
+ if (ws != null && ws.size() == 0) {
+ ws = null;
+ }
+ boolean changed = true;
+ if (ws == null) {
+ mWorkSource = null;
+ } else if (mWorkSource == null) {
+ changed = mWorkSource != null;
+ mWorkSource = new WorkSource(ws);
+ } else {
+ changed = mWorkSource.diff(ws);
+ if (changed) {
+ mWorkSource.set(ws);
+ }
+ }
+ if (changed && mHeld) {
+ try {
+ mService.updateWakeLockWorkSource(mToken, mWorkSource);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
public String toString() {
synchronized (mToken) {
return "WakeLock{"
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 5640a0627721..f695dbb4cdd4 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -626,6 +626,15 @@ public class Process {
throws IllegalArgumentException, SecurityException;
/**
+ * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
+ * throw an exception if passed a background-level thread priority. This is only
+ * effective if the JNI layer is built with GUARD_THREAD_PRIORITY defined to 1.
+ *
+ * @hide
+ */
+ public static final native void setCanSelfBackground(boolean backgroundOk);
+
+ /**
* Sets the scheduling group for a thread.
* @hide
* @param tid The indentifier of the thread/process to change.
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index b6dc1b52dffa..5fea6fec453f 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -348,6 +348,23 @@ public class RecoverySystem {
}
/**
+ * Reboot into the recovery system to wipe the /data partition and toggle
+ * Encrypted File Systems on/off.
+ * @param extras to add to the RECOVERY_COMPLETED intent after rebooting.
+ * @throws IOException if something goes wrong.
+ *
+ * @hide
+ */
+ public static void rebootToggleEFS(Context context, boolean efsEnabled)
+ throws IOException {
+ if (efsEnabled) {
+ bootCommand(context, "--set_encrypted_filesystem=on");
+ } else {
+ bootCommand(context, "--set_encrypted_filesystem=off");
+ }
+ }
+
+ /**
* Reboot into the recovery system with the supplied argument.
* @param arg to pass to the recovery utility.
* @throws IOException if something goes wrong.
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
new file mode 100644
index 000000000000..7f7b02b5f1bd
--- /dev/null
+++ b/core/java/android/os/StrictMode.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.app.ActivityManagerNative;
+import android.app.ApplicationErrorReport;
+import android.util.Log;
+import android.util.Printer;
+
+import com.android.internal.os.RuntimeInit;
+
+import dalvik.system.BlockGuard;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * <p>StrictMode lets you impose stricter rules under which your
+ * application runs.</p>
+ */
+public final class StrictMode {
+ private static final String TAG = "StrictMode";
+ private static final boolean LOG_V = false;
+
+ // Only log a duplicate stack trace to the logs every second.
+ private static final long MIN_LOG_INTERVAL_MS = 1000;
+
+ // Only show an annoying dialog at most every 30 seconds
+ private static final long MIN_DIALOG_INTERVAL_MS = 30000;
+
+ private StrictMode() {}
+
+ public static final int DISALLOW_DISK_WRITE = 0x01;
+ public static final int DISALLOW_DISK_READ = 0x02;
+ public static final int DISALLOW_NETWORK = 0x04;
+
+ /** @hide */
+ public static final int DISALLOW_MASK =
+ DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK;
+
+ /**
+ * Flag to log to the system log.
+ */
+ public static final int PENALTY_LOG = 0x10; // normal android.util.Log
+
+ /**
+ * Show an annoying dialog to the user. Will be rate-limited to be only
+ * a little annoying.
+ */
+ public static final int PENALTY_DIALOG = 0x20;
+
+ /**
+ * Crash hard if policy is violated.
+ */
+ public static final int PENALTY_DEATH = 0x40;
+
+ /**
+ * Log a stacktrace to the DropBox on policy violation.
+ */
+ public static final int PENALTY_DROPBOX = 0x80;
+
+ /**
+ * Non-public penalty mode which overrides all the other penalty
+ * bits and signals that we're in a Binder call and we should
+ * ignore the other penalty bits and instead serialize back all
+ * our offending stack traces to the caller to ultimately handle
+ * in the originating process.
+ *
+ * This must be kept in sync with the constant in libs/binder/Parcel.cpp
+ *
+ * @hide
+ */
+ public static final int PENALTY_GATHER = 0x100;
+
+ /** @hide */
+ public static final int PENALTY_MASK =
+ PENALTY_LOG | PENALTY_DIALOG |
+ PENALTY_DROPBOX | PENALTY_DEATH;
+
+ /**
+ * Log of strict mode violation stack traces that have occurred
+ * during a Binder call, to be serialized back later to the caller
+ * via Parcel.writeNoException() (amusingly) where the caller can
+ * choose how to react.
+ */
+ private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations =
+ new ThreadLocal<ArrayList<ViolationInfo>>() {
+ @Override protected ArrayList<ViolationInfo> initialValue() {
+ // Starts null to avoid unnecessary allocations when
+ // checking whether there are any violations or not in
+ // hasGatheredViolations() below.
+ return null;
+ }
+ };
+
+ /**
+ * Sets the policy for what actions the current thread is denied,
+ * as well as the penalty for violating the policy.
+ *
+ * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values.
+ */
+ public static void setThreadPolicy(final int policyMask) {
+ // In addition to the Java-level thread-local in Dalvik's
+ // BlockGuard, we also need to keep a native thread-local in
+ // Binder in order to propagate the value across Binder calls,
+ // even across native-only processes. The two are kept in
+ // sync via the callback to onStrictModePolicyChange, below.
+ setBlockGuardPolicy(policyMask);
+
+ // And set the Android native version...
+ Binder.setThreadStrictModePolicy(policyMask);
+ }
+
+ // Sets the policy in Dalvik/libcore (BlockGuard)
+ private static void setBlockGuardPolicy(final int policyMask) {
+ if (policyMask == 0) {
+ BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY);
+ return;
+ }
+ BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+ if (!(policy instanceof AndroidBlockGuardPolicy)) {
+ BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask));
+ } else {
+ AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy;
+ androidPolicy.setPolicyMask(policyMask);
+ }
+ }
+
+ private static class StrictModeNetworkViolation extends BlockGuard.BlockGuardPolicyException {
+ public StrictModeNetworkViolation(int policyMask) {
+ super(policyMask, DISALLOW_NETWORK);
+ }
+ }
+
+ private static class StrictModeDiskReadViolation extends BlockGuard.BlockGuardPolicyException {
+ public StrictModeDiskReadViolation(int policyMask) {
+ super(policyMask, DISALLOW_DISK_READ);
+ }
+ }
+
+ private static class StrictModeDiskWriteViolation extends BlockGuard.BlockGuardPolicyException {
+ public StrictModeDiskWriteViolation(int policyMask) {
+ super(policyMask, DISALLOW_DISK_WRITE);
+ }
+ }
+
+ /**
+ * Returns the bitmask of the current thread's blocking policy.
+ *
+ * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled
+ */
+ public static int getThreadPolicy() {
+ return BlockGuard.getThreadPolicy().getPolicyMask();
+ }
+
+ /**
+ * Updates the current thread's policy mask to allow reading &amp;
+ * writing to disk.
+ *
+ * @return the old policy mask, to be passed to setThreadPolicy to
+ * restore the policy.
+ */
+ public static int allowThreadDiskWrites() {
+ int oldPolicy = getThreadPolicy();
+ int newPolicy = oldPolicy & ~(DISALLOW_DISK_WRITE | DISALLOW_DISK_READ);
+ if (newPolicy != oldPolicy) {
+ setThreadPolicy(newPolicy);
+ }
+ return oldPolicy;
+ }
+
+ /**
+ * Updates the current thread's policy mask to allow reading from
+ * disk.
+ *
+ * @return the old policy mask, to be passed to setThreadPolicy to
+ * restore the policy.
+ */
+ public static int allowThreadDiskReads() {
+ int oldPolicy = getThreadPolicy();
+ int newPolicy = oldPolicy & ~(DISALLOW_DISK_READ);
+ if (newPolicy != oldPolicy) {
+ setThreadPolicy(newPolicy);
+ }
+ return oldPolicy;
+ }
+
+ /**
+ * Parses the BlockGuard policy mask out from the Exception's
+ * getMessage() String value. Kinda gross, but least
+ * invasive. :/
+ *
+ * Input is of form "policy=137 violation=64"
+ *
+ * Returns 0 on failure, which is a valid policy, but not a
+ * valid policy during a violation (else there must've been
+ * some policy in effect to violate).
+ */
+ private static int parsePolicyFromMessage(String message) {
+ if (message == null || !message.startsWith("policy=")) {
+ return 0;
+ }
+ int spaceIndex = message.indexOf(' ');
+ if (spaceIndex == -1) {
+ return 0;
+ }
+ String policyString = message.substring(7, spaceIndex);
+ try {
+ return Integer.valueOf(policyString).intValue();
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ /**
+ * Like parsePolicyFromMessage(), but returns the violation.
+ */
+ private static int parseViolationFromMessage(String message) {
+ if (message == null) {
+ return 0;
+ }
+ int violationIndex = message.indexOf("violation=");
+ if (violationIndex == -1) {
+ return 0;
+ }
+ String violationString = message.substring(violationIndex + 10);
+ try {
+ return Integer.valueOf(violationString).intValue();
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ private static class AndroidBlockGuardPolicy implements BlockGuard.Policy {
+ private int mPolicyMask;
+
+ // Map from violation stacktrace hashcode -> uptimeMillis of
+ // last violation. No locking needed, as this is only
+ // accessed by the same thread.
+ private final HashMap<Integer, Long> mLastViolationTime = new HashMap<Integer, Long>();
+
+ public AndroidBlockGuardPolicy(final int policyMask) {
+ mPolicyMask = policyMask;
+ }
+
+ @Override
+ public String toString() {
+ return "AndroidBlockGuardPolicy; mPolicyMask=" + mPolicyMask;
+ }
+
+ // Part of BlockGuard.Policy interface:
+ public int getPolicyMask() {
+ return mPolicyMask;
+ }
+
+ // Part of BlockGuard.Policy interface:
+ public void onWriteToDisk() {
+ if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
+ return;
+ }
+ BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
+ e.fillInStackTrace();
+ startHandlingViolationException(e);
+ }
+
+ // Part of BlockGuard.Policy interface:
+ public void onReadFromDisk() {
+ if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
+ return;
+ }
+ BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
+ e.fillInStackTrace();
+ startHandlingViolationException(e);
+ }
+
+ // Part of BlockGuard.Policy interface:
+ public void onNetwork() {
+ if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
+ return;
+ }
+ BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
+ e.fillInStackTrace();
+ startHandlingViolationException(e);
+ }
+
+ public void setPolicyMask(int policyMask) {
+ mPolicyMask = policyMask;
+ }
+
+ // Start handling a violation that just started and hasn't
+ // actually run yet (e.g. no disk write or network operation
+ // has yet occurred). This sees if we're in an event loop
+ // thread and, if so, uses it to roughly measure how long the
+ // violation took.
+ void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) {
+ final ViolationInfo info = new ViolationInfo(e, e.getPolicy());
+ info.violationUptimeMillis = SystemClock.uptimeMillis();
+ handleViolationWithTimingAttempt(info);
+ }
+
+ private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
+ new ThreadLocal<ArrayList<ViolationInfo>>() {
+ @Override protected ArrayList<ViolationInfo> initialValue() {
+ return new ArrayList<ViolationInfo>();
+ }
+ };
+
+ // Attempts to fill in the provided ViolationInfo's
+ // durationMillis field if this thread has a Looper we can use
+ // to measure with. We measure from the time of violation
+ // until the time the looper is idle again (right before
+ // the next epoll_wait)
+ void handleViolationWithTimingAttempt(final ViolationInfo info) {
+ Looper looper = Looper.myLooper();
+
+ // Without a Looper, we're unable to time how long the
+ // violation takes place. This case should be rare, as
+ // most users will care about timing violations that
+ // happen on their main UI thread. Note that this case is
+ // also hit when a violation takes place in a Binder
+ // thread, in "gather" mode. In this case, the duration
+ // of the violation is computed by the ultimate caller and
+ // its Looper, if any.
+ // TODO: if in gather mode, ignore Looper.myLooper() and always
+ // go into this immediate mode?
+ if (looper == null) {
+ info.durationMillis = -1; // unknown (redundant, already set)
+ handleViolation(info);
+ return;
+ }
+
+ MessageQueue queue = Looper.myQueue();
+ final ArrayList<ViolationInfo> records = violationsBeingTimed.get();
+ if (records.size() >= 10) {
+ // Not worth measuring. Too many offenses in one loop.
+ return;
+ }
+ records.add(info);
+ if (records.size() > 1) {
+ // There's already been a violation this loop, so we've already
+ // registered an idle handler to process the list of violations
+ // at the end of this Looper's loop.
+ return;
+ }
+
+ queue.addIdleHandler(new MessageQueue.IdleHandler() {
+ public boolean queueIdle() {
+ long loopFinishTime = SystemClock.uptimeMillis();
+ for (int n = 0; n < records.size(); ++n) {
+ ViolationInfo v = records.get(n);
+ v.violationNumThisLoop = n + 1;
+ v.durationMillis =
+ (int) (loopFinishTime - v.violationUptimeMillis);
+ handleViolation(v);
+ }
+ records.clear();
+ return false; // remove this idle handler from the array
+ }
+ });
+ }
+
+ // Note: It's possible (even quite likely) that the
+ // thread-local policy mask has changed from the time the
+ // violation fired and now (after the violating code ran) due
+ // to people who push/pop temporary policy in regions of code,
+ // hence the policy being passed around.
+ void handleViolation(final ViolationInfo info) {
+ if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) {
+ Log.wtf(TAG, "unexpected null stacktrace");
+ return;
+ }
+
+ if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.policy);
+
+ if ((info.policy & PENALTY_GATHER) != 0) {
+ ArrayList<ViolationInfo> violations = gatheredViolations.get();
+ if (violations == null) {
+ violations = new ArrayList<ViolationInfo>(1);
+ gatheredViolations.set(violations);
+ } else if (violations.size() >= 5) {
+ // Too many. In a loop or something? Don't gather them all.
+ return;
+ }
+ for (ViolationInfo previous : violations) {
+ if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) {
+ // Duplicate. Don't log.
+ return;
+ }
+ }
+ violations.add(info);
+ return;
+ }
+
+ // Not perfect, but fast and good enough for dup suppression.
+ Integer crashFingerprint = info.crashInfo.stackTrace.hashCode();
+ long lastViolationTime = 0;
+ if (mLastViolationTime.containsKey(crashFingerprint)) {
+ lastViolationTime = mLastViolationTime.get(crashFingerprint);
+ }
+ long now = SystemClock.uptimeMillis();
+ mLastViolationTime.put(crashFingerprint, now);
+ long timeSinceLastViolationMillis = lastViolationTime == 0 ?
+ Long.MAX_VALUE : (now - lastViolationTime);
+
+ if ((info.policy & PENALTY_LOG) != 0 &&
+ timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+ if (info.durationMillis != -1) {
+ Log.d(TAG, "StrictMode policy violation; ~duration=" +
+ info.durationMillis + " ms: " + info.crashInfo.stackTrace);
+ } else {
+ Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace);
+ }
+ }
+
+ // The violationMask, passed to ActivityManager, is a
+ // subset of the original StrictMode policy bitmask, with
+ // only the bit violated and penalty bits to be executed
+ // by the ActivityManagerService remaining set.
+ int violationMaskSubset = 0;
+
+ if ((info.policy & PENALTY_DIALOG) != 0 &&
+ timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
+ violationMaskSubset |= PENALTY_DIALOG;
+ }
+
+ if ((info.policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
+ violationMaskSubset |= PENALTY_DROPBOX;
+ }
+
+ if (violationMaskSubset != 0) {
+ int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
+ violationMaskSubset |= violationBit;
+ final int savedPolicy = getThreadPolicy();
+ try {
+ // First, remove any policy before we call into the Activity Manager,
+ // otherwise we'll infinite recurse as we try to log policy violations
+ // to disk, thus violating policy, thus requiring logging, etc...
+ // We restore the current policy below, in the finally block.
+ setThreadPolicy(0);
+
+ ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(),
+ violationMaskSubset,
+ info);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
+ } finally {
+ // Restore the policy.
+ setThreadPolicy(savedPolicy);
+ }
+ }
+
+ if ((info.policy & PENALTY_DEATH) != 0) {
+ System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down.");
+ Process.killProcess(Process.myPid());
+ System.exit(10);
+ }
+ }
+ }
+
+ /**
+ * Called from Parcel.writeNoException()
+ */
+ /* package */ static boolean hasGatheredViolations() {
+ return gatheredViolations.get() != null;
+ }
+
+ /**
+ * Called from Parcel.writeException(), so we drop this memory and
+ * don't incorrectly attribute it to the wrong caller on the next
+ * Binder call on this thread.
+ */
+ /* package */ static void clearGatheredViolations() {
+ gatheredViolations.set(null);
+ }
+
+ /**
+ * Called from Parcel.writeNoException()
+ */
+ /* package */ static void writeGatheredViolationsToParcel(Parcel p) {
+ ArrayList<ViolationInfo> violations = gatheredViolations.get();
+ if (violations == null) {
+ p.writeInt(0);
+ } else {
+ p.writeInt(violations.size());
+ for (int i = 0; i < violations.size(); ++i) {
+ violations.get(i).writeToParcel(p, 0 /* unused flags? */);
+ }
+ if (LOG_V) Log.d(TAG, "wrote violations to response parcel; num=" + violations.size());
+ violations.clear(); // somewhat redundant, as we're about to null the threadlocal
+ }
+ gatheredViolations.set(null);
+ }
+
+ private static class LogStackTrace extends Exception {}
+
+ /**
+ * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS,
+ * we here read back all the encoded violations.
+ */
+ /* package */ static void readAndHandleBinderCallViolations(Parcel p) {
+ // Our own stack trace to append
+ StringWriter sw = new StringWriter();
+ new LogStackTrace().printStackTrace(new PrintWriter(sw));
+ String ourStack = sw.toString();
+
+ int policyMask = getThreadPolicy();
+ boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
+
+ int numViolations = p.readInt();
+ for (int i = 0; i < numViolations; ++i) {
+ if (LOG_V) Log.d(TAG, "strict mode violation stacks read from binder call. i=" + i);
+ ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
+ info.crashInfo.stackTrace += "# via Binder call with stack:\n" + ourStack;
+ BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+ if (policy instanceof AndroidBlockGuardPolicy) {
+ ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
+ }
+ }
+ }
+
+ /**
+ * Called from android_util_Binder.cpp's
+ * android_os_Parcel_enforceInterface when an incoming Binder call
+ * requires changing the StrictMode policy mask. The role of this
+ * function is to ask Binder for its current (native) thread-local
+ * policy value and synchronize it to libcore's (Java)
+ * thread-local policy value.
+ */
+ private static void onBinderStrictModePolicyChange(int newPolicy) {
+ setBlockGuardPolicy(newPolicy);
+ }
+
+ /**
+ * Parcelable that gets sent in Binder call headers back to callers
+ * to report violations that happened during a cross-process call.
+ *
+ * @hide
+ */
+ public static class ViolationInfo {
+ /**
+ * Stack and other stuff info.
+ */
+ public final ApplicationErrorReport.CrashInfo crashInfo;
+
+ /**
+ * The strict mode policy mask at the time of violation.
+ */
+ public final int policy;
+
+ /**
+ * The wall time duration of the violation, when known. -1 when
+ * not known.
+ */
+ public int durationMillis = -1;
+
+ /**
+ * Which violation number this was (1-based) since the last Looper loop,
+ * from the perspective of the root caller (if it crossed any processes
+ * via Binder calls). The value is 0 if the root caller wasn't on a Looper
+ * thread.
+ */
+ public int violationNumThisLoop;
+
+ /**
+ * The time (in terms of SystemClock.uptimeMillis()) that the
+ * violation occurred.
+ */
+ public long violationUptimeMillis;
+
+ /**
+ * Create an uninitialized instance of ViolationInfo
+ */
+ public ViolationInfo() {
+ crashInfo = null;
+ policy = 0;
+ }
+
+ /**
+ * Create an instance of ViolationInfo initialized from an exception.
+ */
+ public ViolationInfo(Throwable tr, int policy) {
+ crashInfo = new ApplicationErrorReport.CrashInfo(tr);
+ violationUptimeMillis = SystemClock.uptimeMillis();
+ this.policy = policy;
+ }
+
+ /**
+ * Create an instance of ViolationInfo initialized from a Parcel.
+ */
+ public ViolationInfo(Parcel in) {
+ this(in, false);
+ }
+
+ /**
+ * Create an instance of ViolationInfo initialized from a Parcel.
+ *
+ * @param unsetGatheringBit if true, the caller is the root caller
+ * and the gathering penalty should be removed.
+ */
+ public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
+ crashInfo = new ApplicationErrorReport.CrashInfo(in);
+ int rawPolicy = in.readInt();
+ if (unsetGatheringBit) {
+ policy = rawPolicy & ~PENALTY_GATHER;
+ } else {
+ policy = rawPolicy;
+ }
+ durationMillis = in.readInt();
+ violationNumThisLoop = in.readInt();
+ violationUptimeMillis = in.readLong();
+ }
+
+ /**
+ * Save a ViolationInfo instance to a parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ crashInfo.writeToParcel(dest, flags);
+ dest.writeInt(policy);
+ dest.writeInt(durationMillis);
+ dest.writeInt(violationNumThisLoop);
+ dest.writeLong(violationUptimeMillis);
+ }
+
+
+ /**
+ * Dump a ViolationInfo instance to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ crashInfo.dump(pw, prefix);
+ pw.println(prefix + "policy: " + policy);
+ if (durationMillis != -1) {
+ pw.println(prefix + "durationMillis: " + durationMillis);
+ }
+ if (violationNumThisLoop != 0) {
+ pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop);
+ }
+ pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis);
+ }
+
+ }
+}
diff --git a/core/java/android/os/WorkSource.aidl b/core/java/android/os/WorkSource.aidl
new file mode 100644
index 000000000000..1e7fabcbd889
--- /dev/null
+++ b/core/java/android/os/WorkSource.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.os;
+
+parcelable WorkSource;
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
new file mode 100644
index 000000000000..bba1984367d7
--- /dev/null
+++ b/core/java/android/os/WorkSource.java
@@ -0,0 +1,311 @@
+package android.os;
+
+/**
+ * Describes the source of some work that may be done by someone else.
+ * Currently the public representation of what a work source is is not
+ * defined; this is an opaque container.
+ */
+public class WorkSource implements Parcelable {
+ int mNum;
+ int[] mUids;
+
+ /**
+ * Internal statics to avoid object allocations in some operations.
+ * The WorkSource object itself is not thread safe, but we need to
+ * hold sTmpWorkSource lock while working with these statics.
+ */
+ static final WorkSource sTmpWorkSource = new WorkSource(0);
+ /**
+ * For returning newbie work from a modification operation.
+ */
+ static WorkSource sNewbWork;
+ /**
+ * For returning gone work form a modification operation.
+ */
+ static WorkSource sGoneWork;
+
+ /**
+ * Create an empty work source.
+ */
+ public WorkSource() {
+ mNum = 0;
+ }
+
+ /**
+ * Create a new WorkSource that is a copy of an existing one.
+ * If <var>orig</var> is null, an empty WorkSource is created.
+ */
+ public WorkSource(WorkSource orig) {
+ if (orig == null) {
+ mNum = 0;
+ return;
+ }
+ mNum = orig.mNum;
+ if (orig.mUids != null) {
+ mUids = orig.mUids.clone();
+ } else {
+ mUids = null;
+ }
+ }
+
+ /** @hide */
+ public WorkSource(int uid) {
+ mNum = 1;
+ mUids = new int[] { uid, 0 };
+ }
+
+ WorkSource(Parcel in) {
+ mNum = in.readInt();
+ mUids = in.createIntArray();
+ }
+
+ /** @hide */
+ public int size() {
+ return mNum;
+ }
+
+ /** @hide */
+ public int get(int index) {
+ return mUids[index];
+ }
+
+ /**
+ * Clear this WorkSource to be empty.
+ */
+ public void clear() {
+ mNum = 0;
+ }
+
+ /**
+ * Compare this WorkSource with another.
+ * @param other The WorkSource to compare against.
+ * @return If there is a difference, true is returned.
+ */
+ public boolean diff(WorkSource other) {
+ int N = mNum;
+ if (N != other.mNum) {
+ return true;
+ }
+ final int[] uids1 = mUids;
+ final int[] uids2 = other.mUids;
+ for (int i=0; i<N; i++) {
+ if (uids1[i] != uids2[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Replace the current contents of this work source with the given
+ * work source. If <var>other</var> is null, the current work source
+ * will be made empty.
+ */
+ public void set(WorkSource other) {
+ if (other == null) {
+ mNum = 0;
+ return;
+ }
+ mNum = other.mNum;
+ if (other.mUids != null) {
+ if (mUids != null && mUids.length >= mNum) {
+ System.arraycopy(other.mUids, 0, mUids, 0, mNum);
+ } else {
+ mUids = other.mUids.clone();
+ }
+ } else {
+ mUids = null;
+ }
+ }
+
+ /** @hide */
+ public void set(int uid) {
+ mNum = 1;
+ if (mUids == null) mUids = new int[2];
+ mUids[0] = uid;
+ }
+
+ /** @hide */
+ public WorkSource[] setReturningDiffs(WorkSource other) {
+ synchronized (sTmpWorkSource) {
+ sNewbWork = null;
+ sGoneWork = null;
+ updateLocked(other, true, true);
+ if (sNewbWork != null || sGoneWork != null) {
+ WorkSource[] diffs = new WorkSource[2];
+ diffs[0] = sNewbWork;
+ diffs[1] = sGoneWork;
+ return diffs;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Merge the contents of <var>other</var> WorkSource in to this one.
+ *
+ * @param other The other WorkSource whose contents are to be merged.
+ * @return Returns true if any new sources were added.
+ */
+ public boolean add(WorkSource other) {
+ synchronized (sTmpWorkSource) {
+ return updateLocked(other, false, false);
+ }
+ }
+
+ /** @hide */
+ public WorkSource addReturningNewbs(WorkSource other) {
+ synchronized (sTmpWorkSource) {
+ sNewbWork = null;
+ updateLocked(other, false, true);
+ return sNewbWork;
+ }
+ }
+
+ /** @hide */
+ public boolean add(int uid) {
+ synchronized (sTmpWorkSource) {
+ sTmpWorkSource.mUids[0] = uid;
+ return updateLocked(sTmpWorkSource, false, false);
+ }
+ }
+
+ /** @hide */
+ public WorkSource addReturningNewbs(int uid) {
+ synchronized (sTmpWorkSource) {
+ sNewbWork = null;
+ sTmpWorkSource.mUids[0] = uid;
+ updateLocked(sTmpWorkSource, false, true);
+ return sNewbWork;
+ }
+ }
+
+ public boolean remove(WorkSource other) {
+ int N1 = mNum;
+ final int[] uids1 = mUids;
+ final int N2 = other.mNum;
+ final int[] uids2 = other.mUids;
+ boolean changed = false;
+ int i1 = 0;
+ for (int i2=0; i2<N2 && i1<N1; i2++) {
+ if (uids2[i2] == uids1[i1]) {
+ N1--;
+ if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1-1, N1-i1);
+ }
+ while (i1 < N1 && uids2[i2] > uids1[i1]) {
+ i1++;
+ }
+ }
+
+ mNum = N1;
+
+ return changed;
+ }
+
+ private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
+ int N1 = mNum;
+ int[] uids1 = mUids;
+ final int N2 = other.mNum;
+ final int[] uids2 = other.mUids;
+ boolean changed = false;
+ int i1 = 0;
+ for (int i2=0; i2<N2; i2++) {
+ if (i1 >= N1 || uids2[i2] < uids1[i1]) {
+ // Need to insert a new uid.
+ changed = true;
+ if (uids1 == null) {
+ uids1 = new int[4];
+ uids1[0] = uids2[i2];
+ } else if (i1 >= uids1.length) {
+ int[] newuids = new int[(uids1.length*3)/2];
+ if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
+ if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
+ uids1 = newuids;
+ uids1[i1] = uids2[i2];
+ } else {
+ if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1);
+ uids1[i1] = uids2[i2];
+ }
+ if (returnNewbs) {
+ if (sNewbWork == null) {
+ sNewbWork = new WorkSource(uids2[i2]);
+ } else {
+ sNewbWork.addLocked(uids2[i2]);
+ }
+ }
+ N1++;
+ i1++;
+ } else {
+ if (!set) {
+ // Skip uids that already exist or are not in 'other'.
+ do {
+ i1++;
+ } while (i1 < N1 && uids2[i2] >= uids1[i1]);
+ } else {
+ // Remove any uids that don't exist in 'other'.
+ int start = i1;
+ while (i1 < N1 && uids2[i2] > uids1[i1]) {
+ if (sGoneWork == null) {
+ sGoneWork = new WorkSource(uids1[i1]);
+ } else {
+ sGoneWork.addLocked(uids1[i1]);
+ }
+ i1++;
+ }
+ if (start < i1) {
+ System.arraycopy(uids1, i1, uids1, start, i1-start);
+ N1 -= i1-start;
+ i1 = start;
+ }
+ // If there is a matching uid, skip it.
+ if (i1 < N1 && uids2[i1] == uids1[i1]) {
+ i1++;
+ }
+ }
+ }
+ }
+
+ mNum = N1;
+ mUids = uids1;
+
+ return changed;
+ }
+
+ private void addLocked(int uid) {
+ if (mUids == null) {
+ mUids = new int[4];
+ mUids[0] = uid;
+ mNum = 1;
+ return;
+ }
+ if (mNum >= mUids.length) {
+ int[] newuids = new int[(mNum*3)/2];
+ System.arraycopy(mUids, 0, newuids, 0, mNum);
+ mUids = newuids;
+ }
+
+ mUids[mNum] = uid;
+ mNum++;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mNum);
+ dest.writeIntArray(mUids);
+ }
+
+ public static final Parcelable.Creator<WorkSource> CREATOR
+ = new Parcelable.Creator<WorkSource>() {
+ public WorkSource createFromParcel(Parcel in) {
+ return new WorkSource(in);
+ }
+ public WorkSource[] newArray(int size) {
+ return new WorkSource[size];
+ }
+ };
+}
diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl
deleted file mode 100644
index 4862f80d82c6..000000000000
--- a/core/java/android/os/storage/IMountService.aidl
+++ /dev/null
@@ -1,155 +0,0 @@
-/* //device/java/android/android/os/IUsb.aidl
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-package android.os.storage;
-
-import android.os.storage.IMountServiceListener;
-import android.os.storage.IMountShutdownObserver;
-
-/** WARNING! Update IMountService.h and IMountService.cpp if you change this file.
- * In particular, the ordering of the methods below must match the
- * _TRANSACTION enum in IMountService.cpp
- * @hide - Applications should use android.os.storage.StorageManager to access
- * storage functions.
- */
-interface IMountService
-{
- /**
- * Registers an IMountServiceListener for receiving async
- * notifications.
- */
- void registerListener(IMountServiceListener listener);
-
- /**
- * Unregisters an IMountServiceListener
- */
- void unregisterListener(IMountServiceListener listener);
-
- /**
- * Returns true if a USB mass storage host is connected
- */
- boolean isUsbMassStorageConnected();
-
- /**
- * Enables / disables USB mass storage.
- * The caller should check actual status of enabling/disabling
- * USB mass storage via StorageEventListener.
- */
- void setUsbMassStorageEnabled(boolean enable);
-
- /**
- * Returns true if a USB mass storage host is enabled (media is shared)
- */
- boolean isUsbMassStorageEnabled();
-
- /**
- * Mount external storage at given mount point.
- * Returns an int consistent with MountServiceResultCode
- */
- int mountVolume(String mountPoint);
-
- /**
- * Safely unmount external storage at given mount point.
- * The unmount is an asynchronous operation. Applications
- * should register StorageEventListener for storage related
- * status changes.
- *
- */
- void unmountVolume(String mountPoint, boolean force);
-
- /**
- * Format external storage given a mount point.
- * Returns an int consistent with MountServiceResultCode
- */
- int formatVolume(String mountPoint);
-
- /**
- * Returns an array of pids with open files on
- * the specified path.
- */
- int[] getStorageUsers(String path);
-
- /**
- * Gets the state of a volume via its mountpoint.
- */
- String getVolumeState(String mountPoint);
-
- /*
- * Creates a secure container with the specified parameters.
- * Returns an int consistent with MountServiceResultCode
- */
- int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid);
-
- /*
- * Finalize a container which has just been created and populated.
- * After finalization, the container is immutable.
- * Returns an int consistent with MountServiceResultCode
- */
- int finalizeSecureContainer(String id);
-
- /*
- * Destroy a secure container, and free up all resources associated with it.
- * NOTE: Ensure all references are released prior to deleting.
- * Returns an int consistent with MountServiceResultCode
- */
- int destroySecureContainer(String id, boolean force);
-
- /*
- * Mount a secure container with the specified key and owner UID.
- * Returns an int consistent with MountServiceResultCode
- */
- int mountSecureContainer(String id, String key, int ownerUid);
-
- /*
- * Unount a secure container.
- * Returns an int consistent with MountServiceResultCode
- */
- int unmountSecureContainer(String id, boolean force);
-
- /*
- * Returns true if the specified container is mounted
- */
- boolean isSecureContainerMounted(String id);
-
- /*
- * Rename an unmounted secure container.
- * Returns an int consistent with MountServiceResultCode
- */
- int renameSecureContainer(String oldId, String newId);
-
- /*
- * Returns the filesystem path of a mounted secure container.
- */
- String getSecureContainerPath(String id);
-
- /**
- * Gets an Array of currently known secure container IDs
- */
- String[] getSecureContainerList();
-
- /**
- * Shuts down the MountService and gracefully unmounts all external media.
- * Invokes call back once the shutdown is complete.
- */
- void shutdown(IMountShutdownObserver observer);
-
- /**
- * Call into MountService by PackageManager to notify that its done
- * processing the media status update request.
- */
- void finishMediaUpdate();
-}
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
new file mode 100644
index 000000000000..60ea95c459f7
--- /dev/null
+++ b/core/java/android/os/storage/IMountService.java
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * WARNING! Update IMountService.h and IMountService.cpp if you change this
+ * file. In particular, the ordering of the methods below must match the
+ * _TRANSACTION enum in IMountService.cpp
+ *
+ * @hide - Applications should use android.os.storage.StorageManager to access
+ * storage functions.
+ */
+public interface IMountService extends IInterface {
+ /** Local-side IPC implementation stub class. */
+ public static abstract class Stub extends Binder implements IMountService {
+ private static class Proxy implements IMountService {
+ private IBinder mRemote;
+
+ Proxy(IBinder remote) {
+ mRemote = remote;
+ }
+
+ public IBinder asBinder() {
+ return mRemote;
+ }
+
+ public String getInterfaceDescriptor() {
+ return DESCRIPTOR;
+ }
+
+ /**
+ * Registers an IMountServiceListener for receiving async
+ * notifications.
+ */
+ public void registerListener(IMountServiceListener listener) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeStrongBinder((listener != null ? listener.asBinder() : null));
+ mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Unregisters an IMountServiceListener
+ */
+ public void unregisterListener(IMountServiceListener listener) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeStrongBinder((listener != null ? listener.asBinder() : null));
+ mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Returns true if a USB mass storage host is connected
+ */
+ public boolean isUsbMassStorageConnected() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ boolean _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_isUsbMassStorageConnected, _data, _reply, 0);
+ _reply.readException();
+ _result = 0 != _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Enables / disables USB mass storage. The caller should check
+ * actual status of enabling/disabling USB mass storage via
+ * StorageEventListener.
+ */
+ public void setUsbMassStorageEnabled(boolean enable) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt((enable ? 1 : 0));
+ mRemote.transact(Stub.TRANSACTION_setUsbMassStorageEnabled, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Returns true if a USB mass storage host is enabled (media is
+ * shared)
+ */
+ public boolean isUsbMassStorageEnabled() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ boolean _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_isUsbMassStorageEnabled, _data, _reply, 0);
+ _reply.readException();
+ _result = 0 != _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Mount external storage at given mount point. Returns an int
+ * consistent with MountServiceResultCode
+ */
+ public int mountVolume(String mountPoint) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(mountPoint);
+ mRemote.transact(Stub.TRANSACTION_mountVolume, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Safely unmount external storage at given mount point. The unmount
+ * is an asynchronous operation. Applications should register
+ * StorageEventListener for storage related status changes.
+ */
+ public void unmountVolume(String mountPoint, boolean force) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(mountPoint);
+ _data.writeInt((force ? 1 : 0));
+ mRemote.transact(Stub.TRANSACTION_unmountVolume, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Format external storage given a mount point. Returns an int
+ * consistent with MountServiceResultCode
+ */
+ public int formatVolume(String mountPoint) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(mountPoint);
+ mRemote.transact(Stub.TRANSACTION_formatVolume, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Returns an array of pids with open files on the specified path.
+ */
+ public int[] getStorageUsers(String path) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int[] _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(path);
+ mRemote.transact(Stub.TRANSACTION_getStorageUsers, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.createIntArray();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Gets the state of a volume via its mountpoint.
+ */
+ public String getVolumeState(String mountPoint) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(mountPoint);
+ mRemote.transact(Stub.TRANSACTION_getVolumeState, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readString();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Creates a secure container with the specified parameters. Returns
+ * an int consistent with MountServiceResultCode
+ */
+ public int createSecureContainer(String id, int sizeMb, String fstype, String key,
+ int ownerUid) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(id);
+ _data.writeInt(sizeMb);
+ _data.writeString(fstype);
+ _data.writeString(key);
+ _data.writeInt(ownerUid);
+ mRemote.transact(Stub.TRANSACTION_createSecureContainer, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Destroy a secure container, and free up all resources associated
+ * with it. NOTE: Ensure all references are released prior to
+ * deleting. Returns an int consistent with MountServiceResultCode
+ */
+ public int destroySecureContainer(String id, boolean force) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(id);
+ _data.writeInt((force ? 1 : 0));
+ mRemote.transact(Stub.TRANSACTION_destroySecureContainer, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Finalize a container which has just been created and populated.
+ * After finalization, the container is immutable. Returns an int
+ * consistent with MountServiceResultCode
+ */
+ public int finalizeSecureContainer(String id) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(id);
+ mRemote.transact(Stub.TRANSACTION_finalizeSecureContainer, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Mount a secure container with the specified key and owner UID.
+ * Returns an int consistent with MountServiceResultCode
+ */
+ public int mountSecureContainer(String id, String key, int ownerUid)
+ throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(id);
+ _data.writeString(key);
+ _data.writeInt(ownerUid);
+ mRemote.transact(Stub.TRANSACTION_mountSecureContainer, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Unount a secure container. Returns an int consistent with
+ * MountServiceResultCode
+ */
+ public int unmountSecureContainer(String id, boolean force) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(id);
+ _data.writeInt((force ? 1 : 0));
+ mRemote.transact(Stub.TRANSACTION_unmountSecureContainer, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Returns true if the specified container is mounted
+ */
+ public boolean isSecureContainerMounted(String id) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ boolean _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(id);
+ mRemote.transact(Stub.TRANSACTION_isSecureContainerMounted, _data, _reply, 0);
+ _reply.readException();
+ _result = 0 != _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Rename an unmounted secure container. Returns an int consistent
+ * with MountServiceResultCode
+ */
+ public int renameSecureContainer(String oldId, String newId) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ int _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(oldId);
+ _data.writeString(newId);
+ mRemote.transact(Stub.TRANSACTION_renameSecureContainer, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /*
+ * Returns the filesystem path of a mounted secure container.
+ */
+ public String getSecureContainerPath(String id) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(id);
+ mRemote.transact(Stub.TRANSACTION_getSecureContainerPath, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readString();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Gets an Array of currently known secure container IDs
+ */
+ public String[] getSecureContainerList() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String[] _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getSecureContainerList, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.createStringArray();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Shuts down the MountService and gracefully unmounts all external
+ * media. Invokes call back once the shutdown is complete.
+ */
+ public void shutdown(IMountShutdownObserver observer)
+ throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeStrongBinder((observer != null ? observer.asBinder() : null));
+ mRemote.transact(Stub.TRANSACTION_shutdown, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Call into MountService by PackageManager to notify that its done
+ * processing the media status update request.
+ */
+ public void finishMediaUpdate() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_finishMediaUpdate, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Mounts an Opaque Binary Blob (OBB) with the specified decryption
+ * key and only allows the calling process's UID access to the
+ * contents. MountService will call back to the supplied
+ * IObbActionListener to inform it of the terminal state of the
+ * call.
+ */
+ public void mountObb(String filename, String key, IObbActionListener token)
+ throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(filename);
+ _data.writeString(key);
+ _data.writeStrongBinder((token != null ? token.asBinder() : null));
+ mRemote.transact(Stub.TRANSACTION_mountObb, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Unmounts an Opaque Binary Blob (OBB). When the force flag is
+ * specified, any program using it will be forcibly killed to
+ * unmount the image. MountService will call back to the supplied
+ * IObbActionListener to inform it of the terminal state of the
+ * call.
+ */
+ public void unmountObb(String filename, boolean force, IObbActionListener token)
+ throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(filename);
+ _data.writeInt((force ? 1 : 0));
+ _data.writeStrongBinder((token != null ? token.asBinder() : null));
+ mRemote.transact(Stub.TRANSACTION_unmountObb, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Checks whether the specified Opaque Binary Blob (OBB) is mounted
+ * somewhere.
+ */
+ public boolean isObbMounted(String filename) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ boolean _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(filename);
+ mRemote.transact(Stub.TRANSACTION_isObbMounted, _data, _reply, 0);
+ _reply.readException();
+ _result = 0 != _reply.readInt();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ /**
+ * Gets the path to the mounted Opaque Binary Blob (OBB).
+ */
+ public String getMountedObbPath(String filename) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ String _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(filename);
+ mRemote.transact(Stub.TRANSACTION_getMountedObbPath, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.readString();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+ }
+
+ private static final String DESCRIPTOR = "IMountService";
+
+ static final int TRANSACTION_registerListener = IBinder.FIRST_CALL_TRANSACTION + 0;
+
+ static final int TRANSACTION_unregisterListener = IBinder.FIRST_CALL_TRANSACTION + 1;
+
+ static final int TRANSACTION_isUsbMassStorageConnected = IBinder.FIRST_CALL_TRANSACTION + 2;
+
+ static final int TRANSACTION_setUsbMassStorageEnabled = IBinder.FIRST_CALL_TRANSACTION + 3;
+
+ static final int TRANSACTION_isUsbMassStorageEnabled = IBinder.FIRST_CALL_TRANSACTION + 4;
+
+ static final int TRANSACTION_mountVolume = IBinder.FIRST_CALL_TRANSACTION + 5;
+
+ static final int TRANSACTION_unmountVolume = IBinder.FIRST_CALL_TRANSACTION + 6;
+
+ static final int TRANSACTION_formatVolume = IBinder.FIRST_CALL_TRANSACTION + 7;
+
+ static final int TRANSACTION_getStorageUsers = IBinder.FIRST_CALL_TRANSACTION + 8;
+
+ static final int TRANSACTION_getVolumeState = IBinder.FIRST_CALL_TRANSACTION + 9;
+
+ static final int TRANSACTION_createSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 10;
+
+ static final int TRANSACTION_finalizeSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 11;
+
+ static final int TRANSACTION_destroySecureContainer = IBinder.FIRST_CALL_TRANSACTION + 12;
+
+ static final int TRANSACTION_mountSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 13;
+
+ static final int TRANSACTION_unmountSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 14;
+
+ static final int TRANSACTION_isSecureContainerMounted = IBinder.FIRST_CALL_TRANSACTION + 15;
+
+ static final int TRANSACTION_renameSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 16;
+
+ static final int TRANSACTION_getSecureContainerPath = IBinder.FIRST_CALL_TRANSACTION + 17;
+
+ static final int TRANSACTION_getSecureContainerList = IBinder.FIRST_CALL_TRANSACTION + 18;
+
+ static final int TRANSACTION_shutdown = IBinder.FIRST_CALL_TRANSACTION + 19;
+
+ static final int TRANSACTION_finishMediaUpdate = IBinder.FIRST_CALL_TRANSACTION + 20;
+
+ static final int TRANSACTION_mountObb = IBinder.FIRST_CALL_TRANSACTION + 21;
+
+ static final int TRANSACTION_unmountObb = IBinder.FIRST_CALL_TRANSACTION + 22;
+
+ static final int TRANSACTION_isObbMounted = IBinder.FIRST_CALL_TRANSACTION + 23;
+
+ static final int TRANSACTION_getMountedObbPath = IBinder.FIRST_CALL_TRANSACTION + 24;
+
+ /**
+ * Cast an IBinder object into an IMountService interface, generating a
+ * proxy if needed.
+ */
+ public static IMountService asInterface(IBinder obj) {
+ if (obj == null) {
+ return null;
+ }
+ IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+ if (iin != null && iin instanceof IMountService) {
+ return (IMountService) iin;
+ }
+ return new IMountService.Stub.Proxy(obj);
+ }
+
+ /** Construct the stub at attach it to the interface. */
+ public Stub() {
+ attachInterface(this, DESCRIPTOR);
+ }
+
+ public IBinder asBinder() {
+ return this;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply,
+ int flags) throws RemoteException {
+ switch (code) {
+ case INTERFACE_TRANSACTION: {
+ reply.writeString(DESCRIPTOR);
+ return true;
+ }
+ case TRANSACTION_registerListener: {
+ data.enforceInterface(DESCRIPTOR);
+ IMountServiceListener listener;
+ listener = IMountServiceListener.Stub.asInterface(data.readStrongBinder());
+ registerListener(listener);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_unregisterListener: {
+ data.enforceInterface(DESCRIPTOR);
+ IMountServiceListener listener;
+ listener = IMountServiceListener.Stub.asInterface(data.readStrongBinder());
+ unregisterListener(listener);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_isUsbMassStorageConnected: {
+ data.enforceInterface(DESCRIPTOR);
+ boolean result = isUsbMassStorageConnected();
+ reply.writeNoException();
+ reply.writeInt((result ? 1 : 0));
+ return true;
+ }
+ case TRANSACTION_setUsbMassStorageEnabled: {
+ data.enforceInterface(DESCRIPTOR);
+ boolean enable;
+ enable = 0 != data.readInt();
+ setUsbMassStorageEnabled(enable);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_isUsbMassStorageEnabled: {
+ data.enforceInterface(DESCRIPTOR);
+ boolean result = isUsbMassStorageEnabled();
+ reply.writeNoException();
+ reply.writeInt((result ? 1 : 0));
+ return true;
+ }
+ case TRANSACTION_mountVolume: {
+ data.enforceInterface(DESCRIPTOR);
+ String mountPoint;
+ mountPoint = data.readString();
+ int resultCode = mountVolume(mountPoint);
+ reply.writeNoException();
+ reply.writeInt(resultCode);
+ return true;
+ }
+ case TRANSACTION_unmountVolume: {
+ data.enforceInterface(DESCRIPTOR);
+ String mountPoint;
+ mountPoint = data.readString();
+ boolean force;
+ force = 0 != data.readInt();
+ unmountVolume(mountPoint, force);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_formatVolume: {
+ data.enforceInterface(DESCRIPTOR);
+ String mountPoint;
+ mountPoint = data.readString();
+ int result = formatVolume(mountPoint);
+ reply.writeNoException();
+ reply.writeInt(result);
+ return true;
+ }
+ case TRANSACTION_getStorageUsers: {
+ data.enforceInterface(DESCRIPTOR);
+ String path;
+ path = data.readString();
+ int[] pids = getStorageUsers(path);
+ reply.writeNoException();
+ reply.writeIntArray(pids);
+ return true;
+ }
+ case TRANSACTION_getVolumeState: {
+ data.enforceInterface(DESCRIPTOR);
+ String mountPoint;
+ mountPoint = data.readString();
+ String state = getVolumeState(mountPoint);
+ reply.writeNoException();
+ reply.writeString(state);
+ return true;
+ }
+ case TRANSACTION_createSecureContainer: {
+ data.enforceInterface(DESCRIPTOR);
+ String id;
+ id = data.readString();
+ int sizeMb;
+ sizeMb = data.readInt();
+ String fstype;
+ fstype = data.readString();
+ String key;
+ key = data.readString();
+ int ownerUid;
+ ownerUid = data.readInt();
+ int resultCode = createSecureContainer(id, sizeMb, fstype, key, ownerUid);
+ reply.writeNoException();
+ reply.writeInt(resultCode);
+ return true;
+ }
+ case TRANSACTION_finalizeSecureContainer: {
+ data.enforceInterface(DESCRIPTOR);
+ String id;
+ id = data.readString();
+ int resultCode = finalizeSecureContainer(id);
+ reply.writeNoException();
+ reply.writeInt(resultCode);
+ return true;
+ }
+ case TRANSACTION_destroySecureContainer: {
+ data.enforceInterface(DESCRIPTOR);
+ String id;
+ id = data.readString();
+ boolean force;
+ force = 0 != data.readInt();
+ int resultCode = destroySecureContainer(id, force);
+ reply.writeNoException();
+ reply.writeInt(resultCode);
+ return true;
+ }
+ case TRANSACTION_mountSecureContainer: {
+ data.enforceInterface(DESCRIPTOR);
+ String id;
+ id = data.readString();
+ String key;
+ key = data.readString();
+ int ownerUid;
+ ownerUid = data.readInt();
+ int resultCode = mountSecureContainer(id, key, ownerUid);
+ reply.writeNoException();
+ reply.writeInt(resultCode);
+ return true;
+ }
+ case TRANSACTION_unmountSecureContainer: {
+ data.enforceInterface(DESCRIPTOR);
+ String id;
+ id = data.readString();
+ boolean force;
+ force = 0 != data.readInt();
+ int resultCode = unmountSecureContainer(id, force);
+ reply.writeNoException();
+ reply.writeInt(resultCode);
+ return true;
+ }
+ case TRANSACTION_isSecureContainerMounted: {
+ data.enforceInterface(DESCRIPTOR);
+ String id;
+ id = data.readString();
+ boolean status = isSecureContainerMounted(id);
+ reply.writeNoException();
+ reply.writeInt((status ? 1 : 0));
+ return true;
+ }
+ case TRANSACTION_renameSecureContainer: {
+ data.enforceInterface(DESCRIPTOR);
+ String oldId;
+ oldId = data.readString();
+ String newId;
+ newId = data.readString();
+ int resultCode = renameSecureContainer(oldId, newId);
+ reply.writeNoException();
+ reply.writeInt(resultCode);
+ return true;
+ }
+ case TRANSACTION_getSecureContainerPath: {
+ data.enforceInterface(DESCRIPTOR);
+ String id;
+ id = data.readString();
+ String path = getSecureContainerPath(id);
+ reply.writeNoException();
+ reply.writeString(path);
+ return true;
+ }
+ case TRANSACTION_getSecureContainerList: {
+ data.enforceInterface(DESCRIPTOR);
+ String[] ids = getSecureContainerList();
+ reply.writeNoException();
+ reply.writeStringArray(ids);
+ return true;
+ }
+ case TRANSACTION_shutdown: {
+ data.enforceInterface(DESCRIPTOR);
+ IMountShutdownObserver observer;
+ observer = IMountShutdownObserver.Stub.asInterface(data
+ .readStrongBinder());
+ shutdown(observer);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_finishMediaUpdate: {
+ data.enforceInterface(DESCRIPTOR);
+ finishMediaUpdate();
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_mountObb: {
+ data.enforceInterface(DESCRIPTOR);
+ String filename;
+ filename = data.readString();
+ String key;
+ key = data.readString();
+ IObbActionListener observer;
+ observer = IObbActionListener.Stub.asInterface(data.readStrongBinder());
+ mountObb(filename, key, observer);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_unmountObb: {
+ data.enforceInterface(DESCRIPTOR);
+ String filename;
+ filename = data.readString();
+ boolean force;
+ force = 0 != data.readInt();
+ IObbActionListener observer;
+ observer = IObbActionListener.Stub.asInterface(data.readStrongBinder());
+ unmountObb(filename, force, observer);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_isObbMounted: {
+ data.enforceInterface(DESCRIPTOR);
+ String filename;
+ filename = data.readString();
+ boolean status = isObbMounted(filename);
+ reply.writeNoException();
+ reply.writeInt((status ? 1 : 0));
+ return true;
+ }
+ case TRANSACTION_getMountedObbPath: {
+ data.enforceInterface(DESCRIPTOR);
+ String filename;
+ filename = data.readString();
+ String mountedPath = getMountedObbPath(filename);
+ reply.writeNoException();
+ reply.writeString(mountedPath);
+ return true;
+ }
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+ }
+
+ /*
+ * Creates a secure container with the specified parameters. Returns an int
+ * consistent with MountServiceResultCode
+ */
+ public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid)
+ throws RemoteException;
+
+ /*
+ * Destroy a secure container, and free up all resources associated with it.
+ * NOTE: Ensure all references are released prior to deleting. Returns an
+ * int consistent with MountServiceResultCode
+ */
+ public int destroySecureContainer(String id, boolean force) throws RemoteException;
+
+ /*
+ * Finalize a container which has just been created and populated. After
+ * finalization, the container is immutable. Returns an int consistent with
+ * MountServiceResultCode
+ */
+ public int finalizeSecureContainer(String id) throws RemoteException;
+
+ /**
+ * Call into MountService by PackageManager to notify that its done
+ * processing the media status update request.
+ */
+ public void finishMediaUpdate() throws RemoteException;
+
+ /**
+ * Format external storage given a mount point. Returns an int consistent
+ * with MountServiceResultCode
+ */
+ public int formatVolume(String mountPoint) throws RemoteException;
+
+ /**
+ * Gets the path to the mounted Opaque Binary Blob (OBB).
+ */
+ public String getMountedObbPath(String filename) throws RemoteException;
+
+ /**
+ * Gets an Array of currently known secure container IDs
+ */
+ public String[] getSecureContainerList() throws RemoteException;
+
+ /*
+ * Returns the filesystem path of a mounted secure container.
+ */
+ public String getSecureContainerPath(String id) throws RemoteException;
+
+ /**
+ * Returns an array of pids with open files on the specified path.
+ */
+ public int[] getStorageUsers(String path) throws RemoteException;
+
+ /**
+ * Gets the state of a volume via its mountpoint.
+ */
+ public String getVolumeState(String mountPoint) throws RemoteException;
+
+ /**
+ * Checks whether the specified Opaque Binary Blob (OBB) is mounted
+ * somewhere.
+ */
+ public boolean isObbMounted(String filename) throws RemoteException;
+
+ /*
+ * Returns true if the specified container is mounted
+ */
+ public boolean isSecureContainerMounted(String id) throws RemoteException;
+
+ /**
+ * Returns true if a USB mass storage host is connected
+ */
+ public boolean isUsbMassStorageConnected() throws RemoteException;
+
+ /**
+ * Returns true if a USB mass storage host is enabled (media is shared)
+ */
+ public boolean isUsbMassStorageEnabled() throws RemoteException;
+
+ /**
+ * Mounts an Opaque Binary Blob (OBB) with the specified decryption key and
+ * only allows the calling process's UID access to the contents.
+ * MountService will call back to the supplied IObbActionListener to inform
+ * it of the terminal state of the call.
+ */
+ public void mountObb(String filename, String key, IObbActionListener token)
+ throws RemoteException;
+
+ /*
+ * Mount a secure container with the specified key and owner UID. Returns an
+ * int consistent with MountServiceResultCode
+ */
+ public int mountSecureContainer(String id, String key, int ownerUid) throws RemoteException;
+
+ /**
+ * Mount external storage at given mount point. Returns an int consistent
+ * with MountServiceResultCode
+ */
+ public int mountVolume(String mountPoint) throws RemoteException;
+
+ /**
+ * Registers an IMountServiceListener for receiving async notifications.
+ */
+ public void registerListener(IMountServiceListener listener) throws RemoteException;
+
+ /*
+ * Rename an unmounted secure container. Returns an int consistent with
+ * MountServiceResultCode
+ */
+ public int renameSecureContainer(String oldId, String newId) throws RemoteException;
+
+ /**
+ * Enables / disables USB mass storage. The caller should check actual
+ * status of enabling/disabling USB mass storage via StorageEventListener.
+ */
+ public void setUsbMassStorageEnabled(boolean enable) throws RemoteException;
+
+ /**
+ * Shuts down the MountService and gracefully unmounts all external media.
+ * Invokes call back once the shutdown is complete.
+ */
+ public void shutdown(IMountShutdownObserver observer) throws RemoteException;
+
+ /**
+ * Unmounts an Opaque Binary Blob (OBB). When the force flag is specified,
+ * any program using it will be forcibly killed to unmount the image.
+ * MountService will call back to the supplied IObbActionListener to inform
+ * it of the terminal state of the call.
+ */
+ public void unmountObb(String filename, boolean force, IObbActionListener token)
+ throws RemoteException;
+
+ /*
+ * Unount a secure container. Returns an int consistent with
+ * MountServiceResultCode
+ */
+ public int unmountSecureContainer(String id, boolean force) throws RemoteException;
+
+ /**
+ * Safely unmount external storage at given mount point. The unmount is an
+ * asynchronous operation. Applications should register StorageEventListener
+ * for storage related status changes.
+ */
+ public void unmountVolume(String mountPoint, boolean force) throws RemoteException;
+
+ /**
+ * Unregisters an IMountServiceListener
+ */
+ public void unregisterListener(IMountServiceListener listener) throws RemoteException;
+}
diff --git a/core/java/android/os/storage/IMountServiceListener.aidl b/core/java/android/os/storage/IMountServiceListener.aidl
deleted file mode 100644
index 883413ad103e..000000000000
--- a/core/java/android/os/storage/IMountServiceListener.aidl
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os.storage;
-
-/**
- * Callback class for receiving events from MountService.
- *
- * @hide - Applications should use android.os.storage.IStorageEventListener
- * for storage event callbacks.
- */
-interface IMountServiceListener {
- /**
- * Detection state of USB Mass Storage has changed
- *
- * @param available true if a UMS host is connected.
- */
- void onUsbMassStorageConnectionChanged(boolean connected);
-
- /**
- * Storage state has changed.
- *
- * @param path The volume mount path.
- * @param oldState The old state of the volume.
- * @param newState The new state of the volume.
- *
- * Note: State is one of the values returned by Environment.getExternalStorageState()
- */
- void onStorageStateChanged(String path, String oldState, String newState);
-}
diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java
new file mode 100644
index 000000000000..d5c5fa579802
--- /dev/null
+++ b/core/java/android/os/storage/IMountServiceListener.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * Callback class for receiving events from MountService.
+ *
+ * @hide - Applications should use IStorageEventListener for storage event
+ * callbacks.
+ */
+public interface IMountServiceListener extends IInterface {
+ /** Local-side IPC implementation stub class. */
+ public static abstract class Stub extends Binder implements IMountServiceListener {
+ private static final String DESCRIPTOR = "IMountServiceListener";
+
+ /** Construct the stub at attach it to the interface. */
+ public Stub() {
+ this.attachInterface(this, DESCRIPTOR);
+ }
+
+ /**
+ * Cast an IBinder object into an IMountServiceListener interface,
+ * generating a proxy if needed.
+ */
+ public static IMountServiceListener asInterface(IBinder obj) {
+ if ((obj == null)) {
+ return null;
+ }
+ IInterface iin = (IInterface) obj.queryLocalInterface(DESCRIPTOR);
+ if (((iin != null) && (iin instanceof IMountServiceListener))) {
+ return ((IMountServiceListener) iin);
+ }
+ return new IMountServiceListener.Stub.Proxy(obj);
+ }
+
+ public IBinder asBinder() {
+ return this;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ switch (code) {
+ case INTERFACE_TRANSACTION: {
+ reply.writeString(DESCRIPTOR);
+ return true;
+ }
+ case TRANSACTION_onUsbMassStorageConnectionChanged: {
+ data.enforceInterface(DESCRIPTOR);
+ boolean connected;
+ connected = (0 != data.readInt());
+ this.onUsbMassStorageConnectionChanged(connected);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_onStorageStateChanged: {
+ data.enforceInterface(DESCRIPTOR);
+ String path;
+ path = data.readString();
+ String oldState;
+ oldState = data.readString();
+ String newState;
+ newState = data.readString();
+ this.onStorageStateChanged(path, oldState, newState);
+ reply.writeNoException();
+ return true;
+ }
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ private static class Proxy implements IMountServiceListener {
+ private IBinder mRemote;
+
+ Proxy(IBinder remote) {
+ mRemote = remote;
+ }
+
+ public IBinder asBinder() {
+ return mRemote;
+ }
+
+ public String getInterfaceDescriptor() {
+ return DESCRIPTOR;
+ }
+
+ /**
+ * Detection state of USB Mass Storage has changed
+ *
+ * @param available true if a UMS host is connected.
+ */
+ public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt(((connected) ? (1) : (0)));
+ mRemote.transact(Stub.TRANSACTION_onUsbMassStorageConnectionChanged, _data,
+ _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ /**
+ * Storage state has changed.
+ *
+ * @param path The volume mount path.
+ * @param oldState The old state of the volume.
+ * @param newState The new state of the volume. Note: State is one
+ * of the values returned by
+ * Environment.getExternalStorageState()
+ */
+ public void onStorageStateChanged(String path, String oldState, String newState)
+ throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(path);
+ _data.writeString(oldState);
+ _data.writeString(newState);
+ mRemote.transact(Stub.TRANSACTION_onStorageStateChanged, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+ }
+
+ static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0);
+
+ static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1);
+ }
+
+ /**
+ * Detection state of USB Mass Storage has changed
+ *
+ * @param available true if a UMS host is connected.
+ */
+ public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException;
+
+ /**
+ * Storage state has changed.
+ *
+ * @param path The volume mount path.
+ * @param oldState The old state of the volume.
+ * @param newState The new state of the volume. Note: State is one of the
+ * values returned by Environment.getExternalStorageState()
+ */
+ public void onStorageStateChanged(String path, String oldState, String newState)
+ throws RemoteException;
+}
diff --git a/core/java/android/os/storage/IMountShutdownObserver.java b/core/java/android/os/storage/IMountShutdownObserver.java
new file mode 100644
index 000000000000..d946e1a7cba5
--- /dev/null
+++ b/core/java/android/os/storage/IMountShutdownObserver.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * Callback class for receiving events related to shutdown.
+ *
+ * @hide - For internal consumption only.
+ */
+public interface IMountShutdownObserver extends IInterface {
+ /** Local-side IPC implementation stub class. */
+ public static abstract class Stub extends Binder implements IMountShutdownObserver {
+ private static final java.lang.String DESCRIPTOR = "IMountShutdownObserver";
+
+ /** Construct the stub at attach it to the interface. */
+ public Stub() {
+ this.attachInterface(this, DESCRIPTOR);
+ }
+
+ /**
+ * Cast an IBinder object into an IMountShutdownObserver interface,
+ * generating a proxy if needed.
+ */
+ public static IMountShutdownObserver asInterface(IBinder obj) {
+ if ((obj == null)) {
+ return null;
+ }
+ IInterface iin = (IInterface) obj.queryLocalInterface(DESCRIPTOR);
+ if (((iin != null) && (iin instanceof IMountShutdownObserver))) {
+ return ((IMountShutdownObserver) iin);
+ }
+ return new IMountShutdownObserver.Stub.Proxy(obj);
+ }
+
+ public IBinder asBinder() {
+ return this;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ switch (code) {
+ case INTERFACE_TRANSACTION: {
+ reply.writeString(DESCRIPTOR);
+ return true;
+ }
+ case TRANSACTION_onShutDownComplete: {
+ data.enforceInterface(DESCRIPTOR);
+ int statusCode;
+ statusCode = data.readInt();
+ this.onShutDownComplete(statusCode);
+ reply.writeNoException();
+ return true;
+ }
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ private static class Proxy implements IMountShutdownObserver {
+ private IBinder mRemote;
+
+ Proxy(IBinder remote) {
+ mRemote = remote;
+ }
+
+ public IBinder asBinder() {
+ return mRemote;
+ }
+
+ public java.lang.String getInterfaceDescriptor() {
+ return DESCRIPTOR;
+ }
+
+ /**
+ * This method is called when the shutdown of MountService
+ * completed.
+ *
+ * @param statusCode indicates success or failure of the shutdown.
+ */
+ public void onShutDownComplete(int statusCode) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt(statusCode);
+ mRemote.transact(Stub.TRANSACTION_onShutDownComplete, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+ }
+
+ static final int TRANSACTION_onShutDownComplete = (IBinder.FIRST_CALL_TRANSACTION + 0);
+ }
+
+ /**
+ * This method is called when the shutdown of MountService completed.
+ *
+ * @param statusCode indicates success or failure of the shutdown.
+ */
+ public void onShutDownComplete(int statusCode) throws RemoteException;
+}
diff --git a/core/java/android/os/storage/IObbActionListener.java b/core/java/android/os/storage/IObbActionListener.java
new file mode 100644
index 000000000000..2c098ac6c911
--- /dev/null
+++ b/core/java/android/os/storage/IObbActionListener.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * Callback class for receiving events from MountService about Opaque Binary
+ * Blobs (OBBs).
+ *
+ * @hide - Applications should use StorageManager to interact with OBBs.
+ */
+public interface IObbActionListener extends IInterface {
+ /** Local-side IPC implementation stub class. */
+ public static abstract class Stub extends Binder implements IObbActionListener {
+ private static final String DESCRIPTOR = "IObbActionListener";
+
+ /** Construct the stub at attach it to the interface. */
+ public Stub() {
+ this.attachInterface(this, DESCRIPTOR);
+ }
+
+ /**
+ * Cast an IBinder object into an IObbActionListener interface,
+ * generating a proxy if needed.
+ */
+ public static IObbActionListener asInterface(IBinder obj) {
+ if ((obj == null)) {
+ return null;
+ }
+ IInterface iin = (IInterface) obj.queryLocalInterface(DESCRIPTOR);
+ if (((iin != null) && (iin instanceof IObbActionListener))) {
+ return ((IObbActionListener) iin);
+ }
+ return new IObbActionListener.Stub.Proxy(obj);
+ }
+
+ public IBinder asBinder() {
+ return this;
+ }
+
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ switch (code) {
+ case INTERFACE_TRANSACTION: {
+ reply.writeString(DESCRIPTOR);
+ return true;
+ }
+ case TRANSACTION_onObbResult: {
+ data.enforceInterface(DESCRIPTOR);
+ String filename;
+ filename = data.readString();
+ String status;
+ status = data.readString();
+ this.onObbResult(filename, status);
+ reply.writeNoException();
+ return true;
+ }
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+
+ private static class Proxy implements IObbActionListener {
+ private IBinder mRemote;
+
+ Proxy(IBinder remote) {
+ mRemote = remote;
+ }
+
+ public IBinder asBinder() {
+ return mRemote;
+ }
+
+ public String getInterfaceDescriptor() {
+ return DESCRIPTOR;
+ }
+
+ /**
+ * Return from an OBB action result.
+ *
+ * @param filename the path to the OBB the operation was performed
+ * on
+ * @param returnCode status of the operation
+ */
+ public void onObbResult(String filename, String status) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(filename);
+ _data.writeString(status);
+ mRemote.transact(Stub.TRANSACTION_onObbResult, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+ }
+
+ static final int TRANSACTION_onObbResult = (IBinder.FIRST_CALL_TRANSACTION + 0);
+ }
+
+ /**
+ * Return from an OBB action result.
+ *
+ * @param filename the path to the OBB the operation was performed on
+ * @param returnCode status of the operation
+ */
+ public void onObbResult(String filename, String status) throws RemoteException;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index a12603ce42ee..df0b69c9fe24 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,37 +16,20 @@
package android.os.storage;
-import android.content.Context;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.Parcelable;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.storage.IMountService;
-import android.os.storage.IMountServiceListener;
import android.util.Log;
-import android.util.SparseArray;
-import java.io.FileDescriptor;
-import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
/**
* StorageManager is the interface to the systems storage service.
* Get an instance of this class by calling
* {@link android.content.Context#getSystemService(java.lang.String)} with an argument
* of {@link android.content.Context#STORAGE_SERVICE}.
- *
- * @hide
- *
*/
public class StorageManager
@@ -90,6 +73,17 @@ public class StorageManager
}
/**
+ * Binder listener for OBB action results.
+ */
+ private final ObbActionBinderListener mObbActionListener = new ObbActionBinderListener();
+ private class ObbActionBinderListener extends IObbActionListener.Stub {
+ @Override
+ public void onObbResult(String filename, String status) throws RemoteException {
+ Log.i(TAG, "filename = " + filename + ", result = " + status);
+ }
+ }
+
+ /**
* Private base class for messages sent between the callback thread
* and the target looper handler.
*/
@@ -209,6 +203,7 @@ public class StorageManager
*
* @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
*
+ * @hide
*/
public void registerListener(StorageEventListener listener) {
if (listener == null) {
@@ -225,6 +220,7 @@ public class StorageManager
*
* @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object.
*
+ * @hide
*/
public void unregisterListener(StorageEventListener listener) {
if (listener == null) {
@@ -245,6 +241,8 @@ public class StorageManager
/**
* Enables USB Mass Storage (UMS) on the device.
+ *
+ * @hide
*/
public void enableUsbMassStorage() {
try {
@@ -256,6 +254,8 @@ public class StorageManager
/**
* Disables USB Mass Storage (UMS) on the device.
+ *
+ * @hide
*/
public void disableUsbMassStorage() {
try {
@@ -268,6 +268,8 @@ public class StorageManager
/**
* Query if a USB Mass Storage (UMS) host is connected.
* @return true if UMS host is connected.
+ *
+ * @hide
*/
public boolean isUsbMassStorageConnected() {
try {
@@ -281,6 +283,8 @@ public class StorageManager
/**
* Query if a USB Mass Storage (UMS) is enabled on the device.
* @return true if UMS host is enabled.
+ *
+ * @hide
*/
public boolean isUsbMassStorageEnabled() {
try {
@@ -290,4 +294,96 @@ public class StorageManager
}
return false;
}
+
+ /**
+ * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is
+ * specified, it is supplied to the mounting process to be used in any
+ * encryption used in the OBB.
+ * <p>
+ * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
+ * file matches a package ID that is owned by the calling program's UID.
+ * That is, shared UID applications can obtain access to any other
+ * application's OBB that shares its UID.
+ * <p>
+ * STOPSHIP document more; discuss lack of guarantees of security
+ *
+ * @param filename the path to the OBB file
+ * @param key decryption key
+ * @return whether the mount call was successfully queued or not
+ */
+ public boolean mountObb(String filename, String key) {
+ try {
+ mMountService.mountObb(filename, key, mObbActionListener);
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to mount OBB", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Unmount an Opaque Binary Blob (OBB) file. If the <code>force</code> flag
+ * is true, it will kill any application needed to unmount the given OBB.
+ * <p>
+ * <em>Note:</em> you can only mount OBB files for which the OBB tag on the
+ * file matches a package ID that is owned by the calling program's UID.
+ * That is, shared UID applications can obtain access to any other
+ * application's OBB that shares its UID.
+ * <p>
+ * STOPSHIP document more; discuss lack of guarantees of security
+ *
+ * @param filename path to the OBB file
+ * @param force whether to kill any programs using this in order to unmount
+ * it
+ * @return whether the unmount call was successfully queued or not
+ * @throws IllegalArgumentException when OBB is not already mounted
+ */
+ public boolean unmountObb(String filename, boolean force) throws IllegalArgumentException {
+ try {
+ mMountService.unmountObb(filename, force, mObbActionListener);
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to mount OBB", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Check whether an Opaque Binary Blob (OBB) is mounted or not.
+ *
+ * @param filename path to OBB image
+ * @return true if OBB is mounted; false if not mounted or on error
+ */
+ public boolean isObbMounted(String filename) throws IllegalArgumentException {
+ try {
+ return mMountService.isObbMounted(filename);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to check if OBB is mounted", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Check the mounted path of an Opaque Binary Blob (OBB) file. This will
+ * give you the path to where you can obtain access to the internals of the
+ * OBB.
+ *
+ * @param filename path to OBB image
+ * @return absolute path to mounted OBB image data or <code>null</code> if
+ * not mounted or exception encountered trying to read status
+ */
+ public String getMountedObbPath(String filename) {
+ try {
+ return mMountService.getMountedObbPath(filename);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to find mounted path for OBB", e);
+ } catch (IllegalArgumentException e) {
+ Log.d(TAG, "Couldn't read OBB file", e);
+ }
+
+ return null;
+ }
}
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
index 875c29ef1b11..dcfe980bfcaf 100644
--- a/core/java/android/pim/vcard/JapaneseUtils.java
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -27,7 +27,6 @@ import java.util.Map;
new HashMap<Character, String>();
static {
- // There's no logical mapping rule in Unicode. Sigh.
sHalfWidthMap.put('\u3001', "\uFF64");
sHalfWidthMap.put('\u3002', "\uFF61");
sHalfWidthMap.put('\u300C', "\uFF62");
@@ -366,11 +365,11 @@ import java.util.Map;
}
/**
- * Return half-width version of that character if possible. Return null if not possible
+ * Returns half-width version of that character if possible. Returns null if not possible
* @param ch input character
* @return CharSequence object if the mapping for ch exists. Return null otherwise.
*/
- public static String tryGetHalfWidthText(char ch) {
+ public static String tryGetHalfWidthText(final char ch) {
if (sHalfWidthMap.containsKey(ch)) {
return sHalfWidthMap.get(ch);
} else {
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index 0a6415dd200c..b2007f287836 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -30,11 +30,10 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Base64;
import android.util.CharsetUtils;
import android.util.Log;
-import org.apache.commons.codec.binary.Base64;
-
import java.io.UnsupportedEncodingException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
@@ -47,7 +46,23 @@ import java.util.Map;
import java.util.Set;
/**
- * The class which lets users create their own vCard String.
+ * <p>
+ * The class which lets users create their own vCard String. Typical usage is as follows:
+ * </p>
+ * <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType);
+ * builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ * .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ * .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ * .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ * .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ * .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ * .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
+ * .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
+ * .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ * .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ * .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ * .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ * return builder.toString();</pre>
*/
public class VCardBuilder {
private static final String LOG_TAG = "VCardBuilder";
@@ -75,81 +90,129 @@ public class VCardBuilder {
private static final String VCARD_WS = " ";
private static final String VCARD_PARAM_EQUAL = "=";
- private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-
- private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+ private static final String VCARD_PARAM_ENCODING_QP =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
+ private static final String VCARD_PARAM_ENCODING_BASE64_AS_B =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_B;
private static final String SHIFT_JIS = "SHIFT_JIS";
- private static final String UTF_8 = "UTF-8";
private final int mVCardType;
- private final boolean mIsV30;
+ private final boolean mIsV30OrV40;
private final boolean mIsJapaneseMobilePhone;
private final boolean mOnlyOneNoteFieldIsAvailable;
private final boolean mIsDoCoMo;
private final boolean mShouldUseQuotedPrintable;
private final boolean mUsesAndroidProperty;
private final boolean mUsesDefactProperty;
- private final boolean mUsesUtf8;
- private final boolean mUsesShiftJis;
private final boolean mAppendTypeParamName;
private final boolean mRefrainsQPToNameProperties;
private final boolean mNeedsToConvertPhoneticString;
private final boolean mShouldAppendCharsetParam;
- private final String mCharsetString;
+ private final String mCharset;
private final String mVCardCharsetParameter;
private StringBuilder mBuilder;
private boolean mEndAppended;
public VCardBuilder(final int vcardType) {
+ // Default charset should be used
+ this(vcardType, null);
+ }
+
+ /**
+ * @param vcardType
+ * @param charset If null, we use default charset for export.
+ * @hide
+ */
+ public VCardBuilder(final int vcardType, String charset) {
mVCardType = vcardType;
- mIsV30 = VCardConfig.isV30(vcardType);
+ Log.w(LOG_TAG,
+ "Should not use vCard 4.0 when building vCard. " +
+ "It is not officially published yet.");
+
+ mIsV30OrV40 = VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType);
mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
- mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
- mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8);
-
- if (mIsDoCoMo) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
- }
- mCharsetString = charset;
- // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
- // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
- // Android, not shown to the public).
- mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
- } else if (mUsesShiftJis) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
- }
- mCharsetString = charset;
+ // vCard 2.1 requires charset.
+ // vCard 3.0 does not allow it but we found some devices use it to determine
+ // the exact charset.
+ // We currently append it only when charset other than UTF_8 is used.
+ mShouldAppendCharsetParam =
+ !(VCardConfig.isVersion30(vcardType) && "UTF-8".equalsIgnoreCase(charset));
+
+ if (VCardConfig.isDoCoMo(vcardType)) {
+ if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
+ } else {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
+ }
mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else {
- mCharsetString = UTF_8;
- mVCardCharsetParameter = "CHARSET=" + UTF_8;
+ if (TextUtils.isEmpty(charset)) {
+ Log.i(LOG_TAG,
+ "Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET
+ + "\" for export.");
+ mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET;
+ mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ mVCardCharsetParameter = "CHARSET=" + charset;
+ }
}
clear();
}
@@ -158,9 +221,14 @@ public class VCardBuilder {
mBuilder = new StringBuilder();
mEndAppended = false;
appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
- if (mIsV30) {
+ if (VCardConfig.isVersion40(mVCardType)) {
+ appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V40);
+ } else if (VCardConfig.isVersion30(mVCardType)) {
appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
} else {
+ if (!VCardConfig.isVersion21(mVCardType)) {
+ Log.w(LOG_TAG, "Unknown vCard version detected.");
+ }
appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
}
}
@@ -227,18 +295,127 @@ public class VCardBuilder {
}
/**
+ * To avoid unnecessary complication in logic, we use this method to construct N, FN
+ * properties for vCard 4.0.
+ */
+ private VCardBuilder appendNamePropertiesV40(final List<ContentValues> contentValuesList) {
+ if (mIsDoCoMo || mNeedsToConvertPhoneticString) {
+ // Ignore all flags that look stale from the view of vCard 4.0 to
+ // simplify construction algorithm. Actually we don't have any vCard file
+ // available from real world yet, so we may need to re-enable some of these
+ // in the future.
+ Log.w(LOG_TAG, "Invalid flag is used in vCard 4.0 construction. Ignored.");
+ }
+
+ if (contentValuesList == null || contentValuesList.isEmpty()) {
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ return this;
+ }
+
+ // We have difficulty here. How can we appropriately handle StructuredName with
+ // missing parts necessary for displaying while it has suppremental information.
+ //
+ // e.g. How to handle non-empty phonetic names with empty structured names?
+
+ final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
+ String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+ final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+ final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+ final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+ final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+ final String formattedName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+ if (TextUtils.isEmpty(familyName)
+ && TextUtils.isEmpty(givenName)
+ && TextUtils.isEmpty(middleName)
+ && TextUtils.isEmpty(prefix)
+ && TextUtils.isEmpty(suffix)) {
+ if (TextUtils.isEmpty(formattedName)) {
+ appendLine(VCardConstants.PROPERTY_FN, "");
+ return this;
+ }
+ familyName = formattedName;
+ }
+
+ final String phoneticFamilyName =
+ contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+ final String phoneticMiddleName =
+ contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+ final String phoneticGivenName =
+ contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+ final String escapedFamily = escapeCharacters(familyName);
+ final String escapedGiven = escapeCharacters(givenName);
+ final String escapedMiddle = escapeCharacters(middleName);
+ final String escapedPrefix = escapeCharacters(prefix);
+ final String escapedSuffix = escapeCharacters(suffix);
+
+ mBuilder.append(VCardConstants.PROPERTY_N);
+
+ if (!(TextUtils.isEmpty(phoneticFamilyName) &&
+ TextUtils.isEmpty(phoneticMiddleName) &&
+ TextUtils.isEmpty(phoneticGivenName))) {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ final String sortAs = escapeCharacters(phoneticFamilyName)
+ + ';' + escapeCharacters(phoneticGivenName)
+ + ';' + escapeCharacters(phoneticMiddleName);
+ mBuilder.append("SORT-AS=").append(
+ VCardUtils.toStringAsV40ParamValue(sortAs));
+ }
+
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(escapedFamily);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(escapedGiven);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(escapedMiddle);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(escapedPrefix);
+ mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(escapedSuffix);
+ mBuilder.append(VCARD_END_OF_LINE);
+
+ if (TextUtils.isEmpty(formattedName)) {
+ // Note:
+ // DISPLAY_NAME doesn't exist while some other elements do, which is usually
+ // weird in Android, as DISPLAY_NAME should (usually) be constructed
+ // from the others using locale information and its code points.
+ Log.w(LOG_TAG, "DISPLAY_NAME is empty.");
+
+ final String escaped = escapeCharacters(VCardUtils.constructNameFromElements(
+ VCardConfig.getNameOrderType(mVCardType),
+ familyName, middleName, givenName, prefix, suffix));
+ appendLine(VCardConstants.PROPERTY_FN, escaped);
+ } else {
+ final String escapedFormatted = escapeCharacters(formattedName);
+ mBuilder.append(VCardConstants.PROPERTY_FN);
+ mBuilder.append(VCARD_DATA_SEPARATOR);
+ mBuilder.append(escapedFormatted);
+ mBuilder.append(VCARD_END_OF_LINE);
+ }
+
+ // We may need X- properties for phonetic names.
+ appendPhoneticNameFields(contentValues);
+ return this;
+ }
+
+ /**
* For safety, we'll emit just one value around StructuredName, as external importers
* may get confused with multiple "N", "FN", etc. properties, though it is valid in
* vCard spec.
*/
public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
+ if (VCardConfig.isVersion40(mVCardType)) {
+ return appendNamePropertiesV40(contentValuesList);
+ }
+
if (contentValuesList == null || contentValuesList.isEmpty()) {
- if (mIsDoCoMo) {
- appendLine(VCardConstants.PROPERTY_N, "");
- } else if (mIsV30) {
+ if (VCardConfig.isVersion30(mVCardType)) {
// vCard 3.0 requires "N" and "FN" properties.
+ // vCard 4.0 does NOT require N, but we take care of possible backward
+ // compatibility issues.
appendLine(VCardConstants.PROPERTY_N, "");
appendLine(VCardConstants.PROPERTY_FN, "");
+ } else if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
}
return this;
}
@@ -360,6 +537,7 @@ public class VCardBuilder {
encodeQuotedPrintable(displayName) :
escapeCharacters(displayName);
+ // N
mBuilder.append(VCardConstants.PROPERTY_N);
if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR);
@@ -376,11 +554,13 @@ public class VCardBuilder {
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(VCARD_END_OF_LINE);
+
+ // FN
mBuilder.append(VCardConstants.PROPERTY_FN);
// Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
- // when it would be useful for external importers, assuming no external
- // importer allows this vioration.
+ // when it would be useful or necessary for external importers,
+ // assuming the external importer allows this vioration of the spec.
if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(mVCardCharsetParameter);
@@ -388,8 +568,7 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedDisplayName);
mBuilder.append(VCARD_END_OF_LINE);
- } else if (mIsV30) {
- // vCard 3.0 specification requires these fields.
+ } else if (VCardConfig.isVersion30(mVCardType)) {
appendLine(VCardConstants.PROPERTY_N, "");
appendLine(VCardConstants.PROPERTY_FN, "");
} else if (mIsDoCoMo) {
@@ -400,6 +579,9 @@ public class VCardBuilder {
return this;
}
+ /**
+ * Emits SOUND;IRMC, SORT-STRING, and de-fact values for phonetic names like X-PHONETIC-FAMILY.
+ */
private void appendPhoneticNameFields(final ContentValues contentValues) {
final String phoneticFamilyName;
final String phoneticMiddleName;
@@ -439,13 +621,18 @@ public class VCardBuilder {
return;
}
- // Try to emit the field(s) related to phonetic name.
- if (mIsV30) {
- final String sortString = VCardUtils
- .constructNameFromElements(mVCardType,
+ if (VCardConfig.isVersion40(mVCardType)) {
+ // We don't want SORT-STRING anyway.
+ } else if (VCardConfig.isVersion30(mVCardType)) {
+ final String sortString =
+ VCardUtils.constructNameFromElements(mVCardType,
phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
- if (shouldAppendCharsetParam(sortString)) {
+ if (VCardConfig.isVersion30(mVCardType) && shouldAppendCharsetParam(sortString)) {
+ // vCard 3.0 does not force us to use UTF-8 and actually we see some
+ // programs which emit this value. It is incorrect from the view of
+ // specification, but actually necessary for parsing vCard with non-UTF-8
+ // charsets, expecting other parsers not get confused with this value.
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(mVCardCharsetParameter);
}
@@ -454,18 +641,18 @@ public class VCardBuilder {
mBuilder.append(VCARD_END_OF_LINE);
} else if (mIsJapaneseMobilePhone) {
// Note: There is no appropriate property for expressing
- // phonetic name in vCard 2.1, while there is in
+ // phonetic name (Yomigana in Japanese) in vCard 2.1, while there is in
// vCard 3.0 (SORT-STRING).
- // We chose to use DoCoMo's way when the device is Japanese one
- // since it is supported by
- // a lot of Japanese mobile phones. This is "X-" property, so
- // any parser hopefully would not get confused with this.
+ // We use DoCoMo's way when the device is Japanese one since it is already
+ // supported by a lot of Japanese mobile phones.
+ // This is "X-" property, so any parser hopefully would not get
+ // confused with this.
//
// Also, DoCoMo's specification requires vCard composer to use just the first
// column.
// i.e.
- // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
- // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+ // good: SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+ // bad : SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
mBuilder.append(VCardConstants.PROPERTY_SOUND);
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
@@ -519,13 +706,14 @@ public class VCardBuilder {
mBuilder.append(encodedPhoneticGivenName);
}
}
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // family;given
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // given;middle
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // middle;prefix
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // prefix;suffix
mBuilder.append(VCARD_END_OF_LINE);
}
+ Log.d("@@@", "hoge");
if (mUsesDefactProperty) {
if (!TextUtils.isEmpty(phoneticGivenName)) {
final boolean reallyUseQuotedPrintable =
@@ -549,7 +737,7 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticGivenName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticMiddleName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -572,7 +760,7 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticMiddleName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticFamilyName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -595,13 +783,13 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticFamilyName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticFamilyName))
}
}
public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
final boolean useAndroidProperty;
- if (mIsV30) {
+ if (mIsV30OrV40) { // These specifications have NICKNAME property.
useAndroidProperty = false;
} else if (mUsesAndroidProperty) {
useAndroidProperty = true;
@@ -642,22 +830,18 @@ public class VCardBuilder {
if (TextUtils.isEmpty(phoneNumber)) {
continue;
}
- int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
- if (type == Phone.TYPE_PAGER) {
+
+ // PAGER number needs unformatted "phone number".
+ final int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE);
+ if (type == Phone.TYPE_PAGER ||
+ VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
phoneLineExists = true;
if (!phoneSet.contains(phoneNumber)) {
phoneSet.add(phoneNumber);
appendTelLine(type, label, phoneNumber, isPrimary);
}
} else {
- // The entry "may" have several phone numbers when the contact entry is
- // corrupted because of its original source.
- //
- // e.g. I encountered the entry like the following.
- // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
- // This kind of entry is not able to be inserted via Android devices, but
- // possible if the source of the data is already corrupted.
- List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+ final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber);
if (phoneNumberList.isEmpty()) {
continue;
}
@@ -670,7 +854,7 @@ public class VCardBuilder {
phoneSet.add(actualPhoneNumber);
appendTelLine(type, label, formattedPhoneNumber, isPrimary);
}
- }
+ } // for (String actualPhoneNumber : phoneNumberList) {
}
}
}
@@ -682,15 +866,38 @@ public class VCardBuilder {
return this;
}
- private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
- List<String> phoneList = new ArrayList<String>();
+ /**
+ * <p>
+ * Splits a given string expressing phone numbers into several strings, and remove
+ * unnecessary characters inside them. The size of a returned list becomes 1 when
+ * no split is needed.
+ * </p>
+ * <p>
+ * The given number "may" have several phone numbers when the contact entry is corrupted
+ * because of its original source.
+ * e.g. "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami)"
+ * </p>
+ * <p>
+ * This kind of "phone numbers" will not be created with Android vCard implementation,
+ * but we may encounter them if the source of the input data has already corrupted
+ * implementation.
+ * </p>
+ * <p>
+ * To handle this case, this method first splits its input into multiple parts
+ * (e.g. "111-222-3333 (Miami)", "444-555-6666 (Broward", and 305653-6796 (Miami)") and
+ * removes unnecessary strings like "(Miami)".
+ * </p>
+ * <p>
+ * Do not call this method when trimming is inappropriate for its receivers.
+ * </p>
+ */
+ private List<String> splitAndTrimPhoneNumbers(final String phoneNumber) {
+ final List<String> phoneList = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
final int length = phoneNumber.length();
for (int i = 0; i < length; i++) {
final char ch = phoneNumber.charAt(i);
- // TODO: add a test case for string with '+', and care the other possible issues
- // which may happen by ignoring non-digits other than '+'.
if (Character.isDigit(ch) || ch == '+') {
builder.append(ch);
} else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
@@ -903,21 +1110,21 @@ public class VCardBuilder {
encodedCountry = escapeCharacters(rawCountry);
encodedNeighborhood = escapeCharacters(rawNeighborhood);
}
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(encodedPoBox);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedStreet);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedLocality);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedRegion);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedPostalCode);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedCountry);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(encodedPoBox);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(encodedStreet);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(encodedLocality);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(encodedRegion);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(encodedPostalCode);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
+ addressBuilder.append(encodedCountry);
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
} else { // VCardUtils.areAllEmpty(rawAddressArray) == true
// Try to use FORMATTED_ADDRESS instead.
final String rawFormattedAddress =
@@ -940,16 +1147,16 @@ public class VCardBuilder {
// We use the second value ("Extended Address") just because Japanese mobile phones
// do so. If the other importer expects the value be in the other field, some flag may
// be needed.
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedFormattedAddress);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(encodedFormattedAddress);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
}
}
@@ -1089,7 +1296,8 @@ public class VCardBuilder {
Log.d(LOG_TAG, "Unknown photo type. Ignored.");
continue;
}
- final String photoString = new String(Base64.encodeBase64(data));
+ // TODO: check this works fine.
+ final String photoString = new String(Base64.encode(data, Base64.NO_WRAP));
if (!TextUtils.isEmpty(photoString)) {
appendPhotoLine(photoString, photoType);
}
@@ -1146,6 +1354,8 @@ public class VCardBuilder {
}
public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
+ // There's possibility where a given object may have more than one birthday, which
+ // is inappropriate. We just build one birthday.
if (contentValuesList != null) {
String primaryBirthday = null;
String secondaryBirthday = null;
@@ -1213,16 +1423,19 @@ public class VCardBuilder {
return this;
}
+ /**
+ * @param emitEveryTime If true, builder builds the line even when there's no entry.
+ */
public void appendPostalLine(final int type, final String label,
final ContentValues contentValues,
- final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean isPrimary, final boolean emitEveryTime) {
final boolean reallyUseQuotedPrintable;
final boolean appendCharset;
final String addressValue;
{
PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
if (postalStruct == null) {
- if (emitLineEveryTime) {
+ if (emitEveryTime) {
reallyUseQuotedPrintable = false;
appendCharset = false;
addressValue = "";
@@ -1444,6 +1657,9 @@ public class VCardBuilder {
parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
} else if (VCardUtils.isMobilePhoneLabel(label)) {
parameterList.add(VCardConstants.PARAM_TYPE_CELL);
+ } else if (mIsV30OrV40) {
+ // This label is appropriately encoded in appendTypeParameters.
+ parameterList.add(label);
} else {
final String upperLabel = label.toUpperCase();
if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) {
@@ -1504,8 +1720,8 @@ public class VCardBuilder {
StringBuilder tmpBuilder = new StringBuilder();
tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
tmpBuilder.append(VCARD_PARAM_SEPARATOR);
- if (mIsV30) {
- tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
+ if (mIsV30OrV40) {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_AS_B);
} else {
tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
}
@@ -1537,7 +1753,8 @@ public class VCardBuilder {
mBuilder.append(VCARD_END_OF_LINE);
}
- public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) {
+ public void appendAndroidSpecificProperty(
+ final String mimeType, ContentValues contentValues) {
if (!sAllowedAndroidPropertySet.contains(mimeType)) {
return;
}
@@ -1659,7 +1876,7 @@ public class VCardBuilder {
encodedValue = encodeQuotedPrintable(rawValue);
} else {
// TODO: one line may be too huge, which may be invalid in vCard spec, though
- // several (even well-known) applications do not care this.
+ // several (even well-known) applications do not care that violation.
encodedValue = escapeCharacters(rawValue);
}
@@ -1722,21 +1939,35 @@ public class VCardBuilder {
// which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
boolean first = true;
for (final String typeValue : types) {
- // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
- // we don't emit that kind of vCard 3.0 specific type since there should be
- // high probabilyty in which external importers cannot understand them.
- //
- // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they
- // are quoted.)
- if (!VCardUtils.isV21Word(typeValue)) {
- continue;
- }
- if (first) {
- first = false;
- } else {
- mBuilder.append(VCARD_PARAM_SEPARATOR);
+ if (VCardConfig.isVersion30(mVCardType)) {
+ final String encoded = VCardUtils.toStringAsV30ParamValue(typeValue);
+ if (TextUtils.isEmpty(encoded)) {
+ continue;
+ }
+
+ // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
+ // we don't emit that kind of vCard 3.0 specific type since there should be
+ // high probabilyty in which external importers cannot understand them.
+ //
+ // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they
+ // are quoted.)
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ }
+ appendTypeParameter(encoded);
+ } else { // vCard 2.1
+ if (!VCardUtils.isV21Word(typeValue)) {
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ mBuilder.append(VCARD_PARAM_SEPARATOR);
+ }
+ appendTypeParameter(typeValue);
}
- appendTypeParameter(typeValue);
}
}
@@ -1752,7 +1983,8 @@ public class VCardBuilder {
// device is DoCoMo's (just for safety).
//
// Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
- if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) {
+ if (VCardConfig.isVersion40(mVCardType) ||
+ ((VCardConfig.isVersion30(mVCardType) || mAppendTypeParamName) && !mIsDoCoMo)) {
builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
}
builder.append(type);
@@ -1794,9 +2026,9 @@ public class VCardBuilder {
byte[] strArray = null;
try {
- strArray = str.getBytes(mCharsetString);
+ strArray = str.getBytes(mCharset);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. "
+ "Try default charset");
strArray = str.getBytes();
}
@@ -1862,7 +2094,7 @@ public class VCardBuilder {
break;
}
case '\\': {
- if (mIsV30) {
+ if (mIsV30OrV40) {
tmpBuilder.append("\\\\");
break;
} else {
@@ -1880,7 +2112,7 @@ public class VCardBuilder {
break;
}
case ',': {
- if (mIsV30) {
+ if (mIsV30OrV40) {
tmpBuilder.append("\\,");
} else {
tmpBuilder.append(ch);
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 0e8b66566f32..193cf1e32b92 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -19,16 +19,12 @@ import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
-import android.content.EntityIterator;
import android.content.Entity.NamedContentValues;
+import android.content.EntityIterator;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.pim.vcard.exception.VCardException;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.RawContactsEntity;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -41,6 +37,11 @@ import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
+import android.text.TextUtils;
import android.util.CharsetUtils;
import android.util.Log;
@@ -61,15 +62,11 @@ import java.util.Map;
/**
* <p>
- * The class for composing VCard from Contacts information. Note that this is
- * completely differnt implementation from
- * android.syncml.pim.vcard.VCardComposer, which is not maintained anymore.
+ * The class for composing vCard from Contacts information.
* </p>
- *
* <p>
* Usually, this class should be used like this.
* </p>
- *
* <pre class="prettyprint">VCardComposer composer = null;
* try {
* composer = new VCardComposer(context);
@@ -93,15 +90,18 @@ import java.util.Map;
* if (composer != null) {
* composer.terminate();
* }
- * } </pre>
+ * }</pre>
+ * <p>
+ * Users have to manually take care of memory efficiency. Even one vCard may contain
+ * image of non-trivial size for mobile devices.
+ * </p>
+ * <p>
+ * {@link VCardBuilder} is used to build each vCard.
+ * </p>
*/
public class VCardComposer {
private static final String LOG_TAG = "VCardComposer";
- public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
- public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
- public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
-
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information";
@@ -119,6 +119,8 @@ public class VCardComposer {
public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
+ // Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
+ // since usual vCard devices for Japanese devices already use it.
private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8";
@@ -141,7 +143,7 @@ public class VCardComposer {
sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
- // Google talk is a special case.
+ // We don't add Google talk here since it has to be handled separately.
}
public static interface OneEntryHandler {
@@ -152,37 +154,37 @@ public class VCardComposer {
/**
* <p>
- * An useful example handler, which emits VCard String to outputstream one by one.
+ * An useful handler for emitting vCard String to an OutputStream object one by one.
* </p>
* <p>
* The input OutputStream object is closed() on {@link #onTerminate()}.
- * Must not close the stream outside.
+ * Must not close the stream outside this class.
* </p>
*/
- public class HandlerForOutputStream implements OneEntryHandler {
+ public final class HandlerForOutputStream implements OneEntryHandler {
@SuppressWarnings("hiding")
- private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream";
-
- final private OutputStream mOutputStream; // mWriter will close this.
- private Writer mWriter;
+ private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
private boolean mOnTerminateIsCalled = false;
+ private final OutputStream mOutputStream; // mWriter will close this.
+ private Writer mWriter;
+
/**
* Input stream will be closed on the detruction of this object.
*/
- public HandlerForOutputStream(OutputStream outputStream) {
+ public HandlerForOutputStream(final OutputStream outputStream) {
mOutputStream = outputStream;
}
- public boolean onInit(Context context) {
+ public boolean onInit(final Context context) {
try {
mWriter = new BufferedWriter(new OutputStreamWriter(
- mOutputStream, mCharsetString));
+ mOutputStream, mCharset));
} catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
+ Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
mErrorReason = "Encoding is not supported (usually this does not happen!): "
- + mCharsetString;
+ + mCharset;
return false;
}
@@ -235,14 +237,19 @@ public class VCardComposer {
"IOException during closing the output stream: "
+ e.getMessage());
} finally {
- try {
- mWriter.close();
- } catch (IOException e) {
- }
+ closeOutputStream();
}
}
}
+ public void closeOutputStream() {
+ try {
+ mWriter.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
+ }
+ }
+
@Override
public void finalize() {
if (!mOnTerminateIsCalled) {
@@ -257,11 +264,10 @@ public class VCardComposer {
private final ContentResolver mContentResolver;
private final boolean mIsDoCoMo;
- private final boolean mUsesShiftJis;
private Cursor mCursor;
private int mIdColumn;
- private final String mCharsetString;
+ private final String mCharset;
private boolean mTerminateIsCalled;
private final List<OneEntryHandler> mHandlerList;
@@ -272,52 +278,107 @@ public class VCardComposer {
};
public VCardComposer(Context context) {
- this(context, VCardConfig.VCARD_TYPE_DEFAULT, true);
+ this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
}
+ /**
+ * The variant which sets charset to null and sets careHandlerErrors to true.
+ */
public VCardComposer(Context context, int vcardType) {
- this(context, vcardType, true);
+ this(context, vcardType, null, true);
}
- public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
- this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
+ public VCardComposer(Context context, int vcardType, String charset) {
+ this(context, vcardType, charset, true);
}
/**
- * Construct for supporting call log entry vCard composing.
+ * The variant which sets charset to null.
*/
public VCardComposer(final Context context, final int vcardType,
final boolean careHandlerErrors) {
+ this(context, vcardType, null, careHandlerErrors);
+ }
+
+ /**
+ * Construct for supporting call log entry vCard composing.
+ *
+ * @param context Context to be used during the composition.
+ * @param vcardType The type of vCard, typically available via {@link VCardConfig}.
+ * @param charset The charset to be used. Use null when you don't need the charset.
+ * @param careHandlerErrors If true, This object returns false everytime
+ * a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
+ * If false, this ignores those errors.
+ */
+ public VCardComposer(final Context context, final int vcardType, String charset,
+ final boolean careHandlerErrors) {
mContext = context;
mVCardType = vcardType;
mCareHandlerErrors = careHandlerErrors;
mContentResolver = context.getContentResolver();
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
- if (mIsDoCoMo) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
- }
- mCharsetString = charset;
- } else if (mUsesShiftJis) {
- String charset;
- try {
- charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
- } catch (UnsupportedCharsetException e) {
- Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
- charset = SHIFT_JIS;
+ charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
+ final boolean shouldAppendCharsetParam = !(
+ VCardConfig.isVersion30(vcardType) && UTF_8.equalsIgnoreCase(charset));
+
+ if (mIsDoCoMo || shouldAppendCharsetParam) {
+ if (SHIFT_JIS.equalsIgnoreCase(charset)) {
+ if (mIsDoCoMo) {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "DoCoMo-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.e(LOG_TAG,
+ "Career-specific SHIFT_JIS was not found. "
+ + "Use SHIFT_JIS as is.");
+ charset = SHIFT_JIS;
+ }
+ }
+ mCharset = charset;
+ } else {
+ Log.w(LOG_TAG,
+ "The charset \"" + charset + "\" is used while "
+ + SHIFT_JIS + " is needed to be used.");
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = SHIFT_JIS;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
}
- mCharsetString = charset;
} else {
- mCharsetString = UTF_8;
+ if (TextUtils.isEmpty(charset)) {
+ mCharset = UTF_8;
+ } else {
+ try {
+ charset = CharsetUtils.charsetForVendor(charset).name();
+ } catch (UnsupportedCharsetException e) {
+ Log.i(LOG_TAG,
+ "Career-specific \"" + charset + "\" was not found (as usual). "
+ + "Use it as is.");
+ }
+ mCharset = charset;
+ }
}
+
+ Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
}
/**
@@ -351,7 +412,7 @@ public class VCardComposer {
}
if (mCareHandlerErrors) {
- List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
for (OneEntryHandler handler : mHandlerList) {
if (!handler.onInit(mContext)) {
@@ -414,7 +475,7 @@ public class VCardComposer {
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
}
- String vcard;
+ final String vcard;
try {
if (mIdColumn >= 0) {
vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
@@ -437,8 +498,7 @@ public class VCardComposer {
mCursor.moveToNext();
}
- // This function does not care the OutOfMemoryError on the handler side
- // :-P
+ // This function does not care the OutOfMemoryError on the handler side :-P
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -457,7 +517,7 @@ public class VCardComposer {
}
private String createOneEntryInternal(final String contactId,
- Method getEntityIteratorMethod) throws VCardException {
+ final Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
// The resolver may return the entity iterator with no data. It is possible.
@@ -466,12 +526,13 @@ public class VCardComposer {
EntityIterator entityIterator = null;
try {
final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
+ // .appendQueryParameter("for_export_only", "1")
.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
.build();
final String selection = Data.CONTACT_ID + "=?";
final String[] selectionArgs = new String[] {contactId};
if (getEntityIteratorMethod != null) {
- // Please note that this branch is executed by some tests only
+ // Please note that this branch is executed by unit tests only
try {
entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
mContentResolver, uri, selection, selectionArgs, null);
@@ -527,22 +588,35 @@ public class VCardComposer {
}
}
- final VCardBuilder builder = new VCardBuilder(mVCardType);
- builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
- .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
- .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
- .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
- .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
- .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
- .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
- if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
- builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
+ return buildVCard(contentValuesListMap);
+ }
+
+ /**
+ * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
+ * {ContactsContract}. Developers can override this method to customize the output.
+ */
+ public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
+ if (contentValuesListMap == null) {
+ Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
+ return "";
+ } else {
+ final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
+ builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
+ .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
+ .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
+ .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
+ .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
+ .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
+ .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
+ if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
+ builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
+ }
+ builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
+ .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
+ .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
+ .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
+ return builder.toString();
}
- builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
- .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
- .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
- .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
- return builder.toString();
}
public void terminate() {
@@ -565,26 +639,38 @@ public class VCardComposer {
@Override
public void finalize() {
if (!mTerminateIsCalled) {
+ Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
terminate();
}
}
+ /**
+ * @return returns the number of available entities. The return value is undefined
+ * when this object is not ready yet (typically when {{@link #init()} is not called
+ * or when {@link #terminate()} is already called).
+ */
public int getCount() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return 0;
}
return mCursor.getCount();
}
+ /**
+ * @return true when there's no entity to be built. The return value is undefined
+ * when this object is not ready yet.
+ */
public boolean isAfterLast() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return false;
}
return mCursor.isAfterLast();
}
/**
- * @return Return the error reason if possible.
+ * @return Returns the error reason.
*/
public String getErrorReason() {
return mErrorReason;
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 3409be6b91e1..8e759e3960b2 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -15,6 +15,7 @@
*/
package android.pim.vcard;
+import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
@@ -37,20 +38,43 @@ public class VCardConfig {
/* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
- /* package */ static final int PARSE_TYPE_UNKNOWN = 0;
- /* package */ static final int PARSE_TYPE_APPLE = 1;
- /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; // For Japanese mobile phones.
- /* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones.
- /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4;
+ /**
+ * <p>
+ * The charset used during import.
+ * </p>
+ * <p>
+ * We cannot determine which charset should be used to interpret lines in vCard,
+ * while Java requires us to specify it when InputStream is used.
+ * We need to rely on the mechanism due to some performance reason.
+ * </p>
+ * <p>
+ * In order to avoid "misinterpretation" of charset and lose any data in vCard,
+ * "ISO-8859-1" is first used for reading the stream.
+ * When a charset is specified in a property (with "CHARSET=..." parameter),
+ * the string is decoded to raw bytes and encoded into the specific charset,
+ * </p>
+ * <p>
+ * Unicode specification there's a one to one mapping between each byte in ISO-8859-1
+ * and a codepoint, and Java specification requires runtime must have the charset.
+ * Thus, ISO-8859-1 is one effective mapping for intermediate mapping.
+ * </p>
+ */
+ public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1";
- // Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and
- // decode the unicode to the original charset. If not, this setting will cause some bug.
- public static final String DEFAULT_CHARSET = "iso-8859-1";
-
- public static final int FLAG_V21 = 0;
- public static final int FLAG_V30 = 1;
+ /**
+ * The charset used when there's no information affbout what charset should be used to
+ * encode the binary given from vCard.
+ */
+ public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
+ public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
- // 0x2 is reserved for the future use ...
+ /**
+ * Do not use statically like "version == VERSION_V21"
+ */
+ public static final int VERSION_21 = 0;
+ public static final int VERSION_30 = 1;
+ public static final int VERSION_40 = 2;
+ public static final int VERSION_MASK = 3;
public static final int NAME_ORDER_DEFAULT = 0;
public static final int NAME_ORDER_EUROPE = 0x4;
@@ -58,311 +82,340 @@ public class VCardConfig {
private static final int NAME_ORDER_MASK = 0xC;
// 0x10 is reserved for safety
-
- private static final int FLAG_CHARSET_UTF8 = 0;
- private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
- private static final int FLAG_CHARSET_MASK = 0xF00;
/**
+ * <p>
* The flag indicating the vCard composer will add some "X-" properties used only in Android
* when the formal vCard specification does not have appropriate fields for that data.
- *
+ * </p>
+ * <p>
* For example, Android accepts nickname information while vCard 2.1 does not.
* When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
* instead of just dropping it.
- *
+ * </p>
+ * <p>
* vCard parser code automatically parses the field emitted even when this flag is off.
- *
- * Note that this flag does not assure all the information must be hold in the emitted vCard.
+ * </p>
*/
private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
/**
+ * <p>
* The flag indicating the vCard composer will add some "X-" properties seen in the
* vCard data emitted by the other softwares/devices when the formal vCard specification
- * does not have appropriate field(s) for that data.
- *
+ * does not have appropriate field(s) for that data.
+ * </p>
+ * <p>
* One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
* for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
* non-Android devices/softwares. We chose to enable the vCard composer to use those
* defact properties since they are also useful for Android devices.
- *
+ * </p>
+ * <p>
* Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
* allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
* in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
+ * </p>
*/
private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
/**
- * The flag indicating some specific dialect seen in vcard of DoCoMo (one of Japanese
+ * <p>
+ * The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
* mobile careers) should be used. This flag does not include any other information like
* that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
* dialect but the name order should be European", but it is not recommended.
+ * </p>
*/
private static final int FLAG_DOCOMO = 0x20000000;
/**
- * <P>
+ * <p>
* The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
* properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
- * </P>
- * <P>
+ * </p>
+ * <p>
* We actually cannot define what is the "primary" property. Note that this is NOT defined
* in vCard specification either. Also be aware that it is NOT related to "primary" notion
* used in {@link android.provider.ContactsContract}.
* This notion is just for vCard composition in Android.
- * </P>
- * <P>
+ * </p>
+ * <p>
* We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
* do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
* even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
* other properties like "ADR", "ORG", etc.
- * <P>
+ * <p>
* We are afraid of the case where some vCard importer also forget handling QP presuming QP is
* not used in such fields.
- * </P>
- * <P>
+ * </p>
+ * <p>
* This flag is useful when some target importer you are going to focus on does not accept
* such properties with Quoted-Printable encoding.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Again, we should not use this flag at all for complying vCard 2.1 spec.
- * </P>
- * <P>
+ * </p>
+ * <p>
* In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
* kind of problem (hopefully).
- * </P>
+ * </p>
+ * @hide
*/
public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
/**
- * <P>
+ * <p>
* The flag indicating that phonetic name related fields must be converted to
* appropriate form. Note that "appropriate" is not defined in any vCard specification.
* This is Android-specific.
- * </P>
- * <P>
+ * </p>
+ * <p>
* One typical (and currently sole) example where we need this flag is the time when
* we need to emit Japanese phonetic names into vCard entries. The property values
* should be encoded into half-width katakana when the target importer is Japanese mobile
* phones', which are probably not able to parse full-width hiragana/katakana for
* historical reasons, while the vCard importers embedded to softwares for PC should be
* able to parse them as we expect.
- * </P>
+ * </p>
*/
- public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000;
+ public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000;
/**
- * <P>
+ * <p>
* The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
* every time possible. The default behavior does not emit it and is valid in the spec.
* In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Detail:
* How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
* </p>
- * <P>
- * e.g.<BR />
- * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR />
- * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR />
- * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR />
- * </P>
- * <P>
- * 2) had been the default of VCard exporter/importer in Android, but it is found that
- * some external exporter is not able to parse the type format like 2) but only 3).
- * </P>
- * <P>
+ * <p>
+ * e.g.
+ * </p>
+ * <ol>
+ * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
+ * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
+ * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
+ * </ol>
+ * <p>
* If you are targeting to the importer which cannot accept TYPE params without "TYPE="
* strings (which should be rare though), please use this flag.
- * </P>
- * <P>
- * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
- * </P>
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
+ * </p>
*/
public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
/**
- * <P>
- * The flag asking exporter to refrain image export.
- * </P>
- * @hide will be deleted in the near future.
+ * <p>
+ * The flag indicating the vCard composer does touch nothing toward phone number Strings
+ * but leave it as is.
+ * </p>
+ * <p>
+ * The vCard specifications mention nothing toward phone numbers, while some devices
+ * do (wrongly, but with innevitable reasons).
+ * For example, there's a possibility Japanese mobile phones are expected to have
+ * just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones
+ * should get such characters. To make exported vCard simple for external parsers,
+ * we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and
+ * removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)"
+ * becomes "111-222-3333").
+ * Unfortunate side effect of that use was some control characters used in the other
+ * areas may be badly affected by the formatting.
+ * </p>
+ * <p>
+ * This flag disables that formatting, affecting both importer and exporter.
+ * If the user is aware of some side effects due to the implicit formatting, use this flag.
+ * </p>
+ */
+ public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
+
+ /**
+ * <p>
+ * For importer only. Ignored in exporter.
+ * </p>
+ * <p>
+ * The flag indicating the parser should handle a nested vCard, in which vCard clause starts
+ * in another vCard clause. Here's a typical example.
+ * </p>
+ * <pre class="prettyprint">BEGIN:VCARD
+ * BEGIN:VCARD
+ * VERSION:2.1
+ * ...
+ * END:VCARD
+ * END:VCARD</pre>
+ * <p>
+ * The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries,
+ * while some mobile devices emit nested ones as primary data to be imported.
+ * </p>
+ * <p>
+ * This flag forces a vCard parser to torelate such a nest and understand its content.
+ * </p>
*/
- public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000;
+ public static final int FLAG_TORELATE_NEST = 0x01000000;
//// The followings are VCard types available from importer/exporter. ////
+ public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x00800000;
+
/**
- * <P>
- * Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset.
- * When composing a vCard entry, the US convension will be used toward formatting
- * some values.
- * </P>
- * <P>
+ * <p>
+ * The type indicating nothing. Used by {@link VCardSourceDetector} when it
+ * was not able to guess the exact vCard type.
+ * </p>
+ */
+ public static final int VCARD_TYPE_UNKNOWN = 0;
+
+ /**
+ * <p>
+ * Generic vCard format with the vCard 2.1. When composing a vCard entry,
+ * the US convension will be used toward formatting some values.
+ * </p>
+ * <p>
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
* while it should be "Prefix Family Middle Given Suffix" in Japan for example.
- * </P>
+ * </p>
+ * <p>
+ * Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
+ * outside Android cannot accept it since vCard 2.1 specifically does not allow
+ * that charset, while we need to use it to support various languages around the world.
+ * </p>
+ * <p>
+ * If you want to use alternative charset, you should notify the charset to the other
+ * compontent to be used.
+ * </p>
*/
- public static final int VCARD_TYPE_V21_GENERIC_UTF8 =
- (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V21_GENERIC =
+ (VERSION_21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic";
+ /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
/**
- * <P>
+ * <p>
* General vCard format with the version 3.0. Uses UTF-8 for the charset.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not fully ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
- (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_GENERIC =
+ (VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
+
+ /**
+ * General vCard format with the version 4.0.
+ * @hide vCard 4.0 is not published yet.
+ */
+ public static final int VCARD_TYPE_V40_GENERIC =
+ (VERSION_40 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V40_GENERIC_STR = "v40_generic";
- /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
-
/**
- * <P>
+ * <p>
* General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
- (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
- /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
+ public static final int VCARD_TYPE_V21_EUROPE =
+ (VERSION_21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
/**
- * <P>
+ * <p>
* General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
- (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_EUROPE =
+ (VERSION_30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
/**
- * <P>
+ * <p>
* The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8";
+ public static final int VCARD_TYPE_V21_JAPANESE =
+ (VERSION_21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /**
- * <P>
- * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
- * parsing/composing the vCard data.
- * </P>
- * <P>
- * Not ready yet. Use with caution when you use this.
- * </P>
- */
- public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
- /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
-
- /**
- * <P>
- * vCard format for miscellaneous Japanese devices, using Shift_Jis for
- * parsing/composing the vCard data.
- * </P>
- * <P>
- * Not ready yet. Use with caution when you use this.
- * </P>
- */
- public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
- (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
-
/**
- * <P>
+ * <p>
* The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
- (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_JAPANESE =
+ (VERSION_30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8";
+ /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8";
/**
- * <P>
+ * <p>
* The vCard 2.1 based format which (partially) considers the convention in Japanese
* mobile phones, where phonetic names are translated to half-width katakana if
- * possible, etc.
- * </P>
- * <P>
- * Not ready yet. Use with caution when you use this.
- * </P>
+ * possible, etc. It would be better to use Shift_JIS as a charset for maximum
+ * compatibility.
+ * </p>
+ * @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_CONVERT_PHONETIC_NAME_STRINGS |
- FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
+ (VERSION_21 | NAME_ORDER_JAPANESE |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
/**
- * <P>
- * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * <p>
+ * The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
* </p>
- * <P>
+ * <p>
* Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
* No Android-specific property nor defact property is included. The "Primary" properties
* are NOT encoded to Quoted-Printable.
- * </P>
+ * </p>
+ * @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_DOCOMO =
(VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
/* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
- public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8;
+ public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
private static final Map<String, Integer> sVCardTypeMap;
private static final Set<Integer> sJapaneseMobileTypeSet;
static {
sVCardTypeMap = new HashMap<String, Integer>();
- sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
- sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
- sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
+ sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
sJapaneseMobileTypeSet = new HashSet<Integer>();
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
@@ -379,20 +432,20 @@ public class VCardConfig {
}
}
- public static boolean isV30(final int vcardType) {
- return ((vcardType & FLAG_V30) != 0);
+ public static boolean isVersion21(final int vcardType) {
+ return (vcardType & VERSION_MASK) == VERSION_21;
}
- public static boolean shouldUseQuotedPrintable(final int vcardType) {
- return !isV30(vcardType);
+ public static boolean isVersion30(final int vcardType) {
+ return (vcardType & VERSION_MASK) == VERSION_30;
}
- public static boolean usesUtf8(final int vcardType) {
- return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
+ public static boolean isVersion40(final int vcardType) {
+ return (vcardType & VERSION_MASK) == VERSION_40;
}
- public static boolean usesShiftJis(final int vcardType) {
- return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
+ public static boolean shouldUseQuotedPrintable(final int vcardType) {
+ return !isVersion30(vcardType);
}
public static int getNameOrderType(final int vcardType) {
@@ -417,7 +470,7 @@ public class VCardConfig {
}
public static boolean appendTypeParamName(final int vcardType) {
- return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
+ return (isVersion30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
}
/**
@@ -431,6 +484,10 @@ public class VCardConfig {
return sJapaneseMobileTypeSet.contains(vcardType);
}
+ /* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) {
+ return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0);
+ }
+
public static boolean needsToConvertPhoneticString(final int vcardType) {
return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0);
}
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
index 8c07126d56bd..76371ef9b02a 100644
--- a/core/java/android/pim/vcard/VCardConstants.java
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -21,6 +21,7 @@ package android.pim.vcard;
public class VCardConstants {
public static final String VERSION_V21 = "2.1";
public static final String VERSION_V30 = "3.0";
+ public static final String VERSION_V40 = "4.0";
// The property names valid both in vCard 2.1 and 3.0.
public static final String PROPERTY_BEGIN = "BEGIN";
@@ -38,25 +39,30 @@ public class VCardConstants {
public static final String PROPERTY_PHOTO = "PHOTO";
public static final String PROPERTY_LOGO = "LOGO";
public static final String PROPERTY_URL = "URL";
- public static final String PROPERTY_BDAY = "BDAY"; // Birthday
+ public static final String PROPERTY_BDAY = "BDAY"; // Birthday (3.0, 4.0)
+ public static final String PROPERTY_BIRTH = "BIRTH"; // Place of birth (4.0)
+ public static final String PROPERTY_ANNIVERSARY = "ANNIVERSARY"; // Date of marriage (4.0)
+ public static final String PROPERTY_NAME = "NAME"; // (3.0, 4,0)
+ public static final String PROPERTY_NICKNAME = "NICKNAME"; // (3.0, 4.0)
+ public static final String PROPERTY_SORT_STRING = "SORT-STRING"; // (3.0, 4.0)
public static final String PROPERTY_END = "END";
- // Valid property names not supported (not appropriately handled) by our vCard importer now.
+ // Valid property names not supported (not appropriately handled) by our importer.
+ // TODO: Should be removed from the view of memory efficiency?
public static final String PROPERTY_REV = "REV";
- public static final String PROPERTY_AGENT = "AGENT";
+ public static final String PROPERTY_AGENT = "AGENT"; // (3.0)
+ public static final String PROPERTY_DDAY = "DDAY"; // Date of death (4.0)
+ public static final String PROPERTY_DEATH = "DEATH"; // Place of death (4.0)
// Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
- public static final String PROPERTY_NAME = "NAME";
- public static final String PROPERTY_NICKNAME = "NICKNAME";
- public static final String PROPERTY_SORT_STRING = "SORT-STRING";
// De-fact property values expressing phonetic names.
public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
- // Properties both ContactsStruct in Eclair and de-fact vCard extensions
- // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
+ // Properties both ContactsStruct and de-fact vCard extensions
+ // Shown in http://en.wikipedia.org/wiki/VCard support are defined here.
public static final String PROPERTY_X_AIM = "X-AIM";
public static final String PROPERTY_X_MSN = "X-MSN";
public static final String PROPERTY_X_YAHOO = "X-YAHOO";
@@ -89,6 +95,9 @@ public class VCardConstants {
public static final String PARAM_TYPE_VOICE = "VOICE";
public static final String PARAM_TYPE_INTERNET = "INTERNET";
+ public static final String PARAM_CHARSET = "CHARSET";
+ public static final String PARAM_ENCODING = "ENCODING";
+
// Abbreviation of "prefered" according to vCard 2.1 specification.
// We interpret this value as "primary" property during import/export.
//
@@ -109,6 +118,12 @@ public class VCardConstants {
public static final String PARAM_TYPE_BBS = "BBS";
public static final String PARAM_TYPE_VIDEO = "VIDEO";
+ public static final String PARAM_ENCODING_7BIT = "7BIT";
+ public static final String PARAM_ENCODING_8BIT = "8BIT";
+ public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
+ public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
+ public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
+
// TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
// These types are basically encoded to "X-" parameters when composing vCard.
// Parser passes these when "X-" is added to the parameter or not.
@@ -126,14 +141,15 @@ public class VCardConstants {
public static final String PARAM_ADR_TYPE_DOM = "DOM";
public static final String PARAM_ADR_TYPE_INTL = "INTL";
+ public static final String PARAM_LANGUAGE = "LANGUAGE";
+
+ // SORT-AS parameter introduced in vCard 4.0 (as of rev.13)
+ public static final String PARAM_SORT_AS = "SORT-AS";
+
// TYPE parameters not officially valid but used in some vCard exporter.
// Do not use in composer side.
public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
- // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
- // vCard 3.0.
- public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
-
public interface ImportOnly {
public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
// Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,7 +158,14 @@ public class VCardConstants {
public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
}
- /* package */ static final int MAX_DATA_COLUMN = 15;
+ //// Mainly for package constants.
+
+ // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
+ // SORT-STRING invCard 3.0.
+ /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
+
+ // Used in unit test.
+ public static final int MAX_DATA_COLUMN = 15;
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
index 13277704bdcf..94f7c5fe1b0d 100644
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -20,13 +20,11 @@ import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.OperationApplicationException;
-import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
@@ -61,9 +59,6 @@ public class VCardEntry {
private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
- private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
- private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
-
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
static {
@@ -78,12 +73,12 @@ public class VCardEntry {
Im.PROTOCOL_GOOGLE_TALK);
}
- static public class PhoneData {
+ public static class PhoneData {
public final int type;
public final String data;
public final String label;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
+ // isPrimary is (not final but) changable, only when there's no appropriate one existing
+ // in the original VCard.
public boolean isPrimary;
public PhoneData(int type, String data, String label, boolean isPrimary) {
this.type = type;
@@ -109,13 +104,11 @@ public class VCardEntry {
}
}
- static public class EmailData {
+ public static class EmailData {
public final int type;
public final String data;
// Used only when TYPE is TYPE_CUSTOM.
public final String label;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
public boolean isPrimary;
public EmailData(int type, String data, String label, boolean isPrimary) {
this.type = type;
@@ -141,9 +134,9 @@ public class VCardEntry {
}
}
- static public class PostalData {
- // Determined by vCard spec.
- // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
+ public static class PostalData {
+ // Determined by vCard specification.
+ // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
public static final int ADDR_MAX_DATA_SIZE = 7;
private final String[] dataArray;
public final String pobox;
@@ -248,24 +241,28 @@ public class VCardEntry {
}
}
- static public class OrganizationData {
+ public static class OrganizationData {
public final int type;
// non-final is Intentional: we may change the values since this info is separated into
- // two parts in vCard: "ORG" + "TITLE".
+ // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in
+ // different timing.
public String companyName;
public String departmentName;
public String titleName;
+ public final String phoneticName; // We won't have this in "TITLE" property.
public boolean isPrimary;
public OrganizationData(int type,
- String companyName,
- String departmentName,
- String titleName,
- boolean isPrimary) {
+ final String companyName,
+ final String departmentName,
+ final String titleName,
+ final String phoneticName,
+ final boolean isPrimary) {
this.type = type;
this.companyName = companyName;
this.departmentName = departmentName;
this.titleName = titleName;
+ this.phoneticName = phoneticName;
this.isPrimary = isPrimary;
}
@@ -313,7 +310,7 @@ public class VCardEntry {
}
}
- static public class ImData {
+ public static class ImData {
public final int protocol;
public final String customProtocol;
public final int type;
@@ -434,6 +431,14 @@ public class VCardEntry {
}
}
+ // TODO(dmiyakawa): vCard 4.0 logically has multiple formatted names and we need to
+ // select the most preferable one using PREF parameter.
+ //
+ // e.g. (based on rev.13)
+ // FN;PREF=1:John M. Doe
+ // FN;PREF=2:John Doe
+ // FN;PREF=3;John
+
private String mFamilyName;
private String mGivenName;
private String mMiddleName;
@@ -441,7 +446,7 @@ public class VCardEntry {
private String mSuffix;
// Used only when no family nor given name is found.
- private String mFullName;
+ private String mFormattedName;
private String mPhoneticFamilyName;
private String mPhoneticGivenName;
@@ -454,6 +459,7 @@ public class VCardEntry {
private String mDisplayName;
private String mBirthday;
+ private String mAnniversary;
private List<String> mNoteList;
private List<PhoneData> mPhoneList;
@@ -469,7 +475,7 @@ public class VCardEntry {
private final Account mAccount;
public VCardEntry() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC);
}
public VCardEntry(int vcardType) {
@@ -488,7 +494,7 @@ public class VCardEntry {
final StringBuilder builder = new StringBuilder();
final String trimed = data.trim();
final String formattedNumber;
- if (type == Phone.TYPE_PAGER) {
+ if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) {
formattedNumber = trimed;
} else {
final int length = trimed.length();
@@ -499,9 +505,7 @@ public class VCardEntry {
}
}
- // Use NANP in default when there's no information about locale.
- final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ?
- PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP);
+ final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
}
PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary);
@@ -530,22 +534,44 @@ public class VCardEntry {
}
/**
- * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+ * Should be called via {@link #handleOrgValue(int, List, Map, boolean) or
* {@link #handleTitleValue(String)}.
*/
private void addNewOrganization(int type, final String companyName,
final String departmentName,
- final String titleName, boolean isPrimary) {
+ final String titleName,
+ final String phoneticName,
+ final boolean isPrimary) {
if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>();
}
mOrganizationList.add(new OrganizationData(type, companyName,
- departmentName, titleName, isPrimary));
+ departmentName, titleName, phoneticName, isPrimary));
}
private static final List<String> sEmptyList =
Collections.unmodifiableList(new ArrayList<String>(0));
+ private String buildSinglePhoneticNameFromSortAsParam(Map<String, Collection<String>> paramMap) {
+ final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS);
+ if (sortAsCollection != null && sortAsCollection.size() != 0) {
+ if (sortAsCollection.size() > 1) {
+ Log.w(LOG_TAG, "Incorrect multiple SORT_AS parameters detected: " +
+ Arrays.toString(sortAsCollection.toArray()));
+ }
+ final List<String> sortNames =
+ VCardUtils.constructListFromValue(sortAsCollection.iterator().next(),
+ mVCardType);
+ final StringBuilder builder = new StringBuilder();
+ for (final String elem : sortNames) {
+ builder.append(elem);
+ }
+ return builder.toString();
+ } else {
+ return null;
+ }
+ }
+
/**
* Set "ORG" related values to the appropriate data. If there's more than one
* {@link OrganizationData} objects, this input data are attached to the last one which
@@ -553,7 +579,9 @@ public class VCardEntry {
* {@link OrganizationData} object, a new {@link OrganizationData} is created,
* whose title is set to null.
*/
- private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) {
+ private void handleOrgValue(final int type, List<String> orgList,
+ Map<String, Collection<String>> paramMap, boolean isPrimary) {
+ final String phoneticName = buildSinglePhoneticNameFromSortAsParam(paramMap);
if (orgList == null) {
orgList = sEmptyList;
}
@@ -588,7 +616,7 @@ public class VCardEntry {
if (mOrganizationList == null) {
// Create new first organization entry, with "null" title which may be
// added via handleTitleValue().
- addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary);
return;
}
for (OrganizationData organizationData : mOrganizationList) {
@@ -606,7 +634,7 @@ public class VCardEntry {
}
// No OrganizatioData is available. Create another one, with "null" title, which may be
// added via handleTitleValue().
- addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary);
}
/**
@@ -620,7 +648,7 @@ public class VCardEntry {
if (mOrganizationList == null) {
// Create new first organization entry, with "null" other info, which may be
// added via handleOrgValue().
- addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false);
return;
}
for (OrganizationData organizationData : mOrganizationList) {
@@ -631,7 +659,7 @@ public class VCardEntry {
}
// No Organization is available. Create another one, with "null" other info, which may be
// added via handleOrgValue().
- addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false);
}
private void addIm(int protocol, String customProtocol, int type,
@@ -657,11 +685,54 @@ public class VCardEntry {
mPhotoList.add(photoData);
}
+ /**
+ * Tries to extract paramMap, constructs SORT-AS parameter values, and store them in
+ * appropriate phonetic name variables.
+ *
+ * This method does not care the vCard version. Even when we have SORT-AS parameters in
+ * invalid versions (i.e. 2.1 and 3.0), we scilently accept them so that we won't drop
+ * meaningful information. If we had this parameter in the N field of vCard 3.0, and
+ * the contact data also have SORT-STRING, we will prefer SORT-STRING, since it is
+ * regitimate property to be understood.
+ */
+ private void tryHandleSortAsName(final Map<String, Collection<String>> paramMap) {
+ if (VCardConfig.isVersion30(mVCardType) &&
+ !(TextUtils.isEmpty(mPhoneticFamilyName) &&
+ TextUtils.isEmpty(mPhoneticMiddleName) &&
+ TextUtils.isEmpty(mPhoneticGivenName))) {
+ return;
+ }
+
+ final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS);
+ if (sortAsCollection != null && sortAsCollection.size() != 0) {
+ if (sortAsCollection.size() > 1) {
+ Log.w(LOG_TAG, "Incorrect multiple SORT_AS parameters detected: " +
+ Arrays.toString(sortAsCollection.toArray()));
+ }
+ final List<String> sortNames =
+ VCardUtils.constructListFromValue(sortAsCollection.iterator().next(),
+ mVCardType);
+ int size = sortNames.size();
+ if (size > 3) {
+ size = 3;
+ }
+ switch (size) {
+ case 3: mPhoneticMiddleName = sortNames.get(2); //$FALL-THROUGH$
+ case 2: mPhoneticGivenName = sortNames.get(1); //$FALL-THROUGH$
+ default: mPhoneticFamilyName = sortNames.get(0); break;
+ }
+ }
+ }
+
@SuppressWarnings("fallthrough")
- private void handleNProperty(List<String> elems) {
+ private void handleNProperty(final List<String> paramValues,
+ Map<String, Collection<String>> paramMap) {
+ // in vCard 4.0, SORT-AS parameter is available.
+ tryHandleSortAsName(paramMap);
+
// Family, Given, Middle, Prefix, Suffix. (1 - 5)
int size;
- if (elems == null || (size = elems.size()) < 1) {
+ if (paramValues == null || (size = paramValues.size()) < 1) {
return;
}
if (size > 5) {
@@ -669,12 +740,12 @@ public class VCardEntry {
}
switch (size) {
- // fallthrough
- case 5: mSuffix = elems.get(4);
- case 4: mPrefix = elems.get(3);
- case 3: mMiddleName = elems.get(2);
- case 2: mGivenName = elems.get(1);
- default: mFamilyName = elems.get(0);
+ // Fall-through.
+ case 5: mSuffix = paramValues.get(4);
+ case 4: mPrefix = paramValues.get(3);
+ case 3: mMiddleName = paramValues.get(2);
+ case 2: mGivenName = paramValues.get(1);
+ default: mFamilyName = paramValues.get(0);
}
}
@@ -755,13 +826,13 @@ public class VCardEntry {
if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
} else if (propName.equals(VCardConstants.PROPERTY_FN)) {
- mFullName = propValue;
- } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
+ mFormattedName = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFormattedName == null) {
// Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
// actually exist in the real vCard data, does not exist.
- mFullName = propValue;
+ mFormattedName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_N)) {
- handleNProperty(propValueList);
+ handleNProperty(propValueList, paramMap);
} else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
mPhoneticFullName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
@@ -776,8 +847,7 @@ public class VCardEntry {
// which is correct behavior from the view of vCard 2.1.
// But we want it to be separated, so do the separation here.
final List<String> phoneticNameList =
- VCardUtils.constructListFromValue(propValue,
- VCardConfig.isV30(mVCardType));
+ VCardUtils.constructListFromValue(propValue, mVCardType);
handlePhoneticNameFromSound(phoneticNameList);
} else {
// Ignore this field since Android cannot understand what it is.
@@ -878,7 +948,7 @@ public class VCardEntry {
}
}
}
- handleOrgValue(type, propValueList, isPrimary);
+ handleOrgValue(type, propValueList, paramMap, isPrimary);
} else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
handleTitleValue(propValue);
} else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
@@ -955,7 +1025,7 @@ public class VCardEntry {
}
}
if (type < 0) {
- type = Phone.TYPE_HOME;
+ type = Im.TYPE_HOME;
}
addIm(protocol, null, type, propValue, isPrimary);
} else if (propName.equals(VCardConstants.PROPERTY_NOTE)) {
@@ -967,6 +1037,8 @@ public class VCardEntry {
mWebsiteList.add(propValue);
} else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
mBirthday = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_ANNIVERSARY)) {
+ mAnniversary = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
mPhoneticGivenName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
@@ -975,33 +1047,9 @@ public class VCardEntry {
mPhoneticFamilyName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
final List<String> customPropertyList =
- VCardUtils.constructListFromValue(propValue,
- VCardConfig.isV30(mVCardType));
+ VCardUtils.constructListFromValue(propValue, mVCardType);
handleAndroidCustomProperty(customPropertyList);
- /*} else if (propName.equals("REV")) {
- // Revision of this VCard entry. I think we can ignore this.
- } else if (propName.equals("UID")) {
- } else if (propName.equals("KEY")) {
- // Type is X509 or PGP? I don't know how to handle this...
- } else if (propName.equals("MAILER")) {
- } else if (propName.equals("TZ")) {
- } else if (propName.equals("GEO")) {
- } else if (propName.equals("CLASS")) {
- // vCard 3.0 only.
- // e.g. CLASS:CONFIDENTIAL
- } else if (propName.equals("PROFILE")) {
- // VCard 3.0 only. Must be "VCARD". I think we can ignore this.
- } else if (propName.equals("CATEGORIES")) {
- // VCard 3.0 only.
- // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
- } else if (propName.equals("SOURCE")) {
- // VCard 3.0 only.
- } else if (propName.equals("PRODID")) {
- // VCard 3.0 only.
- // To specify the identifier for the product that created
- // the vCard object.*/
} else {
- // Unknown X- words and IANA token.
}
}
@@ -1017,8 +1065,8 @@ public class VCardEntry {
*/
private void constructDisplayName() {
// FullName (created via "FN" or "NAME" field) is prefered.
- if (!TextUtils.isEmpty(mFullName)) {
- mDisplayName = mFullName;
+ if (!TextUtils.isEmpty(mFormattedName)) {
+ mDisplayName = mFormattedName;
} else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
@@ -1063,23 +1111,6 @@ public class VCardEntry {
if (mAccount != null) {
builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
-
- // Assume that caller side creates this group if it does not exist.
- if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
- final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
- Groups.SOURCE_ID },
- Groups.TITLE + "=?", new String[] {
- GOOGLE_MY_CONTACTS_GROUP }, null);
- try {
- if (cursor != null && cursor.moveToFirst()) {
- myGroupsId = cursor.getString(0);
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- }
} else {
builder.withValue(RawContacts.ACCOUNT_NAME, null);
builder.withValue(RawContacts.ACCOUNT_TYPE, null);
@@ -1155,6 +1186,9 @@ public class VCardEntry {
if (organizationData.titleName != null) {
builder.withValue(Organization.TITLE, organizationData.titleName);
}
+ if (organizationData.phoneticName != null) {
+ builder.withValue(Organization.PHONETIC_NAME, organizationData.phoneticName);
+ }
if (organizationData.isPrimary) {
builder.withValue(Organization.IS_PRIMARY, 1);
}
@@ -1196,12 +1230,14 @@ public class VCardEntry {
builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
builder.withValue(Im.TYPE, imData.type);
builder.withValue(Im.PROTOCOL, imData.protocol);
+ builder.withValue(Im.DATA, imData.data);
if (imData.protocol == Im.PROTOCOL_CUSTOM) {
builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol);
}
if (imData.isPrimary) {
builder.withValue(Data.IS_PRIMARY, 1);
}
+ operationList.add(builder.build());
}
}
@@ -1250,6 +1286,15 @@ public class VCardEntry {
operationList.add(builder.build());
}
+ if (!TextUtils.isEmpty(mAnniversary)) {
+ builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+ builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
+ builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
+ builder.withValue(Event.START_DATE, mAnniversary);
+ builder.withValue(Event.TYPE, Event.TYPE_ANNIVERSARY);
+ operationList.add(builder.build());
+ }
+
if (mAndroidCustomPropertyList != null) {
for (List<String> customPropertyList : mAndroidCustomPropertyList) {
int size = customPropertyList.size();
@@ -1321,7 +1366,7 @@ public class VCardEntry {
&& TextUtils.isEmpty(mGivenName)
&& TextUtils.isEmpty(mPrefix)
&& TextUtils.isEmpty(mSuffix)
- && TextUtils.isEmpty(mFullName)
+ && TextUtils.isEmpty(mFormattedName)
&& TextUtils.isEmpty(mPhoneticFamilyName)
&& TextUtils.isEmpty(mPhoneticMiddleName)
&& TextUtils.isEmpty(mPhoneticGivenName)
@@ -1380,7 +1425,7 @@ public class VCardEntry {
}
public String getFullName() {
- return mFullName;
+ return mFormattedName;
}
public String getPhoneticFamilyName() {
diff --git a/core/java/android/pim/vcard/VCardEntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
index 59a2baf13179..a8c8057a58b6 100644
--- a/core/java/android/pim/vcard/VCardEntryCommitter.java
+++ b/core/java/android/pim/vcard/VCardEntryCommitter.java
@@ -52,9 +52,9 @@ public class VCardEntryCommitter implements VCardEntryHandler {
}
}
- public void onEntryCreated(final VCardEntry contactStruct) {
+ public void onEntryCreated(final VCardEntry vcardEntry) {
long start = System.currentTimeMillis();
- mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver));
+ mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver));
mTimeToCommit += System.currentTimeMillis() - start;
}
diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
index 290ca2bfec14..aa3e3e2f585f 100644
--- a/core/java/android/pim/vcard/VCardEntryConstructor.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -16,78 +16,82 @@
package android.pim.vcard;
import android.accounts.Account;
+import android.text.TextUtils;
+import android.util.Base64;
import android.util.CharsetUtils;
import android.util.Log;
-import org.apache.commons.codec.DecoderException;
-import org.apache.commons.codec.binary.Base64;
-import org.apache.commons.codec.net.QuotedPrintableCodec;
-
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+/**
+ * <p>
+ * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
+ * to easily handle each vCard entry.
+ * </p>
+ * <p>
+ * This class understand details inside vCard and translates it to {@link VCardEntry}.
+ * Then the class throw it to {@link VCardEntryHandler} registered via
+ * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
+ * are able to handle the {@link VCardEntry} object.
+ * </p>
+ * <p>
+ * If you want to know the detail inside vCard, it would be better to implement
+ * {@link VCardInterpreter} directly, instead of relying on this class and
+ * {@link VCardEntry} created by the object.
+ * </p>
+ */
public class VCardEntryConstructor implements VCardInterpreter {
private static String LOG_TAG = "VCardEntryConstructor";
- /**
- * If there's no other information available, this class uses this charset for encoding
- * byte arrays to String.
- */
- /* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8";
-
private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
- private VCardEntry mCurrentContactStruct;
+ private VCardEntry mCurrentVCardEntry;
private String mParamType;
- /**
- * The charset using which {@link VCardInterpreter} parses the text.
- */
- private String mInputCharset;
-
- /**
- * The charset with which byte array is encoded to String.
- */
- final private String mCharsetForDecodedBytes;
- final private boolean mStrictLineBreakParsing;
- final private int mVCardType;
- final private Account mAccount;
+ // The charset using which {@link VCardInterpreter} parses the text.
+ // Each String is first decoded into binary stream with this charset, and encoded back
+ // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
+ // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
+ private final String mSourceCharset;
+
+ private final boolean mStrictLineBreaking;
+ private final int mVCardType;
+ private final Account mAccount;
- /** For measuring performance. */
+ // For measuring performance.
private long mTimePushIntoContentResolver;
- final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+ private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
public VCardEntryConstructor() {
- this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null);
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
}
public VCardEntryConstructor(final int vcardType) {
- this(null, null, false, vcardType, null);
+ this(vcardType, null, null, false);
+ }
+
+ public VCardEntryConstructor(final int vcardType, final Account account) {
+ this(vcardType, account, null, false);
}
- public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
- final int vcardType, final Account account) {
- this(null, charset, strictLineBreakParsing, vcardType, account);
+ public VCardEntryConstructor(final int vcardType, final Account account,
+ final String inputCharset) {
+ this(vcardType, account, inputCharset, false);
}
- public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
- final boolean strictLineBreakParsing, final int vcardType,
- final Account account) {
+ /**
+ * @hide Just for testing.
+ */
+ public VCardEntryConstructor(final int vcardType, final Account account,
+ final String inputCharset, final boolean strictLineBreakParsing) {
if (inputCharset != null) {
- mInputCharset = inputCharset;
- } else {
- mInputCharset = VCardConfig.DEFAULT_CHARSET;
- }
- if (charsetForDetodedBytes != null) {
- mCharsetForDecodedBytes = charsetForDetodedBytes;
+ mSourceCharset = inputCharset;
} else {
- mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
+ mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
}
- mStrictLineBreakParsing = strictLineBreakParsing;
+ mStrictLineBreaking = strictLineBreakParsing;
mVCardType = vcardType;
mAccount = account;
}
@@ -95,60 +99,63 @@ public class VCardEntryConstructor implements VCardInterpreter {
public void addEntryHandler(VCardEntryHandler entryHandler) {
mEntryHandlers.add(entryHandler);
}
-
+
+ @Override
public void start() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onStart();
}
}
+ @Override
public void end() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEnd();
}
}
- /**
- * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
- */
public void clear() {
- mCurrentContactStruct = null;
+ mCurrentVCardEntry = null;
mCurrentProperty = new VCardEntry.Property();
}
- /**
- * Assume that VCard is not nested. In other words, this code does not accept
- */
+ @Override
public void startEntry() {
- if (mCurrentContactStruct != null) {
+ if (mCurrentVCardEntry != null) {
Log.e(LOG_TAG, "Nested VCard code is not supported now.");
}
- mCurrentContactStruct = new VCardEntry(mVCardType, mAccount);
+ mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
}
+ @Override
public void endEntry() {
- mCurrentContactStruct.consolidateFields();
+ mCurrentVCardEntry.consolidateFields();
for (VCardEntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onEntryCreated(mCurrentContactStruct);
+ entryHandler.onEntryCreated(mCurrentVCardEntry);
}
- mCurrentContactStruct = null;
+ mCurrentVCardEntry = null;
}
+ @Override
public void startProperty() {
mCurrentProperty.clear();
}
+ @Override
public void endProperty() {
- mCurrentContactStruct.addProperty(mCurrentProperty);
+ mCurrentVCardEntry.addProperty(mCurrentProperty);
}
+ @Override
public void propertyName(String name) {
mCurrentProperty.setPropertyName(name);
}
+ @Override
public void propertyGroup(String group) {
}
+ @Override
public void propertyParamType(String type) {
if (mParamType != null) {
Log.e(LOG_TAG, "propertyParamType() is called more than once " +
@@ -157,122 +164,41 @@ public class VCardEntryConstructor implements VCardInterpreter {
mParamType = type;
}
+ @Override
public void propertyParamValue(String value) {
if (mParamType == null) {
// From vCard 2.1 specification. vCard 3.0 formally does not allow this case.
mParamType = "TYPE";
}
+ if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
+ value = VCardUtils.convertStringCharset(
+ value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET);
+ }
mCurrentProperty.addParameter(mParamType, value);
mParamType = null;
}
- private String encodeString(String originalString, String charsetForDecodedBytes) {
- if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
- return originalString;
- }
- Charset charset = Charset.forName(mInputCharset);
- ByteBuffer byteBuffer = charset.encode(originalString);
- // byteBuffer.array() "may" return byte array which is larger than
- // byteBuffer.remaining(). Here, we keep on the safe side.
- byte[] bytes = new byte[byteBuffer.remaining()];
- byteBuffer.get(bytes);
- try {
- return new String(bytes, charsetForDecodedBytes);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
- return null;
+ private String handleOneValue(String value,
+ String sourceCharset, String targetCharset, String encoding) {
+ // It is possible when some of multiple values are empty.
+ // e.g. N:;a;;; -> values are "", "a", "", "", and "".
+ if (TextUtils.isEmpty(value)) {
+ return "";
}
- }
- private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) {
if (encoding != null) {
if (encoding.equals("BASE64") || encoding.equals("B")) {
- mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes()));
+ mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
- // "= " -> " ", "=\t" -> "\t".
- // Previous code had done this replacement. Keep on the safe side.
- StringBuilder builder = new StringBuilder();
- int length = value.length();
- for (int i = 0; i < length; i++) {
- char ch = value.charAt(i);
- if (ch == '=' && i < length - 1) {
- char nextCh = value.charAt(i + 1);
- if (nextCh == ' ' || nextCh == '\t') {
-
- builder.append(nextCh);
- i++;
- continue;
- }
- }
- builder.append(ch);
- }
- String quotedPrintable = builder.toString();
-
- String[] lines;
- if (mStrictLineBreakParsing) {
- lines = quotedPrintable.split("\r\n");
- } else {
- builder = new StringBuilder();
- length = quotedPrintable.length();
- ArrayList<String> list = new ArrayList<String>();
- for (int i = 0; i < length; i++) {
- char ch = quotedPrintable.charAt(i);
- if (ch == '\n') {
- list.add(builder.toString());
- builder = new StringBuilder();
- } else if (ch == '\r') {
- list.add(builder.toString());
- builder = new StringBuilder();
- if (i < length - 1) {
- char nextCh = quotedPrintable.charAt(i + 1);
- if (nextCh == '\n') {
- i++;
- }
- }
- } else {
- builder.append(ch);
- }
- }
- String finalLine = builder.toString();
- if (finalLine.length() > 0) {
- list.add(finalLine);
- }
- lines = list.toArray(new String[0]);
- }
-
- builder = new StringBuilder();
- for (String line : lines) {
- if (line.endsWith("=")) {
- line = line.substring(0, line.length() - 1);
- }
- builder.append(line);
- }
- byte[] bytes;
- try {
- bytes = builder.toString().getBytes(mInputCharset);
- } catch (UnsupportedEncodingException e1) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset);
- bytes = builder.toString().getBytes();
- }
-
- try {
- bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
- } catch (DecoderException e) {
- Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
- return "";
- }
-
- try {
- return new String(bytes, charsetForDecodedBytes);
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
- return new String(bytes);
- }
+ return VCardUtils.parseQuotedPrintable(
+ value, mStrictLineBreaking, sourceCharset, targetCharset);
}
- // Unknown encoding. Fall back to default.
+ Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
}
- return encodeString(value, charsetForDecodedBytes);
+
+ // Just translate the charset of a given String from inputCharset to a system one.
+ return VCardUtils.convertStringCharset(value, sourceCharset, targetCharset);
}
public void propertyValues(List<String> values) {
@@ -280,24 +206,27 @@ public class VCardEntryConstructor implements VCardInterpreter {
return;
}
- final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
- final String charset =
- ((charsetCollection != null) ? charsetCollection.iterator().next() : null);
- final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
+ final Collection<String> charsetCollection =
+ mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET);
+ final Collection<String> encodingCollection =
+ mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING);
final String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-
- String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset);
- if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) {
- charsetForDecodedBytes = mCharsetForDecodedBytes;
+ String targetCharset = CharsetUtils.nameForDefaultVendor(
+ ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
+ if (TextUtils.isEmpty(targetCharset)) {
+ targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
for (final String value : values) {
mCurrentProperty.addToPropertyValueList(
- handleOneValue(value, charsetForDecodedBytes, encoding));
+ handleOneValue(value, mSourceCharset, targetCharset, encoding));
}
}
+ /**
+ * @hide
+ */
public void showPerformanceInfo() {
Log.d(LOG_TAG, "time for insert ContactStruct to database: " +
mTimePushIntoContentResolver + " ms");
diff --git a/core/java/android/pim/vcard/VCardEntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
index 83a67fe2c6f8..56bf69d5d371 100644
--- a/core/java/android/pim/vcard/VCardEntryHandler.java
+++ b/core/java/android/pim/vcard/VCardEntryHandler.java
@@ -16,8 +16,13 @@
package android.pim.vcard;
/**
- * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to
- * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}.
+ * <p>
+ * The interface called by {@link VCardEntryConstructor}.
+ * </p>
+ * <p>
+ * This class is useful when you don't want to know vCard data in detail. If you want to know
+ * it, it would be better to consider using {@link VCardInterpreter}.
+ * </p>
*/
public interface VCardEntryHandler {
/**
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
index b5237c066bf0..03704a22a962 100644
--- a/core/java/android/pim/vcard/VCardInterpreter.java
+++ b/core/java/android/pim/vcard/VCardInterpreter.java
@@ -20,7 +20,7 @@ import java.util.List;
/**
* <P>
* The interface which should be implemented by the classes which have to analyze each
- * vCard entry more minutely than {@link VCardEntry} class analysis.
+ * vCard entry minutely.
* </P>
* <P>
* Here, there are several terms specific to vCard (and this library).
diff --git a/core/java/android/pim/vcard/VCardInterpreterCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
index 99f81f704d77..77e44a02b2e0 100644
--- a/core/java/android/pim/vcard/VCardInterpreterCollection.java
+++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java
@@ -23,9 +23,9 @@ import java.util.List;
* {@link VCardInterpreter} objects and make a user object treat them as one
* {@link VCardInterpreter} object.
*/
-public class VCardInterpreterCollection implements VCardInterpreter {
+public final class VCardInterpreterCollection implements VCardInterpreter {
private final Collection<VCardInterpreter> mInterpreterCollection;
-
+
public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
mInterpreterCollection = interpreterCollection;
}
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index 57c52a646cdd..31b9369231f5 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -20,82 +20,36 @@ import android.pim.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
-public abstract class VCardParser {
- protected final int mParseType;
- protected boolean mCanceled;
-
- public VCardParser() {
- this(VCardConfig.PARSE_TYPE_UNKNOWN);
- }
-
- public VCardParser(int parseType) {
- mParseType = parseType;
- }
-
+public interface VCardParser {
/**
- * <P>
- * Parses the given stream and send the VCard data into VCardBuilderBase object.
- * </P.
- * <P>
+ * <p>
+ * Parses the given stream and send the vCard data into VCardBuilderBase object.
+ * </p>.
+ * <p>
* Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
* local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
- * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1,
- * In some exreme case, some VCard may have different charsets in one VCard (though
- * we do not see any device which emits such kind of malicious data)
- * </P>
- * <P>
- * In order to avoid "misunderstanding" charset as much as possible, this method
- * use "ISO-8859-1" for reading the stream. When charset is specified in some property
- * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
- * the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
- * characters, which is not completely sure. In some cases, this "decoding-encoding"
- * scheme may fail. To avoid the case,
- * </P>
- * <P>
- * We recommend you to use {@link VCardSourceDetector} and detect which kind of source the
- * VCard comes from and explicitly specify a charset using the result.
- * </P>
+ * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
+ * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
+ * </p>
+ * <p>
+ * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
+ * vCard comes from and explicitly specify a charset using the result.
+ * </p>
*
* @param is The source to parse.
* @param interepreter A {@link VCardInterpreter} object which used to construct data.
- * @return Returns true for success. Otherwise returns false.
- * @throws IOException, VCardException
- */
- public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
- throws IOException, VCardException;
-
- /**
- * <P>
- * The method variants which accept charset.
- * </P>
- * <P>
- * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
- * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
- * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
- * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
- * </P>
- *
- * @param is The source to parse.
- * @param charset Charset to be used.
- * @param builder The VCardBuilderBase object.
- * @return Returns true when successful. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException;
-
- /**
- * The method variants which tells this object the operation is already canceled.
- */
- public abstract void parse(InputStream is, String charset,
- VCardInterpreter builder, boolean canceled)
- throws IOException, VCardException;
-
+
/**
- * Cancel parsing.
- * Actual cancel is done after the end of the current one vcard entry parsing.
+ * <p>
+ * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
+ * </p>
+ * <p>
+ * Actual cancel is done after parsing the current vcard.
+ * </p>
*/
- public void cancel() {
- mCanceled = true;
- }
+ public abstract void cancel();
}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V21.java b/core/java/android/pim/vcard/VCardParserImpl_V21.java
new file mode 100644
index 000000000000..8b365eb417ad
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V21.java
@@ -0,0 +1,1046 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.pim.vcard;
+
+import android.pim.vcard.exception.VCardAgentNotSupportedException;
+import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardInvalidCommentLineException;
+import android.pim.vcard.exception.VCardInvalidLineException;
+import android.pim.vcard.exception.VCardNestedException;
+import android.pim.vcard.exception.VCardVersionException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard parsing. Based on vCard 2.1,
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V21";
+
+ private static final class EmptyInterpreter implements VCardInterpreter {
+ @Override
+ public void end() {
+ }
+ @Override
+ public void endEntry() {
+ }
+ @Override
+ public void endProperty() {
+ }
+ @Override
+ public void propertyGroup(String group) {
+ }
+ @Override
+ public void propertyName(String name) {
+ }
+ @Override
+ public void propertyParamType(String type) {
+ }
+ @Override
+ public void propertyParamValue(String value) {
+ }
+ @Override
+ public void propertyValues(List<String> values) {
+ }
+ @Override
+ public void start() {
+ }
+ @Override
+ public void startEntry() {
+ }
+ @Override
+ public void startProperty() {
+ }
+ }
+
+ protected static final class CustomBufferedReader extends BufferedReader {
+ private long mTime;
+
+ /**
+ * Needed since "next line" may be null due to end of line.
+ */
+ private boolean mNextLineIsValid;
+ private String mNextLine;
+
+ public CustomBufferedReader(Reader in) {
+ super(in);
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ if (mNextLineIsValid) {
+ final String ret = mNextLine;
+ mNextLine = null;
+ mNextLineIsValid = false;
+ return ret;
+ }
+
+ long start = System.currentTimeMillis();
+ final String line = super.readLine();
+ long end = System.currentTimeMillis();
+ mTime += end - start;
+ return line;
+ }
+
+ /**
+ * Read one line, but make this object store it in its queue.
+ */
+ public String peekLine() throws IOException {
+ if (!mNextLineIsValid) {
+ long start = System.currentTimeMillis();
+ final String line = super.readLine();
+ long end = System.currentTimeMillis();
+ mTime += end - start;
+
+ mNextLine = line;
+ mNextLineIsValid = true;
+ }
+
+ return mNextLine;
+ }
+
+ public long getTotalmillisecond() {
+ return mTime;
+ }
+ }
+
+ private static final String DEFAULT_ENCODING = "8BIT";
+
+ protected boolean mCanceled;
+ protected VCardInterpreter mInterpreter;
+
+ protected final String mIntermediateCharset;
+
+ /**
+ * <p>
+ * The encoding type for deconding byte streams. This member variable is
+ * reset to a default encoding every time when a new item comes.
+ * </p>
+ * <p>
+ * "Encoding" in vCard is different from "Charset". It is mainly used for
+ * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
+ * "QUOTED-PRINTABLE" are known examples.
+ * </p>
+ */
+ protected String mCurrentEncoding;
+
+ /**
+ * <p>
+ * The reader object to be used internally.
+ * </p>
+ * <p>
+ * Developers should not directly read a line from this object. Use
+ * getLine() unless there some reason.
+ * </p>
+ */
+ protected CustomBufferedReader mReader;
+
+ /**
+ * <p>
+ * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
+ * specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownTypeSet = new HashSet<String>();
+
+ /**
+ * <p>
+ * Set for storing unkonwn VALUE attributes, which is not acceptable in
+ * vCard specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownValueSet = new HashSet<String>();
+
+
+ // In some cases, vCard is nested. Currently, we only consider the most
+ // interior vCard data.
+ // See v21_foma_1.vcf in test directory for more information.
+ // TODO: Don't ignore by using count, but read all of information outside vCard.
+ private int mNestCount;
+
+ // Used only for parsing END:VCARD.
+ private String mPreviousLine;
+
+ // For measuring performance.
+ private long mTimeTotal;
+ private long mTimeReadStartRecord;
+ private long mTimeReadEndRecord;
+ private long mTimeStartProperty;
+ private long mTimeEndProperty;
+ private long mTimeParseItems;
+ private long mTimeParseLineAndHandleGroup;
+ private long mTimeParsePropertyValues;
+ private long mTimeParseAdrOrgN;
+ private long mTimeHandleMiscPropertyValue;
+ private long mTimeHandleQuotedPrintable;
+ private long mTimeHandleBase64;
+
+ public VCardParserImpl_V21() {
+ this(VCardConfig.VCARD_TYPE_DEFAULT);
+ }
+
+ public VCardParserImpl_V21(int vcardType) {
+ if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) {
+ mNestCount = 1;
+ }
+
+ mIntermediateCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
+ }
+
+ /**
+ * <p>
+ * Parses the file at the given position.
+ * </p>
+ */
+ // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
+ protected void parseVCardFile() throws IOException, VCardException {
+ boolean readingFirstFile = true;
+ while (true) {
+ if (mCanceled) {
+ break;
+ }
+ if (!parseOneVCard(readingFirstFile)) {
+ break;
+ }
+ readingFirstFile = false;
+ }
+
+ if (mNestCount > 0) {
+ boolean useCache = true;
+ for (int i = 0; i < mNestCount; i++) {
+ readEndVCard(useCache, true);
+ useCache = false;
+ }
+ }
+ }
+
+ /**
+ * @return true when a given property name is a valid property name.
+ */
+ protected boolean isValidPropertyName(final String propertyName) {
+ if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
+ propertyName.startsWith("X-"))
+ && !mUnknownTypeSet.contains(propertyName)) {
+ mUnknownTypeSet.add(propertyName);
+ Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
+ }
+ return true;
+ }
+
+ /**
+ * @return String. It may be null, or its length may be 0
+ * @throws IOException
+ */
+ protected String getLine() throws IOException {
+ return mReader.readLine();
+ }
+
+ protected String peekLine() throws IOException {
+ return mReader.peekLine();
+ }
+
+ /**
+ * @return String with it's length > 0
+ * @throws IOException
+ * @throws VCardException when the stream reached end of line
+ */
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.trim().length() > 0) {
+ return line;
+ }
+ }
+ }
+
+ /*
+ * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
+ * items *CRLF
+ * "END" [ws] ":" [ws] "VCARD"
+ */
+ private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException {
+ boolean allowGarbage = false;
+ if (firstRead) {
+ if (mNestCount > 0) {
+ for (int i = 0; i < mNestCount; i++) {
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ allowGarbage = true;
+ }
+ }
+ }
+
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ final long beforeStartEntry = System.currentTimeMillis();
+ mInterpreter.startEntry();
+ mTimeReadStartRecord += System.currentTimeMillis() - beforeStartEntry;
+
+ final long beforeParseItems = System.currentTimeMillis();
+ parseItems();
+ mTimeParseItems += System.currentTimeMillis() - beforeParseItems;
+
+ readEndVCard(true, false);
+
+ final long beforeEndEntry = System.currentTimeMillis();
+ mInterpreter.endEntry();
+ mTimeReadEndRecord += System.currentTimeMillis() - beforeEndEntry;
+ return true;
+ }
+
+ /**
+ * @return True when successful. False when reaching the end of line
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ String line;
+ do {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ return false;
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ final String[] strArray = line.split(":", 2);
+ final int length = strArray.length;
+
+ // Although vCard 2.1/3.0 specification does not allow lower cases,
+ // we found vCard file emitted by some external vCard expoter have such
+ // invalid Strings.
+ // So we allow it.
+ // e.g.
+ // BEGIN:vCard
+ if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return true;
+ } else if (!allowGarbage) {
+ if (mNestCount > 0) {
+ mPreviousLine = line;
+ return false;
+ } else {
+ throw new VCardException("Expected String \"BEGIN:VCARD\" did not come "
+ + "(Instead, \"" + line + "\" came)");
+ }
+ }
+ } while (allowGarbage);
+
+ throw new VCardException("Reached where must not be reached.");
+ }
+
+ /**
+ * <p>
+ * The arguments useCache and allowGarbase are usually true and false
+ * accordingly when this function is called outside this function itself.
+ * </p>
+ *
+ * @param useCache When true, line is obtained from mPreviousline.
+ * Otherwise, getLine() is used.
+ * @param allowGarbage When true, ignore non "END:VCARD" line.
+ * @throws IOException
+ * @throws VCardException
+ */
+ protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException,
+ VCardException {
+ String line;
+ do {
+ if (useCache) {
+ // Though vCard specification does not allow lower cases,
+ // some data may have them, so we allow it.
+ line = mPreviousLine;
+ } else {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("Expected END:VCARD was not found.");
+ } else if (line.trim().length() > 0) {
+ break;
+ }
+ }
+ }
+
+ String[] strArray = line.split(":", 2);
+ if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END")
+ && strArray[1].trim().equalsIgnoreCase("VCARD")) {
+ return;
+ } else if (!allowGarbage) {
+ throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
+ }
+ useCache = false;
+ } while (allowGarbage);
+ }
+
+ /*
+ * items = *CRLF item / item
+ */
+ protected void parseItems() throws IOException, VCardException {
+ boolean ended = false;
+
+ final long beforeBeginProperty = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - beforeBeginProperty;
+ ended = parseItem();
+ if (!ended) {
+ final long beforeEndProperty = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty;
+ }
+
+ while (!ended) {
+ final long beforeStartProperty = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - beforeStartProperty;
+ try {
+ ended = parseItem();
+ } catch (VCardInvalidCommentLineException e) {
+ Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+ ended = false;
+ }
+
+ if (!ended) {
+ final long beforeEndProperty = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty;
+ }
+ }
+ }
+
+ /*
+ * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR"
+ * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts
+ * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."]
+ * "AGENT" [params] ":" vcard CRLF
+ */
+ protected boolean parseItem() throws IOException, VCardException {
+ mCurrentEncoding = DEFAULT_ENCODING;
+
+ final String line = getNonEmptyLine();
+ long start = System.currentTimeMillis();
+
+ String[] propertyNameAndValue = separateLineAndHandleGroup(line);
+ if (propertyNameAndValue == null) {
+ return true;
+ }
+ if (propertyNameAndValue.length != 2) {
+ throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
+ }
+ String propertyName = propertyNameAndValue[0].toUpperCase();
+ String propertyValue = propertyNameAndValue[1];
+
+ mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
+
+ if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) {
+ start = System.currentTimeMillis();
+ handleMultiplePropertyValue(propertyName, propertyValue);
+ mTimeParseAdrOrgN += System.currentTimeMillis() - start;
+ return false;
+ } else if (propertyName.equals("AGENT")) {
+ handleAgent(propertyValue);
+ return false;
+ } else if (isValidPropertyName(propertyName)) {
+ if (propertyName.equals("BEGIN")) {
+ if (propertyValue.equals("VCARD")) {
+ throw new VCardNestedException("This vCard has nested vCard data in it.");
+ } else {
+ throw new VCardException("Unknown BEGIN type: " + propertyValue);
+ }
+ } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) {
+ throw new VCardVersionException("Incompatible version: " + propertyValue + " != "
+ + getVersionString());
+ }
+ start = System.currentTimeMillis();
+ handlePropertyValue(propertyName, propertyValue);
+ mTimeParsePropertyValues += System.currentTimeMillis() - start;
+ return false;
+ }
+
+ throw new VCardException("Unknown property name: \"" + propertyName + "\"");
+ }
+
+ // For performance reason, the states for group and property name are merged into one.
+ static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
+ static private final int STATE_PARAMS = 1;
+ // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
+ static private final int STATE_PARAMS_IN_DQUOTE = 2;
+
+ protected String[] separateLineAndHandleGroup(String line) throws VCardException {
+ final String[] propertyNameAndValue = new String[2];
+ final int length = line.length();
+ if (length > 0 && line.charAt(0) == '#') {
+ throw new VCardInvalidCommentLineException();
+ }
+
+ int state = STATE_GROUP_OR_PROPERTY_NAME;
+ int nameIndex = 0;
+
+ // This loop is developed so that we don't have to take care of bottle neck here.
+ // Refactor carefully when you need to do so.
+ for (int i = 0; i < length; i++) {
+ final char ch = line.charAt(i);
+ switch (state) {
+ case STATE_GROUP_OR_PROPERTY_NAME: {
+ if (ch == ':') { // End of a property name.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ mInterpreter.propertyName(propertyName);
+ propertyNameAndValue[0] = propertyName;
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ } else if (ch == '.') { // Each group is followed by the dot.
+ final String groupName = line.substring(nameIndex, i);
+ if (groupName.length() == 0) {
+ Log.w(LOG_TAG, "Empty group found. Ignoring.");
+ } else {
+ mInterpreter.propertyGroup(groupName);
+ }
+ nameIndex = i + 1; // Next should be another group or a property name.
+ } else if (ch == ';') { // End of property name and beginneng of parameters.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ mInterpreter.propertyName(propertyName);
+ propertyNameAndValue[0] = propertyName;
+ nameIndex = i + 1;
+ state = STATE_PARAMS; // Start parameter parsing.
+ }
+ // TODO: comma support (in vCard 3.0 and 4.0).
+ break;
+ }
+ case STATE_PARAMS: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS_IN_DQUOTE;
+ } else if (ch == ';') { // Starts another param.
+ handleParams(line.substring(nameIndex, i));
+ nameIndex = i + 1;
+ } else if (ch == ':') { // End of param and beginenning of values.
+ handleParams(line.substring(nameIndex, i));
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ }
+ break;
+ }
+ case STATE_PARAMS_IN_DQUOTE: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS;
+ }
+ break;
+ }
+ }
+ }
+
+ throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
+ }
+
+ /*
+ * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param /
+ * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws]
+ * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "="
+ * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "="
+ * [ws] word / knowntype
+ */
+ protected void handleParams(String params) throws VCardException {
+ final String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ final String paramName = strArray[0].trim().toUpperCase();
+ String paramValue = strArray[1].trim();
+ if (paramName.equals("TYPE")) {
+ handleType(paramValue);
+ } else if (paramName.equals("VALUE")) {
+ handleValue(paramValue);
+ } else if (paramName.equals("ENCODING")) {
+ handleEncoding(paramValue);
+ } else if (paramName.equals("CHARSET")) {
+ handleCharset(paramValue);
+ } else if (paramName.equals("LANGUAGE")) {
+ handleLanguage(paramValue);
+ } else if (paramName.startsWith("X-")) {
+ handleAnyParam(paramName, paramValue);
+ } else {
+ throw new VCardException("Unknown type \"" + paramName + "\"");
+ }
+ } else {
+ handleParamWithoutName(strArray[0]);
+ }
+ }
+
+ /**
+ * vCard 3.0 parser implementation may throw VCardException.
+ */
+ @SuppressWarnings("unused")
+ protected void handleParamWithoutName(final String paramValue) throws VCardException {
+ handleType(paramValue);
+ }
+
+ /*
+ * ptypeval = knowntype / "X-" word
+ */
+ protected void handleType(final String ptypeval) {
+ if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
+ || ptypeval.startsWith("X-"))
+ && !mUnknownTypeSet.contains(ptypeval)) {
+ mUnknownTypeSet.add(ptypeval);
+ Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
+ }
+ mInterpreter.propertyParamType("TYPE");
+ mInterpreter.propertyParamValue(ptypeval);
+ }
+
+ /*
+ * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+ */
+ protected void handleValue(final String pvalueval) {
+ if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
+ || pvalueval.startsWith("X-")
+ || mUnknownValueSet.contains(pvalueval))) {
+ mUnknownValueSet.add(pvalueval);
+ Log.w(LOG_TAG, String.format(
+ "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
+ }
+ mInterpreter.propertyParamType("VALUE");
+ mInterpreter.propertyParamValue(pvalueval);
+ }
+
+ /*
+ * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
+ */
+ protected void handleEncoding(String pencodingval) throws VCardException {
+ if (getAvailableEncodingSet().contains(pencodingval) ||
+ pencodingval.startsWith("X-")) {
+ mInterpreter.propertyParamType("ENCODING");
+ mInterpreter.propertyParamValue(pencodingval);
+ mCurrentEncoding = pencodingval;
+ } else {
+ throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
+ }
+ }
+
+ /**
+ * <p>
+ * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+ * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
+ * We allow any charset.
+ * </p>
+ */
+ protected void handleCharset(String charsetval) {
+ mInterpreter.propertyParamType("CHARSET");
+ mInterpreter.propertyParamValue(charsetval);
+ }
+
+ /**
+ * See also Section 7.1 of RFC 1521
+ */
+ protected void handleLanguage(String langval) throws VCardException {
+ String[] strArray = langval.split("-");
+ if (strArray.length != 2) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ String tmp = strArray[0];
+ int length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ tmp = strArray[1];
+ length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ mInterpreter.propertyParamType(VCardConstants.PARAM_LANGUAGE);
+ mInterpreter.propertyParamValue(langval);
+ }
+
+ private boolean isAsciiLetter(char ch) {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Mainly for "X-" type. This accepts any kind of type without check.
+ */
+ protected void handleAnyParam(String paramName, String paramValue) {
+ mInterpreter.propertyParamType(paramName);
+ mInterpreter.propertyParamValue(paramValue);
+ }
+
+ protected void handlePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ final String upperEncoding = mCurrentEncoding.toUpperCase();
+ if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
+ final long start = System.currentTimeMillis();
+ final String result = getQuotedPrintable(propertyValue);
+ final ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mInterpreter.propertyValues(v);
+ mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
+ } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
+ || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) {
+ final long start = System.currentTimeMillis();
+ // It is very rare, but some BASE64 data may be so big that
+ // OutOfMemoryError occurs. To ignore such cases, use try-catch.
+ try {
+ final ArrayList<String> arrayList = new ArrayList<String>();
+ arrayList.add(getBase64(propertyValue));
+ mInterpreter.propertyValues(arrayList);
+ } catch (OutOfMemoryError error) {
+ Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
+ mInterpreter.propertyValues(null);
+ }
+ mTimeHandleBase64 += System.currentTimeMillis() - start;
+ } else {
+ if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
+ upperEncoding.startsWith("X-"))) {
+ Log.w(LOG_TAG,
+ String.format("The encoding \"%s\" is unsupported by vCard %s",
+ mCurrentEncoding, getVersionString()));
+ }
+
+ // Some device uses line folding defined in RFC 2425, which is not allowed
+ // in vCard 2.1 (while needed in vCard 3.0).
+ //
+ // e.g.
+ // BEGIN:VCARD
+ // VERSION:2.1
+ // N:;Omega;;;
+ // EMAIL;INTERNET:"Omega"
+ // <omega@example.com>
+ // FN:Omega
+ // END:VCARD
+ //
+ // The vCard above assumes that email address should become:
+ // "Omega" <omega@example.com>
+ //
+ // But vCard 2.1 requires Quote-Printable when a line contains line break(s).
+ //
+ // For more information about line folding,
+ // see "5.8.1. Line delimiting and folding" in RFC 2425.
+ //
+ // We take care of this case more formally in vCard 3.0, so we only need to
+ // do this in vCard 2.1.
+ if (getVersion() == VCardConfig.VERSION_21) {
+ StringBuilder builder = null;
+ while (true) {
+ final String nextLine = peekLine();
+ // We don't need to care too much about this exceptional case,
+ // but we should not wrongly eat up "END:VCARD", since it critically
+ // breaks this parser's state machine.
+ // Thus we roughly look over the next line and confirm it is at least not
+ // "END:VCARD". This extra fee is worth paying. This is exceptional
+ // anyway.
+ if (!TextUtils.isEmpty(nextLine) &&
+ nextLine.charAt(0) == ' ' &&
+ !"END:VCARD".contains(nextLine.toUpperCase())) {
+ getLine(); // Drop the next line.
+
+ if (builder == null) {
+ builder = new StringBuilder();
+ builder.append(propertyValue);
+ }
+ builder.append(nextLine.substring(1));
+ } else {
+ break;
+ }
+ }
+ if (builder != null) {
+ propertyValue = builder.toString();
+ }
+ }
+
+ final long start = System.currentTimeMillis();
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(maybeUnescapeText(propertyValue));
+ mInterpreter.propertyValues(v);
+ mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
+ }
+ }
+
+ /**
+ * <p>
+ * Parses and returns Quoted-Printable.
+ * </p>
+ *
+ * @param firstString The string following a parameter name and attributes.
+ * Example: "string" in
+ * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
+ * @return whole Quoted-Printable string, including a given argument and
+ * following lines. Excludes the last empty line following to Quoted
+ * Printable lines.
+ * @throws IOException
+ * @throws VCardException
+ */
+ private String getQuotedPrintable(String firstString) throws IOException, VCardException {
+ // Specifically, there may be some padding between = and CRLF.
+ // See the following:
+ //
+ // qp-line := *(qp-segment transport-padding CRLF)
+ // qp-part transport-padding
+ // qp-segment := qp-section *(SPACE / TAB) "="
+ // ; Maximum length of 76 characters
+ //
+ // e.g. (from RFC 2045)
+ // Now's the time =
+ // for all folk to come=
+ // to the aid of their country.
+ if (firstString.trim().endsWith("=")) {
+ // remove "transport-padding"
+ int pos = firstString.length() - 1;
+ while (firstString.charAt(pos) != '=') {
+ }
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString.substring(0, pos + 1));
+ builder.append("\r\n");
+ String line;
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing a Quoted-Printable String");
+ }
+ if (line.trim().endsWith("=")) {
+ // remove "transport-padding"
+ pos = line.length() - 1;
+ while (line.charAt(pos) != '=') {
+ }
+ builder.append(line.substring(0, pos + 1));
+ builder.append("\r\n");
+ } else {
+ builder.append(line);
+ break;
+ }
+ }
+ return builder.toString();
+ } else {
+ return firstString;
+ }
+ }
+
+ protected String getBase64(String firstString) throws IOException, VCardException {
+ StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * <p>
+ * Mainly for "ADR", "ORG", and "N"
+ * </p>
+ */
+ /*
+ * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr,
+ * Street, Locality, Region, Postal Code, Country Name orgparts =
+ * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are
+ * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family,
+ * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III,
+ * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a
+ * semicolon in this string, it must be escaped ; with a "\" character. We
+ * do not care the number of "strnosemi" here. We are not sure whether we
+ * should add "\" CRLF to each value. We exclude them for now.
+ */
+ protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some
+ // softwares/devices
+ // emit such data.
+ if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ propertyValue = getQuotedPrintable(propertyValue);
+ }
+
+ mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
+ getVersion()));
+ }
+
+ /*
+ * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
+ * error toward the AGENT property.
+ * // TODO: Support AGENT property.
+ * item =
+ * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws]
+ * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD"
+ */
+ protected void handleAgent(final String propertyValue) throws VCardException {
+ if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
+ // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
+ return;
+ } else {
+ throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
+ }
+ }
+
+ /**
+ * For vCard 3.0.
+ */
+ protected String maybeUnescapeText(final String text) {
+ return text;
+ }
+
+ /**
+ * Returns unescaped String if the character should be unescaped. Return
+ * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";"
+ * while "\x" should not be.
+ */
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ /* package */ static String unescapeCharacter(final char ch) {
+ // Original vCard 2.1 specification does not allow transformation
+ // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous
+ // implementation of
+ // this class allowed them, so keep it as is.
+ if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
+ return String.valueOf(ch);
+ } else {
+ return null;
+ }
+ }
+
+ private void showPerformanceInfo() {
+ Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
+ Log.d(LOG_TAG, "Total readLine time: " + mReader.getTotalmillisecond() + " ms");
+ Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms");
+ Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup
+ + " ms");
+ Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
+ Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
+ Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue
+ + " ms");
+ Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms");
+ Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
+ }
+
+ /**
+ * @return {@link VCardConfig#VERSION_21}
+ */
+ protected int getVersion() {
+ return VCardConfig.VERSION_21;
+ }
+
+ /**
+ * @return {@link VCardConfig#VERSION_30}
+ */
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V21;
+ }
+
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V21.sKnownPropertyNameSet;
+ }
+
+ protected Set<String> getKnownTypeSet() {
+ return VCardParser_V21.sKnownTypeSet;
+ }
+
+ protected Set<String> getKnownValueSet() {
+ return VCardParser_V21.sKnownValueSet;
+ }
+
+ protected Set<String> getAvailableEncodingSet() {
+ return VCardParser_V21.sAvailableEncoding;
+ }
+
+ protected String getDefaultEncoding() {
+ return DEFAULT_ENCODING;
+ }
+
+
+ public void parse(InputStream is, VCardInterpreter interpreter)
+ throws IOException, VCardException {
+ if (is == null) {
+ throw new NullPointerException("InputStream must not be null.");
+ }
+
+ final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset);
+ mReader = new CustomBufferedReader(tmpReader);
+
+ mInterpreter = (interpreter != null ? interpreter : new EmptyInterpreter());
+
+ final long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ mInterpreter.start();
+ }
+ parseVCardFile();
+ if (mInterpreter != null) {
+ mInterpreter.end();
+ }
+ mTimeTotal += System.currentTimeMillis() - start;
+
+ if (VCardConfig.showPerformanceLog()) {
+ showPerformanceInfo();
+ }
+ }
+
+ public final void cancel() {
+ mCanceled = true;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V30.java b/core/java/android/pim/vcard/VCardParserImpl_V30.java
new file mode 100644
index 000000000000..3fad9a08cc15
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V30.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.pim.vcard;
+
+import android.pim.vcard.exception.VCardException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard 3.0 parsing.
+ * </p>
+ * <p>
+ * This class inherits vCard 2.1 implementation since technically they are similar,
+ * while specifically there's logical no relevance between them.
+ * So that developers are not confused with the inheritance,
+ * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
+ * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V30";
+
+ private String mPreviousLine;
+ private boolean mEmittedAgentWarning = false;
+
+ public VCardParserImpl_V30() {
+ super();
+ }
+
+ public VCardParserImpl_V30(int vcardType) {
+ super(vcardType);
+ }
+
+ @Override
+ protected int getVersion() {
+ return VCardConfig.VERSION_30;
+ }
+
+ @Override
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V30;
+ }
+
+ @Override
+ protected String getLine() throws IOException {
+ if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ } else {
+ return mReader.readLine();
+ }
+ }
+
+ /**
+ * vCard 3.0 requires that the line with space at the beginning of the line
+ * must be combined with previous line.
+ */
+ @Override
+ protected String getNonEmptyLine() throws IOException, VCardException {
+ String line;
+ StringBuilder builder = null;
+ while (true) {
+ line = mReader.readLine();
+ if (line == null) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ throw new VCardException("Reached end of buffer.");
+ } else if (line.length() == 0) {
+ if (builder != null) {
+ return builder.toString();
+ } else if (mPreviousLine != null) {
+ String ret = mPreviousLine;
+ mPreviousLine = null;
+ return ret;
+ }
+ } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
+ if (builder != null) {
+ // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
+ // Following is the excerpts from it.
+ //
+ // DESCRIPTION:This is a long description that exists on a long line.
+ //
+ // Can be represented as:
+ //
+ // DESCRIPTION:This is a long description
+ // that exists on a long line.
+ //
+ // It could also be represented as:
+ //
+ // DESCRIPTION:This is a long descrip
+ // tion that exists o
+ // n a long line.
+ builder.append(line.substring(1));
+ } else if (mPreviousLine != null) {
+ builder = new StringBuilder();
+ builder.append(mPreviousLine);
+ mPreviousLine = null;
+ builder.append(line.substring(1));
+ } else {
+ throw new VCardException("Space exists at the beginning of the line");
+ }
+ } else {
+ if (mPreviousLine == null) {
+ mPreviousLine = line;
+ if (builder != null) {
+ return builder.toString();
+ }
+ } else {
+ String ret = mPreviousLine;
+ mPreviousLine = line;
+ return ret;
+ }
+ }
+ }
+ }
+
+ /*
+ * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
+ * 1 * (contentline)
+ * ;A vCard object MUST include the VERSION, FN and N types.
+ * [group "."] "END" ":" "VCARD" 1 * CRLF
+ */
+ @Override
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ return super.readBeginVCard(allowGarbage);
+ }
+
+ @Override
+ protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ throws IOException, VCardException {
+ // TODO: vCard 3.0 supports group.
+ super.readEndVCard(useCache, allowGarbage);
+ }
+
+ /**
+ * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
+ */
+ @Override
+ protected void handleParams(final String params) throws VCardException {
+ try {
+ super.handleParams(params);
+ } catch (VCardException e) {
+ // maybe IANA type
+ String[] strArray = params.split("=", 2);
+ if (strArray.length == 2) {
+ handleAnyParam(strArray[0], strArray[1]);
+ } else {
+ // Must not come here in the current implementation.
+ throw new VCardException(
+ "Unknown params value: " + params);
+ }
+ }
+ }
+
+ @Override
+ protected void handleAnyParam(final String paramName, final String paramValue) {
+ mInterpreter.propertyParamType(paramName);
+ splitAndPutParamValue(paramValue);
+ }
+
+ @Override
+ protected void handleParamWithoutName(final String paramValue) {
+ handleType(paramValue);
+ }
+
+ /*
+ * vCard 3.0 defines
+ *
+ * param = param-name "=" param-value *("," param-value)
+ * param-name = iana-token / x-name
+ * param-value = ptext / quoted-string
+ * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+ * QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
+ * ; Any character except CTLs, DQUOTE
+ *
+ * QSAFE-CHAR must not contain DQUOTE, including escaped one (\").
+ */
+ @Override
+ protected void handleType(final String paramValue) {
+ mInterpreter.propertyParamType("TYPE");
+ splitAndPutParamValue(paramValue);
+ }
+
+ /**
+ * Splits parameter values into pieces in accordance with vCard 3.0 specification and
+ * puts pieces into mInterpreter.
+ */
+ /*
+ * param-value = ptext / quoted-string
+ * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
+ * QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
+ * ; Any character except CTLs, DQUOTE
+ *
+ * QSAFE-CHAR must not contain DQUOTE, including escaped one (\")
+ */
+ private void splitAndPutParamValue(String paramValue) {
+ // "comma,separated:inside.dquote",pref
+ // -->
+ // - comma,separated:inside.dquote
+ // - pref
+ //
+ // Note: Though there's a code, we don't need to take much care of
+ // wrongly-added quotes like the example above, as they induce
+ // parse errors at the top level (when splitting a line into parts).
+ StringBuilder builder = null; // Delay initialization.
+ boolean insideDquote = false;
+ final int length = paramValue.length();
+ for (int i = 0; i < length; i++) {
+ final char ch = paramValue.charAt(i);
+ if (ch == '"') {
+ if (insideDquote) {
+ // End of Dquote.
+ mInterpreter.propertyParamValue(builder.toString());
+ builder = null;
+ insideDquote = false;
+ } else {
+ if (builder != null) {
+ if (builder.length() > 0) {
+ // e.g.
+ // pref"quoted"
+ Log.w(LOG_TAG, "Unexpected Dquote inside property.");
+ } else {
+ // e.g.
+ // pref,"quoted"
+ // "quoted",pref
+ mInterpreter.propertyParamValue(builder.toString());
+ }
+ }
+ insideDquote = true;
+ }
+ } else if (ch == ',' && !insideDquote) {
+ if (builder == null) {
+ Log.w(LOG_TAG, "Comma is used before actual string comes. (" +
+ paramValue + ")");
+ } else {
+ mInterpreter.propertyParamValue(builder.toString());
+ builder = null;
+ }
+ } else {
+ // To stop creating empty StringBuffer at the end of parameter,
+ // we delay creating this object until this point.
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(ch);
+ }
+ }
+ if (insideDquote) {
+ // e.g.
+ // "non-quote-at-end
+ Log.d(LOG_TAG, "Dangling Dquote.");
+ }
+ if (builder != null) {
+ if (builder.length() == 0) {
+ Log.w(LOG_TAG, "Unintended behavior. We must not see empty StringBuilder " +
+ "at the end of parameter value parsing.");
+ } else {
+ mInterpreter.propertyParamValue(builder.toString());
+ }
+ }
+ }
+
+ @Override
+ protected void handleAgent(final String propertyValue) {
+ // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
+ //
+ // e.g.
+ // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
+ // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
+ // ET:jfriday@host.com\nEND:VCARD\n
+ //
+ // TODO: fix this.
+ //
+ // issue:
+ // vCard 3.0 also allows this as an example.
+ //
+ // AGENT;VALUE=uri:
+ // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
+ //
+ // This is not vCard. Should we support this?
+ //
+ // Just ignore the line for now, since we cannot know how to handle it...
+ if (!mEmittedAgentWarning) {
+ Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
+ mEmittedAgentWarning = true;
+ }
+ }
+
+ /**
+ * vCard 3.0 does not require two CRLF at the last of BASE64 data.
+ * It only requires that data should be MIME-encoded.
+ */
+ @Override
+ protected String getBase64(final String firstString)
+ throws IOException, VCardException {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(firstString);
+
+ while (true) {
+ final String line = getLine();
+ if (line == null) {
+ throw new VCardException("File ended during parsing BASE64 binary");
+ }
+ if (line.length() == 0) {
+ break;
+ } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
+ mPreviousLine = line;
+ break;
+ }
+ builder.append(line);
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
+ * ; \\ encodes \, \n or \N encodes newline
+ * ; \; encodes ;, \, encodes ,
+ *
+ * Note: Apple escapes ':' into '\:' while does not escape '\'
+ */
+ @Override
+ protected String maybeUnescapeText(final String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(final String text) {
+ StringBuilder builder = new StringBuilder();
+ final int length = text.length();
+ for (int i = 0; i < length; i++) {
+ char ch = text.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ final char next_ch = text.charAt(++i);
+ if (next_ch == 'n' || next_ch == 'N') {
+ builder.append("\n");
+ } else {
+ builder.append(next_ch);
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ protected String maybeUnescapeCharacter(final char ch) {
+ return unescapeCharacter(ch);
+ }
+
+ public static String unescapeCharacter(final char ch) {
+ if (ch == 'n' || ch == 'N') {
+ return "\n";
+ } else {
+ return String.valueOf(ch);
+ }
+ }
+
+ @Override
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V30.sKnownPropertyNameSet;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V40.java b/core/java/android/pim/vcard/VCardParserImpl_V40.java
new file mode 100644
index 000000000000..0fe76bbd39fc
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V40.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.pim.vcard;
+
+import java.util.Set;
+
+
+/**
+ * <p>
+ * Basic implementation parsing vCard 4.0.
+ * </p>
+ * <p>
+ * vCard 4.0 is not published yet. Also this implementation is premature.
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V40 extends VCardParserImpl_V30 {
+ // private static final String LOG_TAG = "VCardParserImpl_V40";
+
+ public VCardParserImpl_V40() {
+ super();
+ }
+
+ public VCardParserImpl_V40(final int vcardType) {
+ super(vcardType);
+ }
+
+ @Override
+ protected int getVersion() {
+ return VCardConfig.VERSION_40;
+ }
+
+ @Override
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V40;
+ }
+
+ /**
+ * We escape "\N" into new line for safety.
+ */
+ @Override
+ protected String maybeUnescapeText(final String text) {
+ return unescapeText(text);
+ }
+
+ public static String unescapeText(final String text) {
+ // TODO: more strictly, vCard 4.0 requires different type of unescaping rule
+ // toward each property.
+ final StringBuilder builder = new StringBuilder();
+ final int length = text.length();
+ for (int i = 0; i < length; i++) {
+ char ch = text.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ final char next_ch = text.charAt(++i);
+ if (next_ch == 'n' || next_ch == 'N') {
+ builder.append("\n");
+ } else {
+ builder.append(next_ch);
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ public static String unescapeCharacter(final char ch) {
+ if (ch == 'n' || ch == 'N') {
+ return "\n";
+ } else {
+ return String.valueOf(ch);
+ }
+ }
+
+ @Override
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V40.sKnownPropertyNameSet;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index fe8cfb07776c..507a1763f377 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,922 +15,95 @@
*/
package android.pim.vcard;
-import android.pim.vcard.exception.VCardAgentNotSupportedException;
import android.pim.vcard.exception.VCardException;
-import android.pim.vcard.exception.VCardInvalidCommentLineException;
-import android.pim.vcard.exception.VCardInvalidLineException;
-import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardVersionException;
-import android.util.Log;
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
- * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail.
+ * </p>
+ * vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
+ * </p>
+ * <p>
+ * The spec is written in 1996, and currently various types of "vCard 2.1" exist.
+ * To handle real the world vCard formats appropriately and effectively, this class does not
+ * obey with strict vCard 2.1.
+ * In stead, not only vCard spec but also real world vCard is considered.
+ * </p>
+ * e.g. A lot of devices and softwares let vCard importer/exporter to use
+ * the PNG format to determine the type of image, while it is not allowed in
+ * the original specification. As of 2010, we can see even the FLV format
+ * (possible in Japanese mobile phones).
+ * </p>
*/
-public class VCardParser_V21 extends VCardParser {
- private static final String LOG_TAG = "VCardParser_V21";
-
- /** Store the known-type */
- private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
- Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
- "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
- "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
- "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
- "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
- "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
- "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
- "WAVE", "AIFF", "PCM", "X509", "PGP"));
-
- /** Store the known-value */
- private static final HashSet<String> sKnownValueSet = new HashSet<String>(
- Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
-
- /** Store the property names available in vCard 2.1 */
- private static final HashSet<String> sAvailablePropertyNameSetV21 =
- new HashSet<String>(Arrays.asList(
- "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
- "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
- "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
-
+public final class VCardParser_V21 implements VCardParser {
/**
- * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
- * We allow it for safety...
- */
- private static final HashSet<String> sAvailableEncodingV21 =
- new HashSet<String>(Arrays.asList(
- "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
-
- // Used only for parsing END:VCARD.
- private String mPreviousLine;
-
- /** The builder to build parsed data */
- protected VCardInterpreter mBuilder = null;
-
- /**
- * The encoding type. "Encoding" in vCard is different from "Charset".
- * e.g. 7BIT, 8BIT, QUOTED-PRINTABLE.
+ * A unmodifiable Set storing the property names available in the vCard 2.1 specification.
*/
- protected String mEncoding = null;
-
- protected final String sDefaultEncoding = "8BIT";
-
- // Should not directly read a line from this object. Use getLine() instead.
- protected BufferedReader mReader;
-
- // In some cases, vCard is nested. Currently, we only consider the most interior vCard data.
- // See v21_foma_1.vcf in test directory for more information.
- private int mNestCount;
-
- // In order to reduce warning message as much as possible, we hold the value which made Logger
- // emit a warning message.
- protected Set<String> mUnknownTypeMap = new HashSet<String>();
- protected Set<String> mUnknownValueMap = new HashSet<String>();
-
- // For measuring performance.
- private long mTimeTotal;
- private long mTimeReadStartRecord;
- private long mTimeReadEndRecord;
- private long mTimeStartProperty;
- private long mTimeEndProperty;
- private long mTimeParseItems;
- private long mTimeParseLineAndHandleGroup;
- private long mTimeParsePropertyValues;
- private long mTimeParseAdrOrgN;
- private long mTimeHandleMiscPropertyValue;
- private long mTimeHandleQuotedPrintable;
- private long mTimeHandleBase64;
-
- public VCardParser_V21() {
- this(null);
- }
-
- public VCardParser_V21(VCardSourceDetector detector) {
- this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
- }
-
- public VCardParser_V21(int parseType) {
- super(parseType);
- if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
- mNestCount = 1;
- }
- }
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
+ "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
/**
- * Parses the file at the given position.
- *
- * vcard_file = [wsls] vcard [wsls]
+ * A unmodifiable Set storing the types known in vCard 2.1.
*/
- protected void parseVCardFile() throws IOException, VCardException {
- boolean firstReading = true;
- while (true) {
- if (mCanceled) {
- break;
- }
- if (!parseOneVCard(firstReading)) {
- break;
- }
- firstReading = false;
- }
-
- if (mNestCount > 0) {
- boolean useCache = true;
- for (int i = 0; i < mNestCount; i++) {
- readEndVCard(useCache, true);
- useCache = false;
- }
- }
- }
-
- protected int getVersion() {
- return VCardConfig.FLAG_V21;
- }
-
- protected String getVersionString() {
- return VCardConstants.VERSION_V21;
- }
+ /* package */ static final Set<String> sKnownTypeSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
+ "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
+ "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
+ "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
+ "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
+ "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
+ "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
+ "WAVE", "AIFF", "PCM", "X509", "PGP")));
/**
- * @return true when the propertyName is a valid property name.
+ * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
*/
- protected boolean isValidPropertyName(String propertyName) {
- if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
- propertyName.startsWith("X-")) &&
- !mUnknownTypeMap.contains(propertyName)) {
- mUnknownTypeMap.add(propertyName);
- Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
- }
- return true;
- }
+ /* package */ static final Set<String> sKnownValueSet =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
/**
- * @return true when the encoding is a valid encoding.
- */
- protected boolean isValidEncoding(String encoding) {
- return sAvailableEncodingV21.contains(encoding.toUpperCase());
- }
-
- /**
- * @return String. It may be null, or its length may be 0
- * @throws IOException
- */
- protected String getLine() throws IOException {
- return mReader.readLine();
- }
-
- /**
- * @return String with it's length > 0
- * @throws IOException
- * @throws VCardException when the stream reached end of line
- */
- protected String getNonEmptyLine() throws IOException, VCardException {
- String line;
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException("Reached end of buffer.");
- } else if (line.trim().length() > 0) {
- return line;
- }
- }
- }
-
- /**
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF
- * "END" [ws] ":" [ws] "VCARD"
- */
- private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
- boolean allowGarbage = false;
- if (firstReading) {
- if (mNestCount > 0) {
- for (int i = 0; i < mNestCount; i++) {
- if (!readBeginVCard(allowGarbage)) {
- return false;
- }
- allowGarbage = true;
- }
- }
- }
-
- if (!readBeginVCard(allowGarbage)) {
- return false;
- }
- long start;
- if (mBuilder != null) {
- start = System.currentTimeMillis();
- mBuilder.startEntry();
- mTimeReadStartRecord += System.currentTimeMillis() - start;
- }
- start = System.currentTimeMillis();
- parseItems();
- mTimeParseItems += System.currentTimeMillis() - start;
- readEndVCard(true, false);
- if (mBuilder != null) {
- start = System.currentTimeMillis();
- mBuilder.endEntry();
- mTimeReadEndRecord += System.currentTimeMillis() - start;
- }
- return true;
- }
-
- /**
- * @return True when successful. False when reaching the end of line
- * @throws IOException
- * @throws VCardException
- */
- protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
- String line;
- do {
- while (true) {
- line = getLine();
- if (line == null) {
- return false;
- } else if (line.trim().length() > 0) {
- break;
- }
- }
- String[] strArray = line.split(":", 2);
- int length = strArray.length;
-
- // Though vCard 2.1/3.0 specification does not allow lower cases,
- // vCard file emitted by some external vCard expoter have such invalid Strings.
- // So we allow it.
- // e.g. BEGIN:vCard
- if (length == 2 &&
- strArray[0].trim().equalsIgnoreCase("BEGIN") &&
- strArray[1].trim().equalsIgnoreCase("VCARD")) {
- return true;
- } else if (!allowGarbage) {
- if (mNestCount > 0) {
- mPreviousLine = line;
- return false;
- } else {
- throw new VCardException(
- "Expected String \"BEGIN:VCARD\" did not come "
- + "(Instead, \"" + line + "\" came)");
- }
- }
- } while(allowGarbage);
-
- throw new VCardException("Reached where must not be reached.");
- }
-
- /**
- * The arguments useCache and allowGarbase are usually true and false accordingly when
- * this function is called outside this function itself.
- *
- * @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine()
- * is used.
- * @param allowGarbage When true, ignore non "END:VCARD" line.
- * @throws IOException
- * @throws VCardException
- */
- protected void readEndVCard(boolean useCache, boolean allowGarbage)
- throws IOException, VCardException {
- String line;
- do {
- if (useCache) {
- // Though vCard specification does not allow lower cases,
- // some data may have them, so we allow it.
- line = mPreviousLine;
- } else {
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException("Expected END:VCARD was not found.");
- } else if (line.trim().length() > 0) {
- break;
- }
- }
- }
-
- String[] strArray = line.split(":", 2);
- if (strArray.length == 2 &&
- strArray[0].trim().equalsIgnoreCase("END") &&
- strArray[1].trim().equalsIgnoreCase("VCARD")) {
- return;
- } else if (!allowGarbage) {
- throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
- }
- useCache = false;
- } while (allowGarbage);
- }
-
- /**
- * items = *CRLF item
- * / item
- */
- protected void parseItems() throws IOException, VCardException {
- boolean ended = false;
-
- if (mBuilder != null) {
- long start = System.currentTimeMillis();
- mBuilder.startProperty();
- mTimeStartProperty += System.currentTimeMillis() - start;
- }
- ended = parseItem();
- if (mBuilder != null && !ended) {
- long start = System.currentTimeMillis();
- mBuilder.endProperty();
- mTimeEndProperty += System.currentTimeMillis() - start;
- }
-
- while (!ended) {
- // follow VCARD ,it wont reach endProperty
- if (mBuilder != null) {
- long start = System.currentTimeMillis();
- mBuilder.startProperty();
- mTimeStartProperty += System.currentTimeMillis() - start;
- }
- try {
- ended = parseItem();
- } catch (VCardInvalidCommentLineException e) {
- Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
- ended = false;
- }
- if (mBuilder != null && !ended) {
- long start = System.currentTimeMillis();
- mBuilder.endProperty();
- mTimeEndProperty += System.currentTimeMillis() - start;
- }
- }
- }
-
- /**
- * item = [groups "."] name [params] ":" value CRLF
- * / [groups "."] "ADR" [params] ":" addressparts CRLF
- * / [groups "."] "ORG" [params] ":" orgparts CRLF
- * / [groups "."] "N" [params] ":" nameparts CRLF
- * / [groups "."] "AGENT" [params] ":" vcard CRLF
- */
- protected boolean parseItem() throws IOException, VCardException {
- mEncoding = sDefaultEncoding;
-
- final String line = getNonEmptyLine();
- long start = System.currentTimeMillis();
-
- String[] propertyNameAndValue = separateLineAndHandleGroup(line);
- if (propertyNameAndValue == null) {
- return true;
- }
- if (propertyNameAndValue.length != 2) {
- throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
- }
- String propertyName = propertyNameAndValue[0].toUpperCase();
- String propertyValue = propertyNameAndValue[1];
-
- mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
-
- if (propertyName.equals("ADR") || propertyName.equals("ORG") ||
- propertyName.equals("N")) {
- start = System.currentTimeMillis();
- handleMultiplePropertyValue(propertyName, propertyValue);
- mTimeParseAdrOrgN += System.currentTimeMillis() - start;
- return false;
- } else if (propertyName.equals("AGENT")) {
- handleAgent(propertyValue);
- return false;
- } else if (isValidPropertyName(propertyName)) {
- if (propertyName.equals("BEGIN")) {
- if (propertyValue.equals("VCARD")) {
- throw new VCardNestedException("This vCard has nested vCard data in it.");
- } else {
- throw new VCardException("Unknown BEGIN type: " + propertyValue);
- }
- } else if (propertyName.equals("VERSION") &&
- !propertyValue.equals(getVersionString())) {
- throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersionString());
- }
- start = System.currentTimeMillis();
- handlePropertyValue(propertyName, propertyValue);
- mTimeParsePropertyValues += System.currentTimeMillis() - start;
- return false;
- }
-
- throw new VCardException("Unknown property name: \"" + propertyName + "\"");
- }
-
- static private final int STATE_GROUP_OR_PROPNAME = 0;
- static private final int STATE_PARAMS = 1;
- // vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not.
- // This is just for safety.
- static private final int STATE_PARAMS_IN_DQUOTE = 2;
-
- protected String[] separateLineAndHandleGroup(String line) throws VCardException {
- int state = STATE_GROUP_OR_PROPNAME;
- int nameIndex = 0;
-
- final String[] propertyNameAndValue = new String[2];
-
- final int length = line.length();
- if (length > 0 && line.charAt(0) == '#') {
- throw new VCardInvalidCommentLineException();
- }
-
- for (int i = 0; i < length; i++) {
- char ch = line.charAt(i);
- switch (state) {
- case STATE_GROUP_OR_PROPNAME: {
- if (ch == ':') {
- final String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
- }
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
- }
- return propertyNameAndValue;
- } else if (ch == '.') {
- String groupName = line.substring(nameIndex, i);
- if (mBuilder != null) {
- mBuilder.propertyGroup(groupName);
- }
- nameIndex = i + 1;
- } else if (ch == ';') {
- String propertyName = line.substring(nameIndex, i);
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
- return null;
- }
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- }
- propertyNameAndValue[0] = propertyName;
- nameIndex = i + 1;
- state = STATE_PARAMS;
- }
- break;
- }
- case STATE_PARAMS: {
- if (ch == '"') {
- state = STATE_PARAMS_IN_DQUOTE;
- } else if (ch == ';') {
- handleParams(line.substring(nameIndex, i));
- nameIndex = i + 1;
- } else if (ch == ':') {
- handleParams(line.substring(nameIndex, i));
- if (i < length - 1) {
- propertyNameAndValue[1] = line.substring(i + 1);
- } else {
- propertyNameAndValue[1] = "";
- }
- return propertyNameAndValue;
- }
- break;
- }
- case STATE_PARAMS_IN_DQUOTE: {
- if (ch == '"') {
- state = STATE_PARAMS;
- }
- break;
- }
- }
- }
-
- throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
- }
-
- /**
- * params = ";" [ws] paramlist
- * paramlist = paramlist [ws] ";" [ws] param
- * / param
- * param = "TYPE" [ws] "=" [ws] ptypeval
- * / "VALUE" [ws] "=" [ws] pvalueval
- * / "ENCODING" [ws] "=" [ws] pencodingval
- * / "CHARSET" [ws] "=" [ws] charsetval
- * / "LANGUAGE" [ws] "=" [ws] langval
- * / "X-" word [ws] "=" [ws] word
- * / knowntype
- */
- protected void handleParams(String params) throws VCardException {
- String[] strArray = params.split("=", 2);
- if (strArray.length == 2) {
- final String paramName = strArray[0].trim().toUpperCase();
- String paramValue = strArray[1].trim();
- if (paramName.equals("TYPE")) {
- handleType(paramValue);
- } else if (paramName.equals("VALUE")) {
- handleValue(paramValue);
- } else if (paramName.equals("ENCODING")) {
- handleEncoding(paramValue);
- } else if (paramName.equals("CHARSET")) {
- handleCharset(paramValue);
- } else if (paramName.equals("LANGUAGE")) {
- handleLanguage(paramValue);
- } else if (paramName.startsWith("X-")) {
- handleAnyParam(paramName, paramValue);
- } else {
- throw new VCardException("Unknown type \"" + paramName + "\"");
- }
- } else {
- handleParamWithoutName(strArray[0]);
- }
- }
-
- /**
- * vCard 3.0 parser may throw VCardException.
- */
- @SuppressWarnings("unused")
- protected void handleParamWithoutName(final String paramValue) throws VCardException {
- handleType(paramValue);
- }
-
- /**
- * ptypeval = knowntype / "X-" word
- */
- protected void handleType(final String ptypeval) {
- String upperTypeValue = ptypeval;
- if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
- !mUnknownTypeMap.contains(ptypeval)) {
- mUnknownTypeMap.add(ptypeval);
- Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("TYPE");
- mBuilder.propertyParamValue(upperTypeValue);
- }
- }
-
- /**
- * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
- */
- protected void handleValue(final String pvalueval) {
- if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
- pvalueval.startsWith("X-") &&
- !mUnknownValueMap.contains(pvalueval)) {
- mUnknownValueMap.add(pvalueval);
- Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("VALUE");
- mBuilder.propertyParamValue(pvalueval);
- }
- }
-
- /**
- * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
- */
- protected void handleEncoding(String pencodingval) throws VCardException {
- if (isValidEncoding(pencodingval) ||
- pencodingval.startsWith("X-")) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("ENCODING");
- mBuilder.propertyParamValue(pencodingval);
- }
- mEncoding = pencodingval;
- } else {
- throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
- }
- }
-
- /**
- * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
- * but today's vCard often contains other charset, so we allow them.
- */
- protected void handleCharset(String charsetval) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("CHARSET");
- mBuilder.propertyParamValue(charsetval);
- }
- }
-
- /**
- * See also Section 7.1 of RFC 1521
- */
- protected void handleLanguage(String langval) throws VCardException {
- String[] strArray = langval.split("-");
- if (strArray.length != 2) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- String tmp = strArray[0];
- int length = tmp.length();
- for (int i = 0; i < length; i++) {
- if (!isLetter(tmp.charAt(i))) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- }
- tmp = strArray[1];
- length = tmp.length();
- for (int i = 0; i < length; i++) {
- if (!isLetter(tmp.charAt(i))) {
- throw new VCardException("Invalid Language: \"" + langval + "\"");
- }
- }
- if (mBuilder != null) {
- mBuilder.propertyParamType("LANGUAGE");
- mBuilder.propertyParamValue(langval);
- }
- }
-
- /**
- * Mainly for "X-" type. This accepts any kind of type without check.
- */
- protected void handleAnyParam(String paramName, String paramValue) {
- if (mBuilder != null) {
- mBuilder.propertyParamType(paramName);
- mBuilder.propertyParamValue(paramValue);
- }
- }
-
- protected void handlePropertyValue(String propertyName, String propertyValue)
- throws IOException, VCardException {
- if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- final long start = System.currentTimeMillis();
- final String result = getQuotedPrintable(propertyValue);
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(result);
- mBuilder.propertyValues(v);
- }
- mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
- } else if (mEncoding.equalsIgnoreCase("BASE64") ||
- mEncoding.equalsIgnoreCase("B")) {
- final long start = System.currentTimeMillis();
- // It is very rare, but some BASE64 data may be so big that
- // OutOfMemoryError occurs. To ignore such cases, use try-catch.
- try {
- final String result = getBase64(propertyValue);
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(result);
- mBuilder.propertyValues(v);
- }
- } catch (OutOfMemoryError error) {
- Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
- if (mBuilder != null) {
- mBuilder.propertyValues(null);
- }
- }
- mTimeHandleBase64 += System.currentTimeMillis() - start;
- } else {
- if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
- || mEncoding.equalsIgnoreCase("8BIT")
- || mEncoding.toUpperCase().startsWith("X-"))) {
- Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
- }
-
- final long start = System.currentTimeMillis();
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(maybeUnescapeText(propertyValue));
- mBuilder.propertyValues(v);
- }
- mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
- }
- }
-
- protected String getQuotedPrintable(String firstString) throws IOException, VCardException {
- // Specifically, there may be some padding between = and CRLF.
- // See the following:
- //
- // qp-line := *(qp-segment transport-padding CRLF)
- // qp-part transport-padding
- // qp-segment := qp-section *(SPACE / TAB) "="
- // ; Maximum length of 76 characters
- //
- // e.g. (from RFC 2045)
- // Now's the time =
- // for all folk to come=
- // to the aid of their country.
- if (firstString.trim().endsWith("=")) {
- // remove "transport-padding"
- int pos = firstString.length() - 1;
- while(firstString.charAt(pos) != '=') {
- }
- StringBuilder builder = new StringBuilder();
- builder.append(firstString.substring(0, pos + 1));
- builder.append("\r\n");
- String line;
- while (true) {
- line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing quoted-printable String");
- }
- if (line.trim().endsWith("=")) {
- // remove "transport-padding"
- pos = line.length() - 1;
- while(line.charAt(pos) != '=') {
- }
- builder.append(line.substring(0, pos + 1));
- builder.append("\r\n");
- } else {
- builder.append(line);
- break;
- }
- }
- return builder.toString();
- } else {
- return firstString;
- }
- }
-
- protected String getBase64(String firstString) throws IOException, VCardException {
- StringBuilder builder = new StringBuilder();
- builder.append(firstString);
-
- while (true) {
- String line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing BASE64 binary");
- }
- if (line.length() == 0) {
- break;
- }
- builder.append(line);
- }
-
- return builder.toString();
- }
-
- /**
- * Mainly for "ADR", "ORG", and "N"
- * We do not care the number of strnosemi here.
- *
- * addressparts = 0*6(strnosemi ";") strnosemi
- * ; PO Box, Extended Addr, Street, Locality, Region,
- * Postal Code, Country Name
- * orgparts = *(strnosemi ";") strnosemi
- * ; First is Organization Name,
- * remainder are Organization Units.
- * nameparts = 0*4(strnosemi ";") strnosemi
- * ; Family, Given, Middle, Prefix, Suffix.
- * ; Example:Public;John;Q.;Reverend Dr.;III, Esq.
- * strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi
- * ; To include a semicolon in this string, it must be escaped
- * ; with a "\" character.
- *
- * We are not sure whether we should add "\" CRLF to each value.
- * For now, we exclude them.
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
+ * </p>
+ * <p>
+ * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
+ * We allow it for safety.
+ * </p>
*/
- protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
- throws IOException, VCardException {
- // vCard 2.1 does not allow QUOTED-PRINTABLE here,
- // but some softwares/devices emit such data.
- if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
- propertyValue = getQuotedPrintable(propertyValue);
- }
-
- if (mBuilder != null) {
- mBuilder.propertyValues(VCardUtils.constructListFromValue(
- propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
- }
- }
+ /* package */ static final Set<String> sAvailableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_QP,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
- /**
- * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
- *
- * item = ...
- * / [groups "."] "AGENT"
- * [params] ":" vcard CRLF
- * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
- * items *CRLF "END" [ws] ":" [ws] "VCARD"
- */
- protected void handleAgent(final String propertyValue) throws VCardException {
- if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
- // Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
- return;
- } else {
- throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
- }
- // TODO: Support AGENT property.
- }
-
- /**
- * For vCard 3.0.
- */
- protected String maybeUnescapeText(final String text) {
- return text;
- }
+ private final VCardParserImpl_V21 mVCardParserImpl;
- /**
- * Returns unescaped String if the character should be unescaped. Return null otherwise.
- * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be.
- */
- protected String maybeUnescapeCharacter(final char ch) {
- return unescapeCharacter(ch);
+ public VCardParser_V21() {
+ mVCardParserImpl = new VCardParserImpl_V21();
}
- public static String unescapeCharacter(final char ch) {
- // Original vCard 2.1 specification does not allow transformation
- // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of
- // this class allowed them, so keep it as is.
- if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
- return String.valueOf(ch);
- } else {
- return null;
- }
- }
-
- @Override
- public boolean parse(final InputStream is, final VCardInterpreter builder)
- throws IOException, VCardException {
- return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
+ public VCardParser_V21(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V21(vcardType);
}
-
- @Override
- public boolean parse(InputStream is, String charset, VCardInterpreter builder)
- throws IOException, VCardException {
- if (charset == null) {
- charset = VCardConfig.DEFAULT_CHARSET;
- }
- final InputStreamReader tmpReader = new InputStreamReader(is, charset);
- if (VCardConfig.showPerformanceLog()) {
- mReader = new CustomBufferedReader(tmpReader);
- } else {
- mReader = new BufferedReader(tmpReader);
- }
-
- mBuilder = builder;
- long start = System.currentTimeMillis();
- if (mBuilder != null) {
- mBuilder.start();
- }
- parseVCardFile();
- if (mBuilder != null) {
- mBuilder.end();
- }
- mTimeTotal += System.currentTimeMillis() - start;
-
- if (VCardConfig.showPerformanceLog()) {
- showPerformanceInfo();
- }
-
- return true;
- }
-
- @Override
- public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- mCanceled = canceled;
- parse(is, charset, builder);
- }
-
- private void showPerformanceInfo() {
- Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
- if (mReader instanceof CustomBufferedReader) {
- Log.d(LOG_TAG, "Total readLine time: " +
- ((CustomBufferedReader)mReader).getTotalmillisecond() + " ms");
- }
- Log.d(LOG_TAG, "Time for handling the beggining of the record: " +
- mTimeReadStartRecord + " ms");
- Log.d(LOG_TAG, "Time for handling the end of the record: " +
- mTimeReadEndRecord + " ms");
- Log.d(LOG_TAG, "Time for parsing line, and handling group: " +
- mTimeParseLineAndHandleGroup + " ms");
- Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
- Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
- Log.d(LOG_TAG, "Time for handling normal property values: " +
- mTimeHandleMiscPropertyValue + " ms");
- Log.d(LOG_TAG, "Time for handling Quoted-Printable: " +
- mTimeHandleQuotedPrintable + " ms");
- Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
+ mVCardParserImpl.parse(is, interepreter);
}
- private boolean isLetter(char ch) {
- if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
- return true;
- }
- return false;
- }
-}
-
-class CustomBufferedReader extends BufferedReader {
- private long mTime;
-
- public CustomBufferedReader(Reader in) {
- super(in);
- }
-
- @Override
- public String readLine() throws IOException {
- long start = System.currentTimeMillis();
- String ret = super.readLine();
- long end = System.currentTimeMillis();
- mTime += end - start;
- return ret;
- }
-
- public long getTotalmillisecond() {
- return mTime;
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 4ecfe9750c66..238d3a8a51de 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -16,343 +16,72 @@
package android.pim.vcard;
import android.pim.vcard.exception.VCardException;
-import android.util.Log;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Set;
/**
- * The class used to parse vCard 3.0.
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
+ * <p>
+ * vCard parser for vCard 3.0. See RFC 2426 for more detail.
+ * </p>
+ * <p>
+ * This parser allows vCard format which is not allowed in the RFC, since
+ * we have seen several vCard 3.0 files which don't comply with it.
+ * </p>
+ * <p>
+ * e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
+ * have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
+ * but it is not a must. We silently allow "CHARSET".
+ * </p>
*/
-public class VCardParser_V30 extends VCardParser_V21 {
- private static final String LOG_TAG = "VCardParser_V30";
-
- private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
- Arrays.asList(
+public class VCardParser_V30 implements VCardParser {
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
- "SORT-STRING", "CATEGORIES", "PRODID")); // 3.0
-
- // Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety.
- private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>(
- Arrays.asList("7BIT", "8BIT", "BASE64", "B"));
-
- // Although RFC 2426 specifies some property must not have parameters, we allow it,
- // since there may be some careers which violates the RFC...
- private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
-
- private String mPreviousLine;
-
- private boolean mEmittedAgentWarning = false;
+ "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
/**
- * True when the caller wants the parser to be strict about the input.
- * Currently this is only for testing.
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
+ * </p>
+ * <p>
+ * Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
+ * </p>
+ * <p>
+ * "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
+ * because the encoding ambiguates how the vCard file to be parsed.
+ * </p>
*/
- private final boolean mStrictParsing;
+ /* package */ static final Set<String> sAcceptableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
- public VCardParser_V30() {
- super();
- mStrictParsing = false;
- }
-
- /**
- * @param strictParsing when true, this object throws VCardException when the vcard is not
- * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
- * is not fully yet for being used with this flag and may not notice invalid line(s).
- *
- * @hide currently only for testing!
- */
- public VCardParser_V30(boolean strictParsing) {
- super();
- mStrictParsing = strictParsing;
- }
-
- public VCardParser_V30(int parseMode) {
- super(parseMode);
- mStrictParsing = false;
- }
-
- @Override
- protected int getVersion() {
- return VCardConfig.FLAG_V30;
- }
+ private final VCardParserImpl_V30 mVCardParserImpl;
- @Override
- protected String getVersionString() {
- return VCardConstants.VERSION_V30;
- }
-
- @Override
- protected boolean isValidPropertyName(String propertyName) {
- if (!(sAcceptablePropsWithParam.contains(propertyName) ||
- acceptablePropsWithoutParam.contains(propertyName) ||
- propertyName.startsWith("X-")) &&
- !mUnknownTypeMap.contains(propertyName)) {
- mUnknownTypeMap.add(propertyName);
- Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
- }
- return true;
- }
-
- @Override
- protected boolean isValidEncoding(String encoding) {
- return sAcceptableEncodingV30.contains(encoding.toUpperCase());
+ public VCardParser_V30() {
+ mVCardParserImpl = new VCardParserImpl_V30();
}
- @Override
- protected String getLine() throws IOException {
- if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- } else {
- return mReader.readLine();
- }
- }
-
- /**
- * vCard 3.0 requires that the line with space at the beginning of the line
- * must be combined with previous line.
- */
- @Override
- protected String getNonEmptyLine() throws IOException, VCardException {
- String line;
- StringBuilder builder = null;
- while (true) {
- line = mReader.readLine();
- if (line == null) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
- throw new VCardException("Reached end of buffer.");
- } else if (line.length() == 0) {
- if (builder != null) {
- return builder.toString();
- } else if (mPreviousLine != null) {
- String ret = mPreviousLine;
- mPreviousLine = null;
- return ret;
- }
- } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
- if (builder != null) {
- // See Section 5.8.1 of RFC 2425 (MIME-DIR document).
- // Following is the excerpts from it.
- //
- // DESCRIPTION:This is a long description that exists on a long line.
- //
- // Can be represented as:
- //
- // DESCRIPTION:This is a long description
- // that exists on a long line.
- //
- // It could also be represented as:
- //
- // DESCRIPTION:This is a long descrip
- // tion that exists o
- // n a long line.
- builder.append(line.substring(1));
- } else if (mPreviousLine != null) {
- builder = new StringBuilder();
- builder.append(mPreviousLine);
- mPreviousLine = null;
- builder.append(line.substring(1));
- } else {
- throw new VCardException("Space exists at the beginning of the line");
- }
- } else {
- if (mPreviousLine == null) {
- mPreviousLine = line;
- if (builder != null) {
- return builder.toString();
- }
- } else {
- String ret = mPreviousLine;
- mPreviousLine = line;
- return ret;
- }
- }
- }
- }
-
-
- /**
- * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
- * 1 * (contentline)
- * ;A vCard object MUST include the VERSION, FN and N types.
- * [group "."] "END" ":" "VCARD" 1 * CRLF
- */
- @Override
- protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
- // TODO: vCard 3.0 supports group.
- return super.readBeginVCard(allowGarbage);
+ public VCardParser_V30(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V30(vcardType);
}
- @Override
- protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- // TODO: vCard 3.0 supports group.
- super.readEndVCard(useCache, allowGarbage);
- }
-
- /**
- * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
- */
- @Override
- protected void handleParams(String params) throws VCardException {
- try {
- super.handleParams(params);
- } catch (VCardException e) {
- // maybe IANA type
- String[] strArray = params.split("=", 2);
- if (strArray.length == 2) {
- handleAnyParam(strArray[0], strArray[1]);
- } else {
- // Must not come here in the current implementation.
- throw new VCardException(
- "Unknown params value: " + params);
- }
- }
- }
-
- @Override
- protected void handleAnyParam(String paramName, String paramValue) {
- super.handleAnyParam(paramName, paramValue);
- }
-
- @Override
- protected void handleParamWithoutName(final String paramValue) throws VCardException {
- if (mStrictParsing) {
- throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
- } else {
- super.handleParamWithoutName(paramValue);
- }
- }
-
- /**
- * vCard 3.0 defines
- *
- * param = param-name "=" param-value *("," param-value)
- * param-name = iana-token / x-name
- * param-value = ptext / quoted-string
- * quoted-string = DQUOTE QSAFE-CHAR DQUOTE
- */
- @Override
- protected void handleType(String ptypevalues) {
- String[] ptypeArray = ptypevalues.split(",");
- mBuilder.propertyParamType("TYPE");
- for (String value : ptypeArray) {
- int length = value.length();
- if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
- mBuilder.propertyParamValue(value.substring(1, value.length() - 1));
- } else {
- mBuilder.propertyParamValue(value);
- }
- }
- }
-
- @Override
- protected void handleAgent(String propertyValue) {
- // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
- //
- // e.g.
- // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
- // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
- // ET:jfriday@host.com\nEND:VCARD\n
- //
- // TODO: fix this.
- //
- // issue:
- // vCard 3.0 also allows this as an example.
- //
- // AGENT;VALUE=uri:
- // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
- //
- // This is not vCard. Should we support this?
- //
- // Just ignore the line for now, since we cannot know how to handle it...
- if (!mEmittedAgentWarning) {
- Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
- mEmittedAgentWarning = true;
- }
- }
-
- /**
- * vCard 3.0 does not require two CRLF at the last of BASE64 data.
- * It only requires that data should be MIME-encoded.
- */
- @Override
- protected String getBase64(String firstString) throws IOException, VCardException {
- StringBuilder builder = new StringBuilder();
- builder.append(firstString);
-
- while (true) {
- String line = getLine();
- if (line == null) {
- throw new VCardException(
- "File ended during parsing BASE64 binary");
- }
- if (line.length() == 0) {
- break;
- } else if (!line.startsWith(" ") && !line.startsWith("\t")) {
- mPreviousLine = line;
- break;
- }
- builder.append(line);
- }
-
- return builder.toString();
- }
-
- /**
- * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
- * ; \\ encodes \, \n or \N encodes newline
- * ; \; encodes ;, \, encodes ,
- *
- * Note: Apple escapes ':' into '\:' while does not escape '\'
- */
- @Override
- protected String maybeUnescapeText(String text) {
- return unescapeText(text);
- }
-
- public static String unescapeText(String text) {
- StringBuilder builder = new StringBuilder();
- int length = text.length();
- for (int i = 0; i < length; i++) {
- char ch = text.charAt(i);
- if (ch == '\\' && i < length - 1) {
- char next_ch = text.charAt(++i);
- if (next_ch == 'n' || next_ch == 'N') {
- builder.append("\n");
- } else {
- builder.append(next_ch);
- }
- } else {
- builder.append(ch);
- }
- }
- return builder.toString();
- }
-
- @Override
- protected String maybeUnescapeCharacter(char ch) {
- return unescapeCharacter(ch);
+ mVCardParserImpl.parse(is, interepreter);
}
- public static String unescapeCharacter(char ch) {
- if (ch == 'n' || ch == 'N') {
- return "\n";
- } else {
- return String.valueOf(ch);
- }
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardParser_V40.java b/core/java/android/pim/vcard/VCardParser_V40.java
new file mode 100644
index 000000000000..65a2f6863e6f
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParser_V40.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.pim.vcard;
+
+import android.pim.vcard.exception.VCardException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <p>
+ * vCard parser for vCard 4.0.
+ * </p>
+ * <p>
+ * Currently this parser is based on vCard 4.0 specification rev 11.
+ * </p>
+ */
+public class VCardParser_V40 implements VCardParser {
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ "BEGIN", "END", "SOURCE", "NAME", "KIND", "XML",
+ "FN", "N", "NICKNAME", "PHOTO", "BDAY", "DDAY",
+ "BIRTH", "DEATH", "ANNIVERSARY", "SEX", "ADR",
+ "LABEL", "TEL", "EMAIL", "IMPP", "LANG", "TZ",
+ "GEO", "TITLE", "ROLE", "LOGO", "ORG", "MEMBER",
+ "RELATED", "CATEGORIES", "NOTE", "PRODID",
+ "REV", "SOUND", "UID", "CLIENTPIDMAP",
+ "URL", "VERSION", "CLASS", "KEY", "FBURL", "CALENDRURI",
+ "CALURI")));
+
+ /**
+ * <p>
+ * A unmodifiable Set storing the values for the type "ENCODING", available in vCard 4.0.
+ * </p>
+ */
+ /* package */ static final Set<String> sAcceptableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_B)));
+
+ private final VCardParserImpl_V30 mVCardParserImpl;
+
+ public VCardParser_V40() {
+ mVCardParserImpl = new VCardParserImpl_V40();
+ }
+
+ public VCardParser_V40(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V40(vcardType);
+ }
+
+ @Override
+ public void parse(InputStream is, VCardInterpreter interepreter)
+ throws IOException, VCardException {
+ mVCardParserImpl.parse(is, interepreter);
+ }
+
+ @Override
+ public void cancel() {
+ mVCardParserImpl.cancel();
+ }
+} \ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
index 7297c500690b..4c6461e98ea9 100644
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ b/core/java/android/pim/vcard/VCardSourceDetector.java
@@ -15,17 +15,33 @@
*/
package android.pim.vcard;
+import android.text.TextUtils;
+import android.util.Log;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
- * Class which tries to detects the source of the vCard from its properties.
- * Currently this implementation is very premature.
- * @hide
+ * <p>
+ * The class which tries to detects the source of a vCard file from its contents.
+ * </p>
+ * <p>
+ * The specification of vCard (including both 2.1 and 3.0) is not so strict as to
+ * guess its format just by reading beginning few lines (usually we can, but in
+ * some most pessimistic case, we cannot until at almost the end of the file).
+ * Also we cannot store all vCard entries in memory, while there's no specification
+ * how big the vCard entry would become after the parse.
+ * </p>
+ * <p>
+ * This class is usually used for the "first scan", in which we can understand which vCard
+ * version is used (and how many entries exist in a file).
+ * </p>
*/
public class VCardSourceDetector implements VCardInterpreter {
+ private static final String LOG_TAG = "VCardSourceDetector";
+
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
"X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
"X-ABADR", "X-ABUID"));
@@ -42,10 +58,30 @@ public class VCardSourceDetector implements VCardInterpreter {
"X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
"X-SD-DESCRIPTION"));
private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
-
- private int mType = VCardConfig.PARSE_TYPE_UNKNOWN;
+
+ /**
+ * Represents that no estimation is available. Users of this class is able to this
+ * constant when you don't want to let a vCard parser rely on estimation for parse type.
+ */
+ public static final int PARSE_TYPE_UNKNOWN = 0;
+
+ // For Apple's software, which does not mean this type is effective for all its products.
+ // We confirmed they usually use UTF-8, but not sure about vCard type.
+ private static final int PARSE_TYPE_APPLE = 1;
+ // For Japanese mobile phones, which are usually using Shift_JIS as a charset.
+ private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
+ // For some of mobile phones released from DoCoMo, which use nested vCard.
+ private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
+ // For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
+ private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
+
+ private int mParseType = 0; // Not sure.
+
+ private boolean mNeedToParseVersion = false;
+ private int mVersion = -1; // -1 == unknown
+
// Some mobile phones (like FOMA) tells us the charset of the data.
- private boolean mNeedParseSpecifiedCharset;
+ private boolean mNeedToParseCharset;
private String mSpecifiedCharset;
public void start() {
@@ -58,7 +94,8 @@ public class VCardSourceDetector implements VCardInterpreter {
}
public void startProperty() {
- mNeedParseSpecifiedCharset = false;
+ mNeedToParseCharset = false;
+ mNeedToParseVersion = false;
}
public void endProperty() {
@@ -71,22 +108,26 @@ public class VCardSourceDetector implements VCardInterpreter {
}
public void propertyName(String name) {
- if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
- mType = VCardConfig.PARSE_TYPE_FOMA;
- mNeedParseSpecifiedCharset = true;
+ if (name.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)) {
+ mNeedToParseVersion = true;
+ return;
+ } else if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
+ mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
+ // Probably Shift_JIS is used, but we should double confirm.
+ mNeedToParseCharset = true;
return;
}
- if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
+ if (mParseType != PARSE_TYPE_UNKNOWN) {
return;
}
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP;
+ mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
} else if (FOMA_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_FOMA;
+ mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP;
+ mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
} else if (APPLE_SIGNS.contains(name)) {
- mType = VCardConfig.PARSE_TYPE_APPLE;
+ mParseType = PARSE_TYPE_APPLE;
}
}
@@ -97,30 +138,65 @@ public class VCardSourceDetector implements VCardInterpreter {
}
public void propertyValues(List<String> values) {
- if (mNeedParseSpecifiedCharset && values.size() > 0) {
+ if (mNeedToParseVersion && values.size() > 0) {
+ final String versionString = values.get(0);
+ if (versionString.equals(VCardConstants.VERSION_V21)) {
+ mVersion = VCardConfig.VERSION_21;
+ } else if (versionString.equals(VCardConstants.VERSION_V30)) {
+ mVersion = VCardConfig.VERSION_30;
+ } else if (versionString.equals(VCardConstants.VERSION_V40)) {
+ mVersion = VCardConfig.VERSION_40;
+ } else {
+ Log.w(LOG_TAG, "Invalid version string: " + versionString);
+ }
+ } else if (mNeedToParseCharset && values.size() > 0) {
mSpecifiedCharset = values.get(0);
}
}
- /* package */ int getEstimatedType() {
- return mType;
+ /**
+ * @return The available type can be used with vCard parser. You probably need to
+ * use {{@link #getEstimatedCharset()} to understand the charset to be used.
+ */
+ public int getEstimatedType() {
+ switch (mParseType) {
+ case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+ return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
+ case PARSE_TYPE_MOBILE_PHONE_JP:
+ return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
+ case PARSE_TYPE_APPLE:
+ case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+ default: {
+ if (mVersion == VCardConfig.VERSION_21) {
+ return VCardConfig.VCARD_TYPE_V21_GENERIC;
+ } else if (mVersion == VCardConfig.VERSION_30) {
+ return VCardConfig.VCARD_TYPE_V30_GENERIC;
+ } else if (mVersion == VCardConfig.VERSION_40) {
+ return VCardConfig.VCARD_TYPE_V40_GENERIC;
+ } else {
+ return VCardConfig.VCARD_TYPE_UNKNOWN;
+ }
+ }
+ }
}
-
+
/**
- * Return charset String guessed from the source's properties.
+ * <p>
+ * Returns charset String guessed from the source's properties.
* This method must be called after parsing target file(s).
+ * </p>
* @return Charset String. Null is returned if guessing the source fails.
*/
public String getEstimatedCharset() {
- if (mSpecifiedCharset != null) {
+ if (TextUtils.isEmpty(mSpecifiedCharset)) {
return mSpecifiedCharset;
}
- switch (mType) {
- case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
- case VCardConfig.PARSE_TYPE_FOMA:
- case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
+ switch (mParseType) {
+ case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+ case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+ case PARSE_TYPE_MOBILE_PHONE_JP:
return "SHIFT_JIS";
- case VCardConfig.PARSE_TYPE_APPLE:
+ case PARSE_TYPE_APPLE:
return "UTF-8";
default:
return null;
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 11b112b76dbb..abceca0cb476 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -16,13 +16,21 @@
package android.pim.vcard;
import android.content.ContentProviderOperation;
-import android.provider.ContactsContract.Data;
+import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Log;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -36,6 +44,8 @@ import java.util.Set;
* Utilities for VCard handling codes.
*/
public class VCardUtils {
+ private static final String LOG_TAG = "VCardUtils";
+
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
// converted to two parameter Strings. These only contain some minor fields valid in both
// vCard and current (as of 2009-08-07) Contacts structure.
@@ -185,8 +195,7 @@ public class VCardUtils {
// For backward compatibility.
// Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
// To support mobile type at that time, this custom label had been used.
- return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label)
- || sMobilePhoneLabelSet.contains(label));
+ return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
}
public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
@@ -240,10 +249,13 @@ public class VCardUtils {
}
/**
+ * <p>
* Inserts postal data into the builder object.
- *
+ * </p>
+ * <p>
* Note that the data structure of ContactsContract is different from that defined in vCard.
* So some conversion may be performed in this method.
+ * </p>
*/
public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
final ContentProviderOperation.Builder builder,
@@ -319,18 +331,33 @@ public class VCardUtils {
return builder.toString();
}
+ /**
+ * Splits the given value into pieces using the delimiter ';' inside it.
+ *
+ * Escaped characters in those values are automatically unescaped into original form.
+ */
public static List<String> constructListFromValue(final String value,
- final boolean isV30) {
+ final int vcardType) {
final List<String> list = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
- int length = value.length();
+ final int length = value.length();
for (int i = 0; i < length; i++) {
char ch = value.charAt(i);
if (ch == '\\' && i < length - 1) {
char nextCh = value.charAt(i + 1);
- final String unescapedString =
- (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
- VCardParser_V21.unescapeCharacter(nextCh));
+ final String unescapedString;
+ if (VCardConfig.isVersion40(vcardType)) {
+ unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh);
+ } else if (VCardConfig.isVersion30(vcardType)) {
+ unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh);
+ } else {
+ if (!VCardConfig.isVersion21(vcardType)) {
+ // Unknown vCard type
+ Log.w(LOG_TAG, "Unknown vCard type");
+ }
+ unescapedString = VCardParserImpl_V21.unescapeCharacter(nextCh);
+ }
+
if (unescapedString != null) {
builder.append(unescapedString);
i++;
@@ -371,9 +398,13 @@ public class VCardUtils {
}
/**
+ * <p>
* This is useful when checking the string should be encoded into quoted-printable
* or not, which is required by vCard 2.1.
+ * </p>
+ * <p>
* See the definition of "7bit" in vCard 2.1 spec for more information.
+ * </p>
*/
public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
if (values == null) {
@@ -407,13 +438,16 @@ public class VCardUtils {
new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
/**
+ * <p>
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen.
- *
+ * </p>
+ * <p>
* Note: It is already known some devices (wrongly) outputs properties with characters
* which should not be in the field. One example is "X-GOOGLE TALK". We accept
* such kind of input but must never output it unless the target is very specific
- * to the device which is able to parse the malformed input.
+ * to the device which is able to parse the malformed input.
+ * </p>
*/
public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
if (values == null) {
@@ -451,14 +485,39 @@ public class VCardUtils {
return true;
}
+ public static boolean containsOnlyWhiteSpaces(final String...values) {
+ if (values == null) {
+ return true;
+ }
+ return containsOnlyWhiteSpaces(Arrays.asList(values));
+ }
+
+ public static boolean containsOnlyWhiteSpaces(final Collection<String> values) {
+ if (values == null) {
+ return true;
+ }
+ for (final String str : values) {
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ final int length = str.length();
+ for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
+ if (!Character.isWhitespace(str.codePointAt(i))) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
/**
- * <P>
+ * <p>
* Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
- * </P>
- * <P>
- * vCard 2.1 specifies:<BR />
+ * </p>
+ * <p>
+ * vCard 2.1 specifies:<br />
* word = &lt;any printable 7bit us-ascii except []=:., &gt;
- * </P>
+ * </p>
*/
public static boolean isV21Word(final String value) {
if (TextUtils.isEmpty(value)) {
@@ -477,6 +536,66 @@ public class VCardUtils {
return true;
}
+ private static final int[] sEscapeIndicatorsV30 = new int[]{
+ ':', ';', ',', ' '
+ };
+
+ private static final int[] sEscapeIndicatorsV40 = new int[]{
+ ';', ':'
+ };
+
+ /**
+ * <P>
+ * Returns String available as parameter value in vCard 3.0.
+ * </P>
+ * <P>
+ * RFC 2426 requires vCard composer to quote parameter values when it contains
+ * semi-colon, for example (See RFC 2426 for more information).
+ * This method checks whether the given String can be used without quotes.
+ * </P>
+ * <P>
+ * Note: We remove DQUOTE inside the given value silently for now.
+ * </P>
+ */
+ public static String toStringAsV30ParamValue(String value) {
+ return toStringAsParamValue(value, sEscapeIndicatorsV30);
+ }
+
+ public static String toStringAsV40ParamValue(String value) {
+ return toStringAsParamValue(value, sEscapeIndicatorsV40);
+ }
+
+ private static String toStringAsParamValue(String value, final int[] escapeIndicators) {
+ if (TextUtils.isEmpty(value)) {
+ value = "";
+ }
+ final int asciiFirst = 0x20;
+ final int asciiLast = 0x7E; // included
+ final StringBuilder builder = new StringBuilder();
+ final int length = value.length();
+ boolean needQuote = false;
+ for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) {
+ final int codePoint = value.codePointAt(i);
+ if (codePoint < asciiFirst || codePoint == '"') {
+ // CTL characters and DQUOTE are never accepted. Remove them.
+ continue;
+ }
+ builder.appendCodePoint(codePoint);
+ for (int indicator : escapeIndicators) {
+ if (codePoint == indicator) {
+ needQuote = true;
+ break;
+ }
+ }
+ }
+
+ final String result = builder.toString();
+ return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result))
+ ? ""
+ : (needQuote ? ('"' + result + '"')
+ : result));
+ }
+
public static String toHalfWidthString(final String orgString) {
if (TextUtils.isEmpty(orgString)) {
return null;
@@ -540,6 +659,138 @@ public class VCardUtils {
return true;
}
+ //// The methods bellow may be used by unit test.
+
+ /**
+ * Unquotes given Quoted-Printable value. value must not be null.
+ */
+ public static String parseQuotedPrintable(
+ final String value, boolean strictLineBreaking,
+ String sourceCharset, String targetCharset) {
+ // "= " -> " ", "=\t" -> "\t".
+ // Previous code had done this replacement. Keep on the safe side.
+ final String quotedPrintable;
+ {
+ final StringBuilder builder = new StringBuilder();
+ final int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch == '=' && i < length - 1) {
+ char nextCh = value.charAt(i + 1);
+ if (nextCh == ' ' || nextCh == '\t') {
+ builder.append(nextCh);
+ i++;
+ continue;
+ }
+ }
+ builder.append(ch);
+ }
+ quotedPrintable = builder.toString();
+ }
+
+ String[] lines;
+ if (strictLineBreaking) {
+ lines = quotedPrintable.split("\r\n");
+ } else {
+ StringBuilder builder = new StringBuilder();
+ final int length = quotedPrintable.length();
+ ArrayList<String> list = new ArrayList<String>();
+ for (int i = 0; i < length; i++) {
+ char ch = quotedPrintable.charAt(i);
+ if (ch == '\n') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else if (ch == '\r') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ if (i < length - 1) {
+ char nextCh = quotedPrintable.charAt(i + 1);
+ if (nextCh == '\n') {
+ i++;
+ }
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ final String lastLine = builder.toString();
+ if (lastLine.length() > 0) {
+ list.add(lastLine);
+ }
+ lines = list.toArray(new String[0]);
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ for (String line : lines) {
+ if (line.endsWith("=")) {
+ line = line.substring(0, line.length() - 1);
+ }
+ builder.append(line);
+ }
+
+ final String rawString = builder.toString();
+ if (TextUtils.isEmpty(rawString)) {
+ Log.w(LOG_TAG, "Given raw string is empty.");
+ }
+
+ byte[] rawBytes = null;
+ try {
+ rawBytes = rawString.getBytes(sourceCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
+ rawBytes = rawString.getBytes();
+ }
+
+ byte[] decodedBytes = null;
+ try {
+ decodedBytes = QuotedPrintableCodec.decodeQuotedPrintable(rawBytes);
+ } catch (DecoderException e) {
+ Log.e(LOG_TAG, "DecoderException is thrown.");
+ decodedBytes = rawBytes;
+ }
+
+ try {
+ return new String(decodedBytes, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(decodedBytes);
+ }
+ }
+
+ public static final VCardParser getAppropriateParser(int vcardType)
+ throws VCardException {
+ if (VCardConfig.isVersion21(vcardType)) {
+ return new VCardParser_V21();
+ } else if (VCardConfig.isVersion30(vcardType)) {
+ return new VCardParser_V30();
+ } else if (VCardConfig.isVersion40(vcardType)) {
+ return new VCardParser_V40();
+ } else {
+ throw new VCardException("Version is not specified");
+ }
+ }
+
+ public static final String convertStringCharset(
+ String originalString, String sourceCharset, String targetCharset) {
+ if (sourceCharset.equalsIgnoreCase(targetCharset)) {
+ return originalString;
+ }
+ final Charset charset = Charset.forName(sourceCharset);
+ final ByteBuffer byteBuffer = charset.encode(originalString);
+ // byteBuffer.array() "may" return byte array which is larger than
+ // byteBuffer.remaining(). Here, we keep on the safe side.
+ final byte[] bytes = new byte[byteBuffer.remaining()];
+ byteBuffer.get(bytes);
+ try {
+ return new String(bytes, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return null;
+ }
+ }
+
+ // TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean
+
private VCardUtils() {
}
}
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
index 330153ec2638..b80584beca7d 100644
--- a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
+++ b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.pim.vcard.exception;
/**
diff --git a/core/java/android/pim/vcard/exception/VCardVersionException.java b/core/java/android/pim/vcard/exception/VCardVersionException.java
index 9fe8b7f92af9..0709fe4113ee 100644
--- a/core/java/android/pim/vcard/exception/VCardVersionException.java
+++ b/core/java/android/pim/vcard/exception/VCardVersionException.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.pim.vcard.exception;
/**
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
index cc48aeb70844..bbad2b6d432c 100644
--- a/core/java/android/preference/DialogPreference.java
+++ b/core/java/android/preference/DialogPreference.java
@@ -33,7 +33,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
/**
@@ -275,7 +274,7 @@ public abstract class DialogPreference extends Preference implements
protected void showDialog(Bundle state) {
Context context = getContext();
- mWhichButtonClicked = DialogInterface.BUTTON2;
+ mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
mBuilder = new AlertDialog.Builder(context)
.setTitle(mDialogTitle)
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 197d976777c6..dde6493fb331 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -1195,7 +1195,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis
private void tryCommit(SharedPreferences.Editor editor) {
if (mPreferenceManager.shouldCommit()) {
- editor.commit();
+ editor.apply();
}
}
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
index 2b2094693f78..fa838976fed9 100644
--- a/core/java/android/preference/PreferenceManager.java
+++ b/core/java/android/preference/PreferenceManager.java
@@ -443,7 +443,7 @@ public class PreferenceManager {
pm.setSharedPreferencesMode(sharedPreferencesMode);
pm.inflateFromResource(context, resId, null);
- defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).commit();
+ defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).apply();
}
}
@@ -481,7 +481,7 @@ public class PreferenceManager {
private void setNoCommit(boolean noCommit) {
if (!noCommit && mEditor != null) {
- mEditor.commit();
+ mEditor.apply();
}
mNoCommit = noCommit;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 40a408a12ee8..13cbda8df636 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -409,6 +409,13 @@ public final class ContactsContract {
public static final String CONTACT_PRESENCE = "contact_presence";
/**
+ * Contact Chat Capabilities. See {@link StatusUpdates} for individual
+ * definitions.
+ * <p>Type: NUMBER</p>
+ */
+ public static final String CONTACT_CHAT_CAPABILITY = "contact_chat_capability";
+
+ /**
* Contact's latest status update.
* <p>Type: TEXT</p>
*/
@@ -1950,6 +1957,29 @@ public final class ContactsContract {
* <p>Type: NUMBER</p>
*/
public static final String STATUS_ICON = "status_icon";
+
+ /**
+ * Contact's audio/video chat capability level.
+ * <P>Type: INTEGER (one of the values below)</P>
+ */
+ public static final String CHAT_CAPABILITY = "chat_capability";
+
+ /**
+ * An allowed value of {@link #CHAT_CAPABILITY}. Indicates that the contact's device can
+ * display a video feed.
+ */
+ public static final int CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY = 1;
+
+ /**
+ * An allowed value of {@link #CHAT_CAPABILITY}. Indicates audio-chat capability.
+ */
+ public static final int CAPABILITY_HAS_VOICE = 2;
+
+ /**
+ * An allowed value of {@link #CHAT_CAPABILITY}. Indicates that the contact's device has a
+ * camera that can be used for video chat (e.g. a front-facing camera on a phone).
+ */
+ public static final int CAPABILITY_HAS_CAMERA = 4;
}
/**
@@ -2275,6 +2305,7 @@ public final class ContactsContract {
* <li>{@link CommonDataKinds.Website Website.CONTENT_ITEM_TYPE}</li>
* <li>{@link CommonDataKinds.Event Event.CONTENT_ITEM_TYPE}</li>
* <li>{@link CommonDataKinds.Relation Relation.CONTENT_ITEM_TYPE}</li>
+ * <li>{@link CommonDataKinds.SipAddress SipAddress.CONTENT_ITEM_TYPE}</li>
* </ul>
* </p>
* </td>
@@ -3095,6 +3126,25 @@ public final class ContactsContract {
* </td>
* </tr>
* <tr>
+ * <td>int</td>
+ * <td>{@link #CHAT_CAPABILITY}</td>
+ * <td>read/write</td>
+ * <td>Contact IM chat compatibility value. The allowed values are:
+ * <p>
+ * <ul>
+ * <li>{@link #CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY}</li>
+ * <li>{@link #CAPABILITY_HAS_VOICE}</li>
+ * <li>{@link #CAPABILITY_HAS_CAMERA}</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Since chat compatibility is inherently volatile as the contact's availability moves from
+ * one device to another, the content provider may choose not to store this field in long-term
+ * storage.
+ * </p>
+ * </td>
+ * </tr>
+ * <tr>
* <td>String</td>
* <td>{@link #STATUS}</td>
* <td>read/write</td>
@@ -4811,6 +4861,52 @@ public final class ContactsContract {
*/
public static final String URL = DATA;
}
+
+ /**
+ * <p>
+ * A data kind representing a SIP address for the contact.
+ * </p>
+ * <p>
+ * You can use all columns defined for {@link ContactsContract.Data} as
+ * well as the following aliases.
+ * </p>
+ * <h2>Column aliases</h2>
+ * <table class="jd-sumtable">
+ * <tr>
+ * <th>Type</th>
+ * <th>Alias</th><th colspan='2'>Data column</th>
+ * </tr>
+ * <tr>
+ * <td>String</td>
+ * <td>{@link #SIP_ADDRESS}</td>
+ * <td>{@link #DATA1}</td>
+ * <td></td>
+ * </tr>
+ * </table>
+ */
+ public static final class SipAddress implements DataColumnsWithJoins {
+ // TODO: Ultimately this class will probably implement
+ // CommonColumns too (in addition to DataColumnsWithJoins)
+ // since it may make sense to have multiple SIP addresses with
+ // different types+labels, just like with phone numbers.
+ //
+ // But that can be extended in the future without breaking any
+ // public API, so let's keep this class ultra-simple for now.
+
+ /**
+ * This utility class cannot be instantiated
+ */
+ private SipAddress() {}
+
+ /** MIME type used when storing this in data table. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address";
+
+ /**
+ * The SIP address.
+ * <P>Type: TEXT</P>
+ */
+ public static final String SIP_ADDRESS = DATA1;
+ }
}
/**
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 348e9e8a63aa..603e5983871e 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -17,11 +17,6 @@
package android.provider;
import android.net.Uri;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-
-import java.io.File;
/**
* The Download Manager
@@ -629,12 +624,17 @@ public final class Downloads {
"android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS";
/**
- * The permission to access downloads to {@link DESTINATION_EXTERNAL}
- * which were downloaded by other applications.
- * @hide
+ * The permission to download files to the cache partition that won't be automatically
+ * purged when space is needed.
*/
- public static final String PERMISSION_SEE_ALL_EXTERNAL =
- "android.permission.SEE_ALL_EXTERNAL";
+ public static final String PERMISSION_CACHE_NON_PURGEABLE =
+ "android.permission.DOWNLOAD_CACHE_NON_PURGEABLE";
+
+ /**
+ * The permission to download files without any system notification being shown.
+ */
+ public static final String PERMISSION_NO_NOTIFICATION =
+ "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION";
/**
* The content:// URI for the data table in the provider
@@ -856,6 +856,38 @@ public final class Downloads {
*/
public static final String COLUMN_DESCRIPTION = "description";
+ /**
+ * The name of the column indicating whether the download was requesting through the public
+ * API. This controls some differences in behavior.
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_IS_PUBLIC_API = "is_public_api";
+
+ /**
+ * The name of the column indicating whether roaming connections can be used. This is only
+ * used for public API downloads.
+ * <P>Type: BOOLEAN</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_ALLOW_ROAMING = "allow_roaming";
+
+ /**
+ * The name of the column holding a bitmask of allowed network types. This is only used for
+ * public API downloads.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types";
+
+ /**
+ * Whether or not this download should be displayed in the system's Downloads UI. Defaults
+ * to true.
+ * <P>Type: INTEGER</P>
+ * <P>Owner can Init/Read</P>
+ */
+ public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui";
+
/*
* Lists the destinations that an application can specify for a download.
*/
@@ -899,6 +931,12 @@ public final class Downloads {
public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3;
/**
+ * This download will be saved to the location given by the file URI in
+ * {@link #COLUMN_FILE_NAME_HINT}.
+ */
+ public static final int DESTINATION_FILE_URI = 4;
+
+ /**
* This download is allowed to run.
*/
public static final int CONTROL_RUN = 0;
@@ -1030,6 +1068,16 @@ public final class Downloads {
public static final int STATUS_PRECONDITION_FAILED = 412;
/**
+ * The lowest-valued error status that is not an actual HTTP status code.
+ */
+ public static final int MIN_ARTIFICIAL_ERROR_STATUS = 489;
+
+ /**
+ * Some possibly transient error occurred, but we can't resume the download.
+ */
+ public static final int STATUS_CANNOT_RESUME = 489;
+
+ /**
* This download was canceled
*/
public static final int STATUS_CANCELED = 490;
@@ -1109,5 +1157,26 @@ public final class Downloads {
* This download doesn't show in the UI or in the notifications.
*/
public static final int VISIBILITY_HIDDEN = 2;
+
+ /**
+ * Constants related to HTTP request headers associated with each download.
+ */
+ public static class RequestHeaders {
+ public static final String HEADERS_DB_TABLE = "request_headers";
+ public static final String COLUMN_DOWNLOAD_ID = "download_id";
+ public static final String COLUMN_HEADER = "header";
+ public static final String COLUMN_VALUE = "value";
+
+ /**
+ * Path segment to add to a download URI to retrieve request headers
+ */
+ public static final String URI_SEGMENT = "headers";
+
+ /**
+ * Prefix for ContentValues keys that contain HTTP header lines, to be passed to
+ * DownloadProvider.insert().
+ */
+ public static final String INSERT_KEY_PREFIX = "http_header_";
+ }
}
}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index c9d125b9159f..075da338cb39 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -75,6 +75,22 @@ public final class MediaStore {
public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
/**
+ * An intent to perform a search for music media and automatically play content from the
+ * result when possible. This can be fired, for example, by the result of a voice recognition
+ * command to listen to music.
+ * <p>
+ * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string
+ * that can contain any type of unstructured music search, like the name of an artist,
+ * an album, a song, a genre, or any combination of these.
+ * <p>
+ * Because this intent includes an open-ended unstructured search string, it makes the most
+ * sense for apps that can support large-scale search of music, such as services connected
+ * to an online database of music which can be streamed and played on the device.
+ */
+ public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
+ "android.media.action.MEDIA_PLAY_FROM_SEARCH";
+
+ /**
* The name of the Intent-extra used to define the artist
*/
public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
@@ -249,6 +265,8 @@ public final class MediaStore {
private static final int MICRO_KIND = 3;
private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
static final int DEFAULT_GROUP_ID = 0;
+ private static final Object sThumbBufLock = new Object();
+ private static byte[] sThumbBuf;
private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) {
Bitmap bitmap = null;
@@ -321,11 +339,15 @@ public final class MediaStore {
long magic = thumbFile.getMagic(origId);
if (magic != 0) {
if (kind == MICRO_KIND) {
- byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
- if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
- bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap == null) {
- Log.w(TAG, "couldn't decode byte array.");
+ synchronized (sThumbBufLock) {
+ if (sThumbBuf == null) {
+ sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
+ }
+ if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
+ bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
+ if (bitmap == null) {
+ Log.w(TAG, "couldn't decode byte array.");
+ }
}
}
return bitmap;
@@ -357,11 +379,15 @@ public final class MediaStore {
// Assuming thumbnail has been generated, at least original image exists.
if (kind == MICRO_KIND) {
- byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
- if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
- bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap == null) {
- Log.w(TAG, "couldn't decode byte array.");
+ synchronized (sThumbBufLock) {
+ if (sThumbBuf == null) {
+ sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
+ }
+ if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
+ bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
+ if (bitmap == null) {
+ Log.w(TAG, "couldn't decode byte array.");
+ }
}
}
} else if (kind == MINI_KIND) {
@@ -1819,4 +1845,12 @@ public final class MediaStore {
* Name of current volume being scanned by the media scanner.
*/
public static final String MEDIA_SCANNER_VOLUME = "volume";
+
+ /**
+ * Name of the file signaling the media scanner to ignore media in the containing directory
+ * and its subdirectories. Developers should use this to avoid application graphics showing
+ * up in the Gallery and likewise prevent application sounds and music from showing up in
+ * the Music app.
+ */
+ public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e12dfb06087e..fd601159981d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -355,6 +355,21 @@ public final class Settings {
"android.settings.MANAGE_APPLICATIONS_SETTINGS";
/**
+ * Activity Action: Show screen of details about a particular application.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: The Intent's data URI specifies the application package name
+ * to be shown, with the "package" scheme. That is "package:com.my.app".
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APPLICATION_DETAILS_SETTINGS =
+ "android.settings.APPLICATION_DETAILS_SETTINGS";
+
+ /**
* Activity Action: Show settings for system update functionality.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -1679,6 +1694,41 @@ public final class Settings {
public static final String UNLOCK_SOUND = "unlock_sound";
/**
+ * Receive incoming SIP calls?
+ * 0 = no
+ * 1 = yes
+ * @hide
+ */
+ public static final String SIP_RECEIVE_CALLS = "sip_receive_calls";
+
+ /**
+ * Call Preference String.
+ * "SIP_ALWAYS" : Always use SIP with network access
+ * "SIP_ADDRESS_ONLY" : Only if destination is a SIP address
+ * "SIP_ASK_ME_EACH_TIME" : Always ask me each time
+ * @hide
+ */
+ public static final String SIP_CALL_OPTIONS = "sip_call_options";
+
+ /**
+ * One of the sip call options: Always use SIP with network access.
+ * @hide
+ */
+ public static final String SIP_ALWAYS = "SIP_ALWAYS";
+
+ /**
+ * One of the sip call options: Only if destination is a SIP address.
+ * @hide
+ */
+ public static final String SIP_ADDRESS_ONLY = "SIP_ADDRESS_ONLY";
+
+ /**
+ * One of the sip call options: Always ask me each time.
+ * @hide
+ */
+ public static final String SIP_ASK_ME_EACH_TIME = "SIP_ASK_ME_EACH_TIME";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
* @hide
@@ -1738,7 +1788,9 @@ public final class Settings {
DOCK_SOUNDS_ENABLED,
LOCKSCREEN_SOUNDS_ENABLED,
SHOW_WEB_SUGGESTIONS,
- NOTIFICATION_LIGHT_PULSE
+ NOTIFICATION_LIGHT_PULSE,
+ SIP_CALL_OPTIONS,
+ SIP_RECEIVE_CALLS
};
// Settings moved to Settings.Secure
@@ -2909,31 +2961,31 @@ public final class Settings {
public static final String WTF_IS_FATAL = "wtf_is_fatal";
/**
- * Maximum age of entries kept by {@link android.os.IDropBox}.
+ * Maximum age of entries kept by {@link com.android.internal.os.IDropBoxManagerService}.
* @hide
*/
public static final String DROPBOX_AGE_SECONDS =
"dropbox_age_seconds";
/**
- * Maximum number of entry files which {@link android.os.IDropBox} will keep around.
+ * Maximum number of entry files which {@link com.android.internal.os.IDropBoxManagerService} will keep around.
* @hide
*/
public static final String DROPBOX_MAX_FILES =
"dropbox_max_files";
/**
- * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what.
+ * Maximum amount of disk space used by {@link com.android.internal.os.IDropBoxManagerService} no matter what.
* @hide
*/
public static final String DROPBOX_QUOTA_KB =
"dropbox_quota_kb";
/**
- * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use.
+ * Percent of free disk (excluding reserve) which {@link com.android.internal.os.IDropBoxManagerService} will use.
* @hide
*/
public static final String DROPBOX_QUOTA_PERCENT =
"dropbox_quota_percent";
/**
- * Percent of total disk which {@link android.os.IDropBox} will never dip into.
+ * Percent of total disk which {@link com.android.internal.os.IDropBoxManagerService} will never dip into.
* @hide
*/
public static final String DROPBOX_RESERVE_PERCENT =
@@ -2993,6 +3045,15 @@ public final class Settings {
"sys_storage_threshold_percentage";
/**
+ * Minimum bytes of free storage on the device before the data
+ * partition is considered full. By default, 1 MB is reserved
+ * to avoid system-wide SQLite disk full exceptions.
+ * @hide
+ */
+ public static final String SYS_STORAGE_FULL_THRESHOLD_BYTES =
+ "sys_storage_full_threshold_bytes";
+
+ /**
* The interval in milliseconds after which Wi-Fi is considered idle.
* When idle, it is possible for the device to be switched from Wi-Fi to
* the mobile data network.
@@ -3358,6 +3419,29 @@ public final class Settings {
public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC =
"throttle_max_ntp_cache_age_sec";
+ /**
+ * The maximum size, in bytes, of a download that the download manager will transfer over
+ * a non-wifi connection.
+ * @hide
+ */
+ public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE =
+ "download_manager_max_bytes_over_mobile";
+
+ /**
+ * ms during which to consume extra events related to Inet connection condition
+ * after a transtion to fully-connected
+ * @hide
+ */
+ public static final String INET_CONDITION_DEBOUNCE_UP_DELAY =
+ "inet_condition_debounce_up_delay";
+
+ /**
+ * ms during which to consume extra events related to Inet connection condtion
+ * after a transtion to partly-connected
+ * @hide
+ */
+ public static final String INET_CONDITION_DEBOUNCE_DOWN_DELAY =
+ "inet_condition_debounce_down_delay";
/**
* @hide
@@ -3397,13 +3481,7 @@ public final class Settings {
*/
public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED);
- if (allowedProviders != null) {
- return (allowedProviders.equals(provider) ||
- allowedProviders.contains("," + provider + ",") ||
- allowedProviders.startsWith(provider + ",") ||
- allowedProviders.endsWith("," + provider));
- }
- return false;
+ return TextUtils.delimitedStringContains(allowedProviders, ',', provider);
}
/**
diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java
index 893db2e748c6..a52a221e01aa 100644
--- a/core/java/android/server/BluetoothA2dpService.java
+++ b/core/java/android/server/BluetoothA2dpService.java
@@ -27,7 +27,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothA2dp;
-import android.os.ParcelUuid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -35,6 +34,7 @@ import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
+import android.os.ParcelUuid;
import android.provider.Settings;
import android.util.Log;
@@ -55,8 +55,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private static final String BLUETOOTH_ENABLED = "bluetooth_enabled";
- private static final int MESSAGE_CONNECT_TO = 1;
-
private static final String PROPERTY_STATE = "State";
private static final String SINK_STATE_DISCONNECTED = "disconnected";
@@ -73,6 +71,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
private final BluetoothService mBluetoothService;
private final BluetoothAdapter mAdapter;
private int mTargetA2dpState;
+ private boolean mAdjustedPriority = false;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -104,16 +103,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED);
break;
}
- } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) {
- if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
- isSinkDevice(device)) {
- // This device is a preferred sink. Make an A2DP connection
- // after a delay. We delay to avoid connection collisions,
- // and to give other profiles such as HFP a chance to
- // connect first.
- Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, device);
- mHandler.sendMessageDelayed(msg, 6000);
- }
} else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
synchronized (this) {
if (mAudioDevices.containsKey(device)) {
@@ -187,6 +176,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
if (mBluetoothService.isEnabled())
onBluetoothEnable();
mTargetA2dpState = -1;
+ mBluetoothService.setA2dpService(this);
}
@Override
@@ -198,29 +188,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
}
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_CONNECT_TO:
- BluetoothDevice device = (BluetoothDevice) msg.obj;
- // check bluetooth is still on, device is still preferred, and
- // nothing is currently connected
- if (mBluetoothService.isEnabled() &&
- getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
- lookupSinksMatchingStates(new int[] {
- BluetoothA2dp.STATE_CONNECTING,
- BluetoothA2dp.STATE_CONNECTED,
- BluetoothA2dp.STATE_PLAYING,
- BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) {
- log("Auto-connecting A2DP to sink " + device);
- connectSink(device);
- }
- break;
- }
- }
- };
-
private int convertBluezSinkStringtoState(String value) {
if (value.equalsIgnoreCase("disconnected"))
return BluetoothA2dp.STATE_DISCONNECTED;
@@ -308,13 +275,37 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false");
}
+ private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) {
+ if (!mBluetoothService.isEnabled() || !isSinkDevice(device) ||
+ getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) {
+ return false;
+ }
+
+ if (mAudioDevices.get(device) == null && !addAudioSink(device)) {
+ return false;
+ }
+
+ String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ if (path == null) {
+ return false;
+ }
+ return true;
+ }
+
public synchronized boolean connectSink(BluetoothDevice device) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (DBG) log("connectSink(" + device + ")");
+ if (!isConnectSinkFeasible(device)) return false;
+ return mBluetoothService.connectSink(device.getAddress());
+ }
+
+ public synchronized boolean connectSinkInternal(BluetoothDevice device) {
if (!mBluetoothService.isEnabled()) return false;
+ int state = mAudioDevices.get(device);
+
// ignore if there are any active sinks
if (lookupSinksMatchingStates(new int[] {
BluetoothA2dp.STATE_CONNECTING,
@@ -324,11 +315,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return false;
}
- if (mAudioDevices.get(device) == null && !addAudioSink(device))
- return false;
-
- int state = mAudioDevices.get(device);
-
switch (state) {
case BluetoothA2dp.STATE_CONNECTED:
case BluetoothA2dp.STATE_PLAYING:
@@ -339,8 +325,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
- if (path == null)
- return false;
// State is DISCONNECTED
handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING);
@@ -353,11 +337,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
return true;
}
- public synchronized boolean disconnectSink(BluetoothDevice device) {
- mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
- "Need BLUETOOTH_ADMIN permission");
- if (DBG) log("disconnectSink(" + device + ")");
-
+ private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) {
String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
if (path == null) {
return false;
@@ -370,6 +350,20 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
case BluetoothA2dp.STATE_DISCONNECTING:
return true;
}
+ return true;
+ }
+
+ public synchronized boolean disconnectSink(BluetoothDevice device) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ if (DBG) log("disconnectSink(" + device + ")");
+ if (!isDisconnectSinkFeasible(device)) return false;
+ return mBluetoothService.disconnectSink(device.getAddress());
+ }
+
+ public synchronized boolean disconnectSinkInternal(BluetoothDevice device) {
+ int state = getSinkState(device);
+ String path = mBluetoothService.getObjectPathFromAddress(device.getAddress());
// State is CONNECTING or CONNECTED or PLAYING
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING);
@@ -504,6 +498,12 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT);
}
+ if (state == BluetoothA2dp.STATE_CONNECTED) {
+ // We will only have 1 device with AUTO_CONNECT priority
+ // To be backward compatible set everyone else to have PRIORITY_ON
+ adjustOtherSinkPriorities(device);
+ }
+
Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState);
@@ -514,6 +514,18 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
}
}
+ private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) {
+ if (!mAdjustedPriority) {
+ for (BluetoothDevice device : mAdapter.getBondedDevices()) {
+ if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT &&
+ !device.equals(connectedDevice)) {
+ setSinkPriority(device, BluetoothA2dp.PRIORITY_ON);
+ }
+ }
+ mAdjustedPriority = true;
+ }
+ }
+
private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) {
Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>();
if (mAudioDevices.isEmpty()) {
@@ -554,6 +566,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub {
if (!result) {
if (deviceObjectPath != null) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
+ if (address == null) return;
BluetoothDevice device = mAdapter.getRemoteDevice(address);
int state = getSinkState(device);
handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED);
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index c0e4600ae24c..e05fe7b8ce9f 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -52,22 +52,14 @@ class BluetoothEventLoop {
private final BluetoothAdapter mAdapter;
private final Context mContext;
- private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
- private static final int EVENT_RESTART_BLUETOOTH = 2;
- private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3;
- private static final int EVENT_AGENT_CANCEL = 4;
+ private static final int EVENT_RESTART_BLUETOOTH = 1;
+ private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 2;
+ private static final int EVENT_AGENT_CANCEL = 3;
private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
private static final int CREATE_DEVICE_SUCCESS = 0;
private static final int CREATE_DEVICE_FAILED = -1;
- // The time (in millisecs) to delay the pairing attempt after the first
- // auto pairing attempt fails. We use an exponential delay with
- // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
- // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
- private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
- private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
-
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
@@ -76,13 +68,6 @@ class BluetoothEventLoop {
public void handleMessage(Message msg) {
String address = null;
switch (msg.what) {
- case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
- address = (String)msg.obj;
- if (address != null) {
- mBluetoothService.createBond(address);
- return;
- }
- break;
case EVENT_RESTART_BLUETOOTH:
mBluetoothService.restart();
break;
@@ -95,8 +80,7 @@ class BluetoothEventLoop {
case EVENT_AGENT_CANCEL:
// Set the Bond State to BOND_NONE.
// We always have only 1 device in BONDING state.
- String[] devices =
- mBluetoothService.getBondState().listInState(BluetoothDevice.BOND_BONDING);
+ String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING);
if (devices.length == 0) {
break;
} else if (devices.length > 1) {
@@ -104,7 +88,7 @@ class BluetoothEventLoop {
break;
}
address = devices[0];
- mBluetoothService.getBondState().setBondState(address,
+ mBluetoothService.setBondState(address,
BluetoothDevice.BOND_NONE,
BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
break;
@@ -115,7 +99,7 @@ class BluetoothEventLoop {
static { classInitNative(); }
private static native void classInitNative();
- /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
+ /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
BluetoothService bluetoothService) {
mBluetoothService = bluetoothService;
mContext = context;
@@ -209,55 +193,7 @@ class BluetoothEventLoop {
private void onCreatePairedDeviceResult(String address, int result) {
address = address.toUpperCase();
- if (result == BluetoothDevice.BOND_SUCCESS) {
- mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
- if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
- mBluetoothService.getBondState().clearPinAttempts(address);
- }
- } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
- mBluetoothService.getBondState().getAttempt(address) == 1) {
- mBluetoothService.getBondState().addAutoPairingFailure(address);
- pairingAttempt(address, result);
- } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
- mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
- pairingAttempt(address, result);
- } else {
- mBluetoothService.getBondState().setBondState(address,
- BluetoothDevice.BOND_NONE, result);
- if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
- mBluetoothService.getBondState().clearPinAttempts(address);
- }
- }
- }
-
- private void pairingAttempt(String address, int result) {
- // This happens when our initial guess of "0000" as the pass key
- // fails. Try to create the bond again and display the pin dialog
- // to the user. Use back-off while posting the delayed
- // message. The initial value is
- // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
- // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
- // reached, display an error to the user.
- int attempt = mBluetoothService.getBondState().getAttempt(address);
- if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
- MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
- mBluetoothService.getBondState().clearPinAttempts(address);
- mBluetoothService.getBondState().setBondState(address,
- BluetoothDevice.BOND_NONE, result);
- return;
- }
-
- Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
- message.obj = address;
- boolean postResult = mHandler.sendMessageDelayed(message,
- attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
- if (!postResult) {
- mBluetoothService.getBondState().clearPinAttempts(address);
- mBluetoothService.getBondState().setBondState(address,
- BluetoothDevice.BOND_NONE, result);
- return;
- }
- mBluetoothService.getBondState().attempt(address);
+ mBluetoothService.onCreatePairedDeviceResult(address, result);
}
private void onDeviceCreated(String deviceObjectPath) {
@@ -275,8 +211,8 @@ class BluetoothEventLoop {
private void onDeviceRemoved(String deviceObjectPath) {
String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
if (address != null) {
- mBluetoothService.getBondState().setBondState(address.toUpperCase(),
- BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
+ mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE,
+ BluetoothDevice.UNBOND_REASON_REMOVED);
mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
}
}
@@ -404,10 +340,14 @@ class BluetoothEventLoop {
mBluetoothService.sendUuidIntent(address);
} else if (name.equals("Paired")) {
if (propValues[1].equals("true")) {
- mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
+ // If locally initiated pairing, we will
+ // not go to BOND_BONDED state until we have received a
+ // successful return value in onCreatePairedDeviceResult
+ if (null == mBluetoothService.getPendingOutgoingBonding()) {
+ mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED);
+ }
} else {
- mBluetoothService.getBondState().setBondState(address,
- BluetoothDevice.BOND_NONE);
+ mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE);
mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
}
} else if (name.equals("Trusted")) {
@@ -437,8 +377,8 @@ class BluetoothEventLoop {
// Also set it only when the state is not already Bonded, we can sometimes
// get an authorization request from the remote end if it doesn't have the link key
// while we still have it.
- if (mBluetoothService.getBondState().getBondState(address) != BluetoothDevice.BOND_BONDED)
- mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING);
+ if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED)
+ mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING);
return address;
}
@@ -452,7 +392,7 @@ class BluetoothEventLoop {
* so we may get this request many times. Also if we respond immediately,
* the other end is unable to handle it. Delay sending the message.
*/
- if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) {
+ if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) {
Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
message.obj = address;
mHandler.sendMessageDelayed(message, 1500);
@@ -497,7 +437,7 @@ class BluetoothEventLoop {
if (address == null) return;
String pendingOutgoingAddress =
- mBluetoothService.getBondState().getPendingOutgoingBonding();
+ mBluetoothService.getPendingOutgoingBonding();
if (address.equals(pendingOutgoingAddress)) {
// we initiated the bonding
@@ -518,12 +458,7 @@ class BluetoothEventLoop {
case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
- if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
- !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
- mBluetoothService.getBondState().attempt(address);
- mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
- return;
- }
+ if (mBluetoothService.attemptAutoPair(address)) return;
}
}
Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
@@ -545,6 +480,17 @@ class BluetoothEventLoop {
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
}
+ private void onRequestOobData(String objectPath , int nativeData) {
+ String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
+ if (address == null) return;
+
+ Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
+ intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
+ BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT);
+ mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
+ }
+
private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
String address = mBluetoothService.getAddressFromObjectPath(objectPath);
if (address == null) {
@@ -566,6 +512,7 @@ class BluetoothEventLoop {
authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
if (authorized) {
Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
+ mBluetoothService.notifyIncomingA2dpConnection(address);
} else {
Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
}
@@ -576,7 +523,21 @@ class BluetoothEventLoop {
return authorized;
}
- boolean isOtherSinkInNonDisconnectingState(String address) {
+ private boolean onAgentOutOfBandDataAvailable(String objectPath) {
+ if (!mBluetoothService.isEnabled()) return false;
+
+ String address = mBluetoothService.getAddressFromObjectPath(objectPath);
+ if (address == null) return false;
+
+ if (mBluetoothService.getDeviceOutOfBandData(
+ mAdapter.getRemoteDevice(address)) != null) {
+ return true;
+ }
+ return false;
+
+ }
+
+ private boolean isOtherSinkInNonDisconnectingState(String address) {
BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks();
if (devices.size() == 0) return false;
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index c0affd3755d6..dfe3a25fbc15 100644
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -28,6 +28,8 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothDeviceProfileState;
+import android.bluetooth.BluetoothProfileState;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetooth;
@@ -48,6 +50,7 @@ import android.os.ServiceManager;
import android.os.SystemService;
import android.provider.Settings;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.app.IBatteryStats;
@@ -101,6 +104,14 @@ public class BluetoothService extends IBluetooth.Stub {
private static final int MESSAGE_FINISH_DISABLE = 2;
private static final int MESSAGE_UUID_INTENT = 3;
private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
+ private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 5;
+
+ // The time (in millisecs) to delay the pairing attempt after the first
+ // auto pairing attempt fails. We use an exponential delay with
+ // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
+ // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
+ private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
+ private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
// The timeout used to sent the UUIDs Intent
// This timeout should be greater than the page timeout
@@ -112,7 +123,7 @@ public class BluetoothService extends IBluetooth.Stub {
BluetoothUuid.HSP,
BluetoothUuid.ObexObjectPush };
-
+ // TODO(): Optimize all these string handling
private final Map<String, String> mAdapterProperties;
private final HashMap<String, Map<String, String>> mDeviceProperties;
@@ -122,6 +133,13 @@ public class BluetoothService extends IBluetooth.Stub {
private final HashMap<Integer, Integer> mServiceRecordToPid;
+ private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
+ private final BluetoothProfileState mA2dpProfileState;
+ private final BluetoothProfileState mHfpProfileState;
+
+ private BluetoothA2dpService mA2dpService;
+ private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData;
+
private static String mDockAddress;
private String mDockPin;
@@ -176,9 +194,16 @@ public class BluetoothService extends IBluetooth.Stub {
mDeviceProperties = new HashMap<String, Map<String,String>>();
mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
+ mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
mUuidIntentTracker = new ArrayList<String>();
mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
mServiceRecordToPid = new HashMap<Integer, Integer>();
+ mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
+ mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
+ mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
+
+ mHfpProfileState.start();
+ mA2dpProfileState.start();
IntentFilter filter = new IntentFilter();
registerForAirplaneMode(filter);
@@ -187,7 +212,7 @@ public class BluetoothService extends IBluetooth.Stub {
mContext.registerReceiver(mReceiver, filter);
}
- public static synchronized String readDockBluetoothAddress() {
+ public static synchronized String readDockBluetoothAddress() {
if (mDockAddress != null) return mDockAddress;
BufferedInputStream file = null;
@@ -485,6 +510,13 @@ public class BluetoothService extends IBluetooth.Stub {
setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1);
}
break;
+ case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
+ address = (String)msg.obj;
+ if (address != null) {
+ createBond(address);
+ return;
+ }
+ break;
}
}
};
@@ -534,6 +566,7 @@ public class BluetoothService extends IBluetooth.Stub {
mIsDiscovering = false;
mBondState.readAutoPairingData();
mBondState.loadBondState();
+ initProfileState();
mHandler.sendMessageDelayed(
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
@@ -573,8 +606,68 @@ public class BluetoothService extends IBluetooth.Stub {
Binder.restoreCallingIdentity(origCallerIdentityToken);
}
- /* package */ BondState getBondState() {
- return mBondState;
+ /*package*/ synchronized boolean attemptAutoPair(String address) {
+ if (!mBondState.hasAutoPairingFailed(address) &&
+ !mBondState.isAutoPairingBlacklisted(address)) {
+ mBondState.attempt(address);
+ setPin(address, BluetoothDevice.convertPinToBytes("0000"));
+ return true;
+ }
+ return false;
+ }
+
+ /*package*/ synchronized void onCreatePairedDeviceResult(String address, int result) {
+ if (result == BluetoothDevice.BOND_SUCCESS) {
+ setBondState(address, BluetoothDevice.BOND_BONDED);
+ if (mBondState.isAutoPairingAttemptsInProgress(address)) {
+ mBondState.clearPinAttempts(address);
+ }
+ } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
+ mBondState.getAttempt(address) == 1) {
+ mBondState.addAutoPairingFailure(address);
+ pairingAttempt(address, result);
+ } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
+ mBondState.isAutoPairingAttemptsInProgress(address)) {
+ pairingAttempt(address, result);
+ } else {
+ setBondState(address, BluetoothDevice.BOND_NONE, result);
+ if (mBondState.isAutoPairingAttemptsInProgress(address)) {
+ mBondState.clearPinAttempts(address);
+ }
+ }
+ }
+
+ /*package*/ synchronized String getPendingOutgoingBonding() {
+ return mBondState.getPendingOutgoingBonding();
+ }
+
+ private void pairingAttempt(String address, int result) {
+ // This happens when our initial guess of "0000" as the pass key
+ // fails. Try to create the bond again and display the pin dialog
+ // to the user. Use back-off while posting the delayed
+ // message. The initial value is
+ // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
+ // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
+ // reached, display an error to the user.
+ int attempt = mBondState.getAttempt(address);
+ if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
+ MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
+ mBondState.clearPinAttempts(address);
+ setBondState(address, BluetoothDevice.BOND_NONE, result);
+ return;
+ }
+
+ Message message = mHandler.obtainMessage(MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+ message.obj = address;
+ boolean postResult = mHandler.sendMessageDelayed(message,
+ attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
+ if (!postResult) {
+ mBondState.clearPinAttempts(address);
+ setBondState(address,
+ BluetoothDevice.BOND_NONE, result);
+ return;
+ }
+ mBondState.attempt(address);
}
/** local cache of bonding state.
@@ -648,6 +741,12 @@ public class BluetoothService extends IBluetooth.Stub {
}
}
+ if (state == BluetoothDevice.BOND_BONDED) {
+ addProfileState(address);
+ } else if (state == BluetoothDevice.BOND_NONE) {
+ removeProfileState(address);
+ }
+
if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
reason + ")");
Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
@@ -899,7 +998,7 @@ public class BluetoothService extends IBluetooth.Stub {
Log.e(TAG, "Error:Adapter Property at index" + i + "is null");
continue;
}
- if (name.equals("Devices")) {
+ if (name.equals("Devices") || name.equals("UUIDs")) {
StringBuilder str = new StringBuilder();
len = Integer.valueOf(properties[++i]);
for (int j = 0; j < len; j++) {
@@ -1099,7 +1198,7 @@ public class BluetoothService extends IBluetooth.Stub {
mIsDiscovering = isDiscovering;
}
- public synchronized boolean createBond(String address) {
+ private boolean isBondingFeasible(String address) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (!isEnabledInternal()) return false;
@@ -1129,17 +1228,67 @@ public class BluetoothService extends IBluetooth.Stub {
return false;
}
}
+ return true;
+ }
+
+ public synchronized boolean createBond(String address) {
+ if (!isBondingFeasible(address)) return false;
+
+ if (!createPairedDeviceNative(address, 60000 /*1 minute*/ )) {
+ return false;
+ }
- if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) {
+ mBondState.setPendingOutgoingBonding(address);
+ mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
+
+ return true;
+ }
+
+ public synchronized boolean createBondOutOfBand(String address, byte[] hash,
+ byte[] randomizer) {
+ if (!isBondingFeasible(address)) return false;
+
+ if (!createPairedDeviceOutOfBandNative(address, 60000 /* 1 minute */)) {
return false;
}
+ setDeviceOutOfBandData(address, hash, randomizer);
mBondState.setPendingOutgoingBonding(address);
mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
return true;
}
+ public synchronized boolean setDeviceOutOfBandData(String address, byte[] hash,
+ byte[] randomizer) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ if (!isEnabledInternal()) return false;
+
+ Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer);
+
+ if (DBG) {
+ log("Setting out of band data for:" + address + ":" +
+ Arrays.toString(hash) + ":" + Arrays.toString(randomizer));
+ }
+
+ mDeviceOobData.put(address, value);
+ return true;
+ }
+
+ Pair<byte[], byte[]> getDeviceOutOfBandData(BluetoothDevice device) {
+ return mDeviceOobData.get(device.getAddress());
+ }
+
+
+ public synchronized byte[] readOutOfBandData() {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ if (!isEnabledInternal()) return null;
+
+ return readAdapterOutOfBandDataNative();
+ }
+
public synchronized boolean cancelBondProcess(String address) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -1167,6 +1316,16 @@ public class BluetoothService extends IBluetooth.Stub {
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
return false;
}
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ state.sendMessage(BluetoothDeviceProfileState.UNPAIR);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public synchronized boolean removeBondInternal(String address) {
return removeDeviceNative(getObjectPathFromAddress(address));
}
@@ -1175,6 +1334,10 @@ public class BluetoothService extends IBluetooth.Stub {
return mBondState.listInState(BluetoothDevice.BOND_BONDED);
}
+ /*package*/ synchronized String[] listInState(int state) {
+ return mBondState.listInState(state);
+ }
+
public synchronized int getBondState(String address) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
@@ -1183,6 +1346,15 @@ public class BluetoothService extends IBluetooth.Stub {
return mBondState.getBondState(address.toUpperCase());
}
+ /*package*/ synchronized boolean setBondState(String address, int state) {
+ return setBondState(address, state, 0);
+ }
+
+ /*package*/ synchronized boolean setBondState(String address, int state, int reason) {
+ mBondState.setBondState(address.toUpperCase(), state);
+ return true;
+ }
+
public synchronized boolean isBluetoothDock(String address) {
SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
mContext.MODE_PRIVATE);
@@ -1521,6 +1693,32 @@ public class BluetoothService extends IBluetooth.Stub {
return setPairingConfirmationNative(address, confirm, data.intValue());
}
+ public synchronized boolean setRemoteOutOfBandData(String address) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+ "Need BLUETOOTH_ADMIN permission");
+ if (!isEnabledInternal()) return false;
+ address = address.toUpperCase();
+ Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
+ if (data == null) {
+ Log.w(TAG, "setRemoteOobData(" + address + ") called but no native data available, " +
+ "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
+ " or by bluez.\n");
+ return false;
+ }
+
+ Pair<byte[], byte[]> val = mDeviceOobData.get(address);
+ byte[] hash, randomizer;
+ if (val == null) {
+ // TODO: check what should be passed in this case.
+ hash = new byte[16];
+ randomizer = new byte[16];
+ } else {
+ hash = val.first;
+ randomizer = val.second;
+ }
+ return setRemoteOutOfBandDataNative(address, hash, randomizer, data.intValue());
+ }
+
public synchronized boolean cancelPairingUserInput(String address) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
@@ -1707,7 +1905,7 @@ public class BluetoothService extends IBluetooth.Stub {
mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
mContext.MODE_PRIVATE).edit();
editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
- editor.commit();
+ editor.apply();
}
}
}
@@ -1836,7 +2034,7 @@ public class BluetoothService extends IBluetooth.Stub {
// Rather not do this from here, but no-where else and I need this
// dump
pw.println("\n--Headset Service--");
- switch (headset.getState()) {
+ switch (headset.getState(headset.getCurrentHeadset())) {
case BluetoothHeadset.STATE_DISCONNECTED:
pw.println("getState() = STATE_DISCONNECTED");
break;
@@ -1919,6 +2117,116 @@ public class BluetoothService extends IBluetooth.Stub {
if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
}
+ public boolean connectHeadset(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING;
+ msg.obj = state;
+ mHfpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean disconnectHeadset(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HFP_OUTGOING;
+ msg.obj = state;
+ mHfpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean connectSink(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING;
+ msg.obj = state;
+ mA2dpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean disconnectSink(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_A2DP_OUTGOING;
+ msg.obj = state;
+ mA2dpProfileState.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ private BluetoothDeviceProfileState addProfileState(String address) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
+ if (state != null) return state;
+
+ state = new BluetoothDeviceProfileState(mContext, address, this, mA2dpService);
+ mDeviceProfileState.put(address, state);
+ state.start();
+ return state;
+ }
+
+ private void removeProfileState(String address) {
+ mDeviceProfileState.remove(address);
+ }
+
+ private void initProfileState() {
+ String []bonds = null;
+ String val = getPropertyInternal("Devices");
+ if (val != null) {
+ bonds = val.split(",");
+ }
+ if (bonds == null) {
+ return;
+ }
+
+ for (String path : bonds) {
+ String address = getAddressFromObjectPath(path);
+ BluetoothDeviceProfileState state = addProfileState(address);
+ // Allow 8 secs for SDP records to get registered.
+ Message msg = new Message();
+ msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES;
+ state.sendMessageDelayed(msg, 8000);
+ }
+ }
+
+ public boolean notifyIncomingConnection(String address) {
+ BluetoothDeviceProfileState state =
+ mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING;
+ state.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ /*package*/ boolean notifyIncomingA2dpConnection(String address) {
+ BluetoothDeviceProfileState state =
+ mDeviceProfileState.get(address);
+ if (state != null) {
+ Message msg = new Message();
+ msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING;
+ state.sendMessage(msg);
+ return true;
+ }
+ return false;
+ }
+
+ /*package*/ void setA2dpService(BluetoothA2dpService a2dpService) {
+ mA2dpService = a2dpService;
+ }
+
private static void log(String msg) {
Log.d(TAG, msg);
}
@@ -1944,6 +2252,9 @@ public class BluetoothService extends IBluetooth.Stub {
private native boolean stopDiscoveryNative();
private native boolean createPairedDeviceNative(String address, int timeout_ms);
+ private native boolean createPairedDeviceOutOfBandNative(String address, int timeout_ms);
+ private native byte[] readAdapterOutOfBandDataNative();
+
private native boolean cancelDeviceCreationNative(String address);
private native boolean removeDeviceNative(String objectPath);
private native int getDeviceServiceChannelNative(String objectPath, String uuid,
@@ -1954,6 +2265,9 @@ public class BluetoothService extends IBluetooth.Stub {
private native boolean setPasskeyNative(String address, int passkey, int nativeData);
private native boolean setPairingConfirmationNative(String address, boolean confirm,
int nativeData);
+ private native boolean setRemoteOutOfBandDataNative(String address, byte[] hash,
+ byte[] randomizer, int nativeData);
+
private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
int value);
private native boolean createDeviceNative(String address);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 3d1d7d63b3ed..2b083dca2ca6 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -18,6 +18,7 @@ package android.service.wallpaper;
import com.android.internal.os.HandlerCaller;
import com.android.internal.view.BaseIWindow;
+import com.android.internal.view.BaseInputHandler;
import com.android.internal.view.BaseSurfaceHolder;
import android.annotation.SdkConstant;
@@ -29,6 +30,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -39,6 +41,11 @@ import android.util.Log;
import android.util.LogPrinter;
import android.view.Gravity;
import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputHandler;
+import android.view.InputQueue;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.View;
@@ -46,6 +53,7 @@ import android.view.ViewGroup;
import android.view.ViewRoot;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
+import android.view.WindowManagerPolicy;
import java.util.ArrayList;
@@ -146,6 +154,7 @@ public abstract class WallpaperService extends Service {
final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
IWindowSession mSession;
+ InputChannel mInputChannel;
final Object mLock = new Object();
boolean mOffsetMessageEnqueued;
@@ -170,6 +179,9 @@ public abstract class WallpaperService extends Service {
};
final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
+ {
+ mRequestedFormat = PixelFormat.RGB_565;
+ }
@Override
public boolean onAllowLockCanvas() {
@@ -205,27 +217,21 @@ public abstract class WallpaperService extends Service {
};
- final BaseIWindow mWindow = new BaseIWindow() {
+ final InputHandler mInputHandler = new BaseInputHandler() {
@Override
- public boolean onDispatchPointer(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- synchronized (mLock) {
- if (event.getAction() == MotionEvent.ACTION_MOVE) {
- if (mPendingMove != null) {
- mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove);
- mPendingMove.recycle();
- }
- mPendingMove = event;
- } else {
- mPendingMove = null;
+ public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+ try {
+ int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ dispatchPointer(event);
}
- Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT,
- event);
- mCaller.sendMessage(msg);
+ } finally {
+ finishedCallback.run();
}
- return false;
}
-
+ };
+
+ final BaseIWindow mWindow = new BaseIWindow() {
@Override
public void resized(int w, int h, Rect coveredInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
@@ -338,7 +344,7 @@ public abstract class WallpaperService extends Service {
? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
: (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
if (mCreated) {
- updateSurface(false, false);
+ updateSurface(false, false, false);
}
}
@@ -423,6 +429,13 @@ public abstract class WallpaperService extends Service {
}
/**
+ * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded
+ * SurfaceHolder.Callback.surfaceRedrawNeeded()}.
+ */
+ public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
+ }
+
+ /**
* Convenience for {@link SurfaceHolder.Callback#surfaceCreated
* SurfaceHolder.Callback.surfaceCreated()}.
*/
@@ -435,8 +448,24 @@ public abstract class WallpaperService extends Service {
*/
public void onSurfaceDestroyed(SurfaceHolder holder) {
}
+
+ private void dispatchPointer(MotionEvent event) {
+ synchronized (mLock) {
+ if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ if (mPendingMove != null) {
+ mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove);
+ mPendingMove.recycle();
+ }
+ mPendingMove = event;
+ } else {
+ mPendingMove = null;
+ }
+ Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event);
+ mCaller.sendMessage(msg);
+ }
+ }
- void updateSurface(boolean forceRelayout, boolean forceReport) {
+ void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
Log.w(TAG, "Ignoring updateSurface: destroyed");
}
@@ -453,7 +482,7 @@ public abstract class WallpaperService extends Service {
final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
final boolean flagsChanged = mCurWindowFlags != mWindowFlags;
if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
- || typeChanged || flagsChanged) {
+ || typeChanged || flagsChanged || redrawNeeded) {
if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged);
@@ -487,8 +516,13 @@ public abstract class WallpaperService extends Service {
mLayout.setTitle(WallpaperService.this.getClass().getName());
mLayout.windowAnimations =
com.android.internal.R.style.Animation_Wallpaper;
- mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets);
+ mInputChannel = new InputChannel();
+ mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets,
+ mInputChannel);
mCreated = true;
+
+ InputQueue.registerInputChannel(mInputChannel, mInputHandler,
+ Looper.myQueue());
}
mSurfaceHolder.mSurfaceLock.lock();
@@ -522,26 +556,24 @@ public abstract class WallpaperService extends Service {
}
try {
- SurfaceHolder.Callback callbacks[] = null;
- synchronized (mSurfaceHolder.mCallbacks) {
- final int N = mSurfaceHolder.mCallbacks.size();
- if (N > 0) {
- callbacks = new SurfaceHolder.Callback[N];
- mSurfaceHolder.mCallbacks.toArray(callbacks);
- }
- }
+ mSurfaceHolder.ungetCallbacks();
if (surfaceCreating) {
mIsCreating = true;
if (DEBUG) Log.v(TAG, "onSurfaceCreated("
+ mSurfaceHolder + "): " + this);
onSurfaceCreated(mSurfaceHolder);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
}
+
+ redrawNeeded |= creating
+ || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0;
+
if (forceReport || creating || surfaceCreating
|| formatChanged || sizeChanged) {
if (DEBUG) {
@@ -557,6 +589,7 @@ public abstract class WallpaperService extends Service {
+ "): " + this);
onSurfaceChanged(mSurfaceHolder, mFormat,
mCurWidth, mCurHeight);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, mFormat,
@@ -564,10 +597,24 @@ public abstract class WallpaperService extends Service {
}
}
}
+
+ if (redrawNeeded) {
+ onSurfaceRedrawNeeded(mSurfaceHolder);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ if (c instanceof SurfaceHolder.Callback2) {
+ ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
+ mSurfaceHolder);
+ }
+ }
+ }
+ }
+
} finally {
mIsCreating = false;
mSurfaceCreated = true;
- if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
+ if (redrawNeeded) {
mSession.finishDrawing(mWindow);
}
}
@@ -592,6 +639,7 @@ public abstract class WallpaperService extends Service {
mSurfaceHolder.setSizeFromLayout();
mInitializing = true;
mSession = ViewRoot.getWindowSession(getMainLooper());
+
mWindow.setSession(mSession);
IntentFilter filter = new IntentFilter();
@@ -603,7 +651,7 @@ public abstract class WallpaperService extends Service {
onCreate(mSurfaceHolder);
mInitializing = false;
- updateSurface(false, false);
+ updateSurface(false, false, false);
}
void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
@@ -632,7 +680,7 @@ public abstract class WallpaperService extends Service {
// If becoming visible, in preview mode the surface
// may have been destroyed so now we need to make
// sure it is re-created.
- updateSurface(false, false);
+ updateSurface(false, false, false);
}
onVisibilityChanged(visible);
}
@@ -698,14 +746,12 @@ public abstract class WallpaperService extends Service {
void reportSurfaceDestroyed() {
if (mSurfaceCreated) {
mSurfaceCreated = false;
- SurfaceHolder.Callback callbacks[];
- synchronized (mSurfaceHolder.mCallbacks) {
- callbacks = new SurfaceHolder.Callback[
- mSurfaceHolder.mCallbacks.size()];
- mSurfaceHolder.mCallbacks.toArray(callbacks);
- }
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceDestroyed(mSurfaceHolder);
+ mSurfaceHolder.ungetCallbacks();
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
}
if (DEBUG) Log.v(TAG, "onSurfaceDestroyed("
+ mSurfaceHolder + "): " + this);
@@ -737,11 +783,23 @@ public abstract class WallpaperService extends Service {
try {
if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
+ mSurfaceHolder.getSurface() + " of: " + this);
+
+ if (mInputChannel != null) {
+ InputQueue.unregisterInputChannel(mInputChannel);
+ }
+
mSession.remove(mWindow);
} catch (RemoteException e) {
}
mSurfaceHolder.mSurface.release();
mCreated = false;
+
+ // Dispose the input channel after removing the window so the Window Manager
+ // doesn't interpret the input channel being closed as an abnormal termination.
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
}
}
}
@@ -793,7 +851,7 @@ public abstract class WallpaperService extends Service {
public void dispatchPointer(MotionEvent event) {
if (mEngine != null) {
- mEngine.mWindow.onDispatchPointer(event, event.getEventTime(), false);
+ mEngine.dispatchPointer(event);
}
}
@@ -827,7 +885,7 @@ public abstract class WallpaperService extends Service {
return;
}
case MSG_UPDATE_SURFACE:
- mEngine.updateSurface(true, false);
+ mEngine.updateSurface(true, false, false);
break;
case MSG_VISIBILITY_CHANGED:
if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
@@ -843,14 +901,8 @@ public abstract class WallpaperService extends Service {
} break;
case MSG_WINDOW_RESIZED: {
final boolean reportDraw = message.arg1 != 0;
- mEngine.updateSurface(true, false);
+ mEngine.updateSurface(true, false, reportDraw);
mEngine.doOffsetsChanged();
- if (reportDraw) {
- try {
- mEngine.mSession.finishDrawing(mEngine.mWindow);
- } catch (RemoteException e) {
- }
- }
} break;
case MSG_TOUCH_EVENT: {
MotionEvent ev = (MotionEvent)message.obj;
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 26c167e1f013..841257f36770 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -1065,6 +1065,9 @@ public class TextToSpeech {
if (!mStarted) {
return result;
}
+ if (loc == null) {
+ return result;
+ }
try {
String language = loc.getISO3Language();
String country = loc.getISO3Country();
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index bb98bce67cee..13cb5e6e305c 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -417,8 +417,8 @@ public class Selection {
}
}
- private static final class START implements NoCopySpan { };
- private static final class END implements NoCopySpan { };
+ private static final class START implements NoCopySpan { }
+ private static final class END implements NoCopySpan { }
/*
* Public constants
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 9589bf369e6e..8675d05f3116 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1651,7 +1651,36 @@ public class TextUtils {
return mode;
}
-
+
+ /**
+ * Does a comma-delimited list 'delimitedString' contain a certain item?
+ * (without allocating memory)
+ *
+ * @hide
+ */
+ public static boolean delimitedStringContains(
+ String delimitedString, char delimiter, String item) {
+ if (isEmpty(delimitedString) || isEmpty(item)) {
+ return false;
+ }
+ int pos = -1;
+ int length = delimitedString.length();
+ while ((pos = delimitedString.indexOf(item, pos + 1)) != -1) {
+ if (pos > 0 && delimitedString.charAt(pos - 1) != delimiter) {
+ continue;
+ }
+ int expectedDelimiterPos = pos + item.length();
+ if (expectedDelimiterPos == length) {
+ // Match at end of string.
+ return true;
+ }
+ if (delimitedString.charAt(expectedDelimiterPos) == delimiter) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
}
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index dde0889a5e8b..4e2c3c395ce7 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -168,6 +168,12 @@ public class DateUtils
public static final int FORMAT_CAP_NOON = 0x00400;
public static final int FORMAT_NO_MIDNIGHT = 0x00800;
public static final int FORMAT_CAP_MIDNIGHT = 0x01000;
+ /**
+ * @deprecated Use
+ * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
+ * and pass in {@link Time#TIMEZONE_UTC Time.TIMEZONE_UTC} for the timeZone instead.
+ */
+ @Deprecated
public static final int FORMAT_UTC = 0x02000;
public static final int FORMAT_ABBREV_TIME = 0x04000;
public static final int FORMAT_ABBREV_WEEKDAY = 0x08000;
@@ -946,12 +952,12 @@ public class DateUtils
* {@link java.util.Formatter} instance and use the version of
* {@link #formatDateRange(Context, long, long, int) formatDateRange}
* that takes a {@link java.util.Formatter}.
- *
+ *
* @param context the context is required only if the time is shown
* @param startMillis the start time in UTC milliseconds
* @param endMillis the end time in UTC milliseconds
* @param flags a bit mask of options See
- * {@link #formatDateRange(Context, long, long, int) formatDateRange}
+ * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
* @return a string containing the formatted date/time range.
*/
public static String formatDateRange(Context context, long startMillis,
@@ -962,6 +968,29 @@ public class DateUtils
/**
* Formats a date or a time range according to the local conventions.
+ * <p>
+ * Note that this is a convenience method for formatting the date or
+ * time range in the local time zone. If you want to specify the time
+ * zone please use
+ * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}.
+ *
+ * @param context the context is required only if the time is shown
+ * @param formatter the Formatter used for formatting the date range.
+ * Note: be sure to call setLength(0) on StringBuilder passed to
+ * the Formatter constructor unless you want the results to accumulate.
+ * @param startMillis the start time in UTC milliseconds
+ * @param endMillis the end time in UTC milliseconds
+ * @param flags a bit mask of options See
+ * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}
+ * @return a string containing the formatted date/time range.
+ */
+ public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
+ long endMillis, int flags) {
+ return formatDateRange(context, formatter, startMillis, endMillis, flags, null);
+ }
+
+ /**
+ * Formats a date or a time range according to the local conventions.
*
* <p>
* Example output strings (date formats in these examples are shown using
@@ -1076,8 +1105,9 @@ public class DateUtils
* FORMAT_24HOUR takes precedence.
*
* <p>
- * If FORMAT_UTC is set, then the UTC timezone is used for the start
- * and end milliseconds.
+ * If FORMAT_UTC is set, then the UTC time zone is used for the start
+ * and end milliseconds unless a time zone is specified. If a time zone
+ * is specified it will be used regardless of the FORMAT_UTC flag.
*
* <p>
* If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the
@@ -1109,11 +1139,13 @@ public class DateUtils
* @param startMillis the start time in UTC milliseconds
* @param endMillis the end time in UTC milliseconds
* @param flags a bit mask of options
- *
+ * @param timeZone the time zone to compute the string in. Use null for local
+ * or if the FORMAT_UTC flag is being used.
+ *
* @return the formatter with the formatted date/time range appended to the string buffer.
*/
public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis,
- long endMillis, int flags) {
+ long endMillis, int flags, String timeZone) {
Resources res = Resources.getSystem();
boolean showTime = (flags & FORMAT_SHOW_TIME) != 0;
boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0;
@@ -1130,7 +1162,14 @@ public class DateUtils
// computation below that'd otherwise be thrown out.
boolean isInstant = (startMillis == endMillis);
- Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+ Time startDate;
+ if (timeZone != null) {
+ startDate = new Time(timeZone);
+ } else if (useUTC) {
+ startDate = new Time(Time.TIMEZONE_UTC);
+ } else {
+ startDate = new Time();
+ }
startDate.set(startMillis);
Time endDate;
@@ -1139,7 +1178,13 @@ public class DateUtils
endDate = startDate;
dayDistance = 0;
} else {
- endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time();
+ if (timeZone != null) {
+ endDate = new Time(timeZone);
+ } else if (useUTC) {
+ endDate = new Time(Time.TIMEZONE_UTC);
+ } else {
+ endDate = new Time();
+ }
endDate.set(endMillis);
int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff);
int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff);
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index 9af42cc60822..04086737523c 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -16,30 +16,38 @@
package android.text.method;
-import android.util.Log;
+import android.text.Layout;
+import android.text.Selection;
+import android.text.Spannable;
import android.view.KeyEvent;
-import android.graphics.Rect;
-import android.text.*;
-import android.widget.TextView;
-import android.view.View;
-import android.view.ViewConfiguration;
import android.view.MotionEvent;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.TextView.CursorController;
// XXX this doesn't extend MetaKeyKeyListener because the signatures
// don't match. Need to figure that out. Meanwhile the meta keys
// won't work in fields that don't take input.
-public class
-ArrowKeyMovementMethod
-implements MovementMethod
-{
+public class ArrowKeyMovementMethod implements MovementMethod {
+ /**
+ * An optional controller for the cursor.
+ * Use {@link #setCursorController(CursorController)} to set this field.
+ */
+ private CursorController mCursorController;
+
+ private boolean isCap(Spannable buffer) {
+ return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) ||
+ (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
+ }
+
+ private boolean isAlt(Spannable buffer) {
+ return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1;
+ }
+
private boolean up(TextView widget, Spannable buffer) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
- boolean alt = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_ALT_ON) == 1;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -60,12 +68,8 @@ implements MovementMethod
}
private boolean down(TextView widget, Spannable buffer) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
- boolean alt = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_ALT_ON) == 1;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -86,12 +90,8 @@ implements MovementMethod
}
private boolean left(TextView widget, Spannable buffer) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
- boolean alt = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_ALT_ON) == 1;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -110,12 +110,8 @@ implements MovementMethod
}
private boolean right(TextView widget, Spannable buffer) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
- boolean alt = MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_ALT_ON) == 1;
+ boolean cap = isCap(buffer);
+ boolean alt = isAlt(buffer);
Layout layout = widget.getLayout();
if (cap) {
@@ -133,35 +129,6 @@ implements MovementMethod
}
}
- private int getOffset(int x, int y, TextView widget){
- // Converts the absolute X,Y coordinates to the character offset for the
- // character whose position is closest to the specified
- // horizontal position.
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- // Clamp the position to inside of the view.
- if (x < 0) {
- x = 0;
- } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) {
- x = widget.getWidth()-widget.getTotalPaddingRight() - 1;
- }
- if (y < 0) {
- y = 0;
- } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) {
- y = widget.getHeight()-widget.getTotalPaddingBottom() - 1;
- }
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
-
- int offset = layout.getOffsetForHorizontal(line, x);
- return offset;
- }
-
public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) {
if (executeDown(widget, buffer, keyCode)) {
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -193,10 +160,9 @@ implements MovementMethod
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
- if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
- if (widget.showContextMenu()) {
+ if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) &&
+ (widget.showContextMenu())) {
handled = true;
- }
}
}
@@ -214,8 +180,7 @@ implements MovementMethod
public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) {
int code = event.getKeyCode();
- if (code != KeyEvent.KEYCODE_UNKNOWN
- && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
+ if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) {
int repeat = event.getRepeatCount();
boolean handled = false;
while ((--repeat) > 0) {
@@ -226,13 +191,22 @@ implements MovementMethod
return false;
}
- public boolean onTrackballEvent(TextView widget, Spannable text,
- MotionEvent event) {
+ public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
+ if (mCursorController != null) {
+ mCursorController.hide();
+ }
return false;
}
- public boolean onTouchEvent(TextView widget, Spannable buffer,
- MotionEvent event) {
+ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
+ if (mCursorController != null) {
+ return onTouchEventCursor(widget, buffer, event);
+ } else {
+ return onTouchEventStandard(widget, buffer, event);
+ }
+ }
+
+ private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
if (event.getAction() == MotionEvent.ACTION_UP) {
initialScrollX = Touch.getInitialScrollX(widget, buffer);
@@ -243,53 +217,20 @@ implements MovementMethod
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
- int x = (int) event.getX();
- int y = (int) event.getY();
- int offset = getOffset(x, y, widget);
-
+ boolean cap = isCap(buffer);
if (cap) {
- buffer.setSpan(LAST_TAP_DOWN, offset, offset,
- Spannable.SPAN_POINT_POINT);
+ int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+
+ buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT);
// Disallow intercepting of the touch events, so that
// users can scroll and select at the same time.
// without this, users would get booted out of select
// mode once the view detected it needed to scroll.
widget.getParent().requestDisallowInterceptTouchEvent(true);
- } else {
- OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
- OnePointFiveTapState.class);
-
- if (tap.length > 0) {
- if (event.getEventTime() - tap[0].mWhen <=
- ViewConfiguration.getDoubleTapTimeout() &&
- sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) {
-
- tap[0].active = true;
- MetaKeyKeyListener.startSelecting(widget, buffer);
- widget.getParent().requestDisallowInterceptTouchEvent(true);
- buffer.setSpan(LAST_TAP_DOWN, offset, offset,
- Spannable.SPAN_POINT_POINT);
- }
-
- tap[0].mWhen = event.getEventTime();
- } else {
- OnePointFiveTapState newtap = new OnePointFiveTapState();
- newtap.mWhen = event.getEventTime();
- newtap.active = false;
- buffer.setSpan(newtap, 0, buffer.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- }
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
+ boolean cap = isCap(buffer);
if (cap && handled) {
// Before selecting, make sure we've moved out of the "slop".
@@ -297,45 +238,15 @@ implements MovementMethod
// OUT of the slop
// Turn long press off while we're selecting. User needs to
- // re-tap on the selection to enable longpress
+ // re-tap on the selection to enable long press
widget.cancelLongPress();
// Update selection as we're moving the selection area.
// Get the current touch position
- int x = (int) event.getX();
- int y = (int) event.getY();
- int offset = getOffset(x, y, widget);
-
- final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(),
- OnePointFiveTapState.class);
-
- if (tap.length > 0 && tap[0].active) {
- // Get the last down touch position (the position at which the
- // user started the selection)
- int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN);
-
- // Compute the selection boundaries
- int spanstart;
- int spanend;
- if (offset >= lastDownOffset) {
- // Expand from word start of the original tap to new word
- // end, since we are selecting "forwards"
- spanstart = findWordStart(buffer, lastDownOffset);
- spanend = findWordEnd(buffer, offset);
- } else {
- // Expand to from new word start to word end of the original
- // tap since we are selecting "backwards".
- // The spanend will always need to be associated with the touch
- // up position, so that refining the selection with the
- // trackball will work as expected.
- spanstart = findWordEnd(buffer, lastDownOffset);
- spanend = findWordStart(buffer, offset);
- }
- Selection.setSelection(buffer, spanstart, spanend);
- } else {
- Selection.extendSelection(buffer, offset);
- }
+ int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+
+ Selection.extendSelection(buffer, offset);
return true;
}
} else if (event.getAction() == MotionEvent.ACTION_UP) {
@@ -344,70 +255,17 @@ implements MovementMethod
// the current scroll offset to avoid the scroll jumping later
// to show it.
if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) ||
- (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
+ (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) {
widget.moveCursorToVisibleOffset();
return true;
}
- int x = (int) event.getX();
- int y = (int) event.getY();
- int off = getOffset(x, y, widget);
-
- // XXX should do the same adjust for x as we do for the line.
-
- OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(),
- OnePointFiveTapState.class);
- if (onepointfivetap.length > 0 && onepointfivetap[0].active &&
- Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
- // If we've set select mode, because there was a onepointfivetap,
- // but there was no ensuing swipe gesture, undo the select mode
- // and remove reference to the last onepointfivetap.
- MetaKeyKeyListener.stopSelecting(widget, buffer);
- for (int i=0; i < onepointfivetap.length; i++) {
- buffer.removeSpan(onepointfivetap[i]);
- }
+ int offset = widget.getOffset((int) event.getX(), (int) event.getY());
+ if (isCap(buffer)) {
buffer.removeSpan(LAST_TAP_DOWN);
- }
- boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
- KeyEvent.META_SHIFT_ON) == 1) ||
- (MetaKeyKeyListener.getMetaState(buffer,
- MetaKeyKeyListener.META_SELECTING) != 0);
-
- DoubleTapState[] tap = buffer.getSpans(0, buffer.length(),
- DoubleTapState.class);
- boolean doubletap = false;
-
- if (tap.length > 0) {
- if (event.getEventTime() - tap[0].mWhen <=
- ViewConfiguration.getDoubleTapTimeout() &&
- sameWord(buffer, off, Selection.getSelectionEnd(buffer))) {
-
- doubletap = true;
- }
-
- tap[0].mWhen = event.getEventTime();
+ Selection.extendSelection(buffer, offset);
} else {
- DoubleTapState newtap = new DoubleTapState();
- newtap.mWhen = event.getEventTime();
- buffer.setSpan(newtap, 0, buffer.length(),
- Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- }
-
- if (cap) {
- buffer.removeSpan(LAST_TAP_DOWN);
- if (onepointfivetap.length > 0 && onepointfivetap[0].active) {
- // If we selecting something with the onepointfivetap-and
- // swipe gesture, stop it on finger up.
- MetaKeyKeyListener.stopSelecting(widget, buffer);
- } else {
- Selection.extendSelection(buffer, off);
- }
- } else if (doubletap) {
- Selection.setSelection(buffer,
- findWordStart(buffer, off),
- findWordEnd(buffer, off));
- } else {
- Selection.setSelection(buffer, off);
+ Selection.setSelection(buffer, offset);
}
MetaKeyKeyListener.adjustMetaAfterKeypress(buffer);
@@ -420,73 +278,45 @@ implements MovementMethod
return handled;
}
- private static class DoubleTapState implements NoCopySpan {
- long mWhen;
- }
-
- /* We check for a onepointfive tap. This is similar to
- * doubletap gesture (where a finger goes down, up, down, up, in a short
- * time period), except in the onepointfive tap, a users finger only needs
- * to go down, up, down in a short time period. We detect this type of tap
- * to implement the onepointfivetap-and-swipe selection gesture.
- * This gesture allows users to select a segment of text without going
- * through the "select text" option in the context menu.
- */
- private static class OnePointFiveTapState implements NoCopySpan {
- long mWhen;
- boolean active;
- }
-
- private static boolean sameWord(CharSequence text, int one, int two) {
- int start = findWordStart(text, one);
- int end = findWordEnd(text, one);
-
- if (end == start) {
- return false;
- }
+ private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
+ if (widget.isFocused() && !widget.didTouchFocusSelect()) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ widget.cancelLongPress();
- return start == findWordStart(text, two) &&
- end == findWordEnd(text, two);
- }
+ // Offset the current touch position (from controller to cursor)
+ final float x = event.getX() + mCursorController.getOffsetX();
+ final float y = event.getY() + mCursorController.getOffsetY();
+ mCursorController.updatePosition((int) x, (int) y);
+ return true;
- // TODO: Unify with TextView.getWordForDictionary()
- private static int findWordStart(CharSequence text, int start) {
- for (; start > 0; start--) {
- char c = text.charAt(start - 1);
- int type = Character.getType(c);
-
- if (c != '\'' &&
- type != Character.UPPERCASE_LETTER &&
- type != Character.LOWERCASE_LETTER &&
- type != Character.TITLECASE_LETTER &&
- type != Character.MODIFIER_LETTER &&
- type != Character.DECIMAL_DIGIT_NUMBER) {
- break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mCursorController = null;
+ return true;
}
}
-
- return start;
+ return false;
}
- // TODO: Unify with TextView.getWordForDictionary()
- private static int findWordEnd(CharSequence text, int end) {
- int len = text.length();
-
- for (; end < len; end++) {
- char c = text.charAt(end);
- int type = Character.getType(c);
-
- if (c != '\'' &&
- type != Character.UPPERCASE_LETTER &&
- type != Character.LOWERCASE_LETTER &&
- type != Character.TITLECASE_LETTER &&
- type != Character.MODIFIER_LETTER &&
- type != Character.DECIMAL_DIGIT_NUMBER) {
- break;
- }
- }
-
- return end;
+ /**
+ * Defines the cursor controller.
+ *
+ * When set, this object can be used to handle touch events, that can be translated into cursor
+ * updates.
+ *
+ * {@link MotionEvent#ACTION_MOVE} events will call back the
+ * {@link CursorController#updatePosition(int, int)} controller's method, passing the current
+ * finger coordinates (offset by {@link CursorController#getOffsetX()} and
+ * {@link CursorController#getOffsetY()}) as parameters.
+ *
+ * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or
+ * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null.
+ *
+ * @param cursorController A cursor controller implementation
+ */
+ public void setCursorController(CursorController cursorController) {
+ mCursorController = cursorController;
}
public boolean canSelectArbitrarily() {
@@ -499,25 +329,9 @@ implements MovementMethod
public void onTakeFocus(TextView view, Spannable text, int dir) {
if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) {
- Layout layout = view.getLayout();
-
- if (layout == null) {
- /*
- * This shouldn't be null, but do something sensible if it is.
- */
+ if (view.getLayout() == null) {
+ // This shouldn't be null, but do something sensible if it is.
Selection.setSelection(text, text.length());
- } else {
- /*
- * Put the cursor at the end of the first line, which is
- * either the last offset if there is only one line, or the
- * offset before the first character of the second line
- * if there is more than one line.
- */
- if (layout.getLineCount() == 1) {
- Selection.setSelection(text, text.length());
- } else {
- Selection.setSelection(text, layout.getLineStart(1) - 1);
- }
}
} else {
Selection.setSelection(text, text.length());
@@ -525,8 +339,9 @@ implements MovementMethod
}
public static MovementMethod getInstance() {
- if (sInstance == null)
+ if (sInstance == null) {
sInstance = new ArrowKeyMovementMethod();
+ }
return sInstance;
}
diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java
index 5be2a4866c4f..09cbbb8069da 100644
--- a/core/java/android/text/method/TextKeyListener.java
+++ b/core/java/android/text/method/TextKeyListener.java
@@ -246,8 +246,10 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher {
private void initPrefs(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
mResolver = new WeakReference<ContentResolver>(contentResolver);
- mObserver = new SettingsObserver();
- contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
+ if (mObserver == null) {
+ mObserver = new SettingsObserver();
+ contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver);
+ }
updatePrefs(contentResolver);
mPrefsInited = true;
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 42ad10e706ef..a19a78e46988 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -17,14 +17,13 @@
package android.text.method;
import android.text.Layout;
-import android.text.NoCopySpan;
import android.text.Layout.Alignment;
+import android.text.NoCopySpan;
import android.text.Spannable;
-import android.util.Log;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;
-import android.view.KeyEvent;
public class Touch {
private Touch() { }
@@ -99,7 +98,7 @@ public class Touch {
MotionEvent event) {
DragState[] ds;
- switch (event.getAction()) {
+ switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ds = buffer.getSpans(0, buffer.length(), DragState.class);
diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java
index 952d8339a29e..69cf93cf35fc 100644
--- a/core/java/android/text/util/Rfc822Tokenizer.java
+++ b/core/java/android/text/util/Rfc822Tokenizer.java
@@ -84,8 +84,10 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
if (c == '"') {
i++;
break;
- } else if (c == '\\' && i + 1 < cursor) {
- name.append(text.charAt(i + 1));
+ } else if (c == '\\') {
+ if (i + 1 < cursor) {
+ name.append(text.charAt(i + 1));
+ }
i += 2;
} else {
name.append(c);
@@ -110,8 +112,10 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer {
comment.append(c);
level++;
i++;
- } else if (c == '\\' && i + 1 < cursor) {
- comment.append(text.charAt(i + 1));
+ } else if (c == '\\') {
+ if (i + 1 < cursor) {
+ comment.append(text.charAt(i + 1));
+ }
i += 2;
} else {
comment.append(c);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 2628eb4026d5..76d81065f2e5 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -135,6 +135,7 @@ public class DisplayMetrics {
int screenLayout) {
boolean expandable = compatibilityInfo.isConfiguredExpandable();
boolean largeScreens = compatibilityInfo.isConfiguredLargeScreens();
+ boolean xlargeScreens = compatibilityInfo.isConfiguredXLargeScreens();
// Note: this assume that configuration is updated before calling
// updateMetrics method.
@@ -157,8 +158,18 @@ public class DisplayMetrics {
compatibilityInfo.setLargeScreens(false);
}
}
+ if (!xlargeScreens) {
+ if ((screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
+ != Configuration.SCREENLAYOUT_SIZE_XLARGE) {
+ xlargeScreens = true;
+ // the current screen size is not large.
+ compatibilityInfo.setXLargeScreens(true);
+ } else {
+ compatibilityInfo.setXLargeScreens(false);
+ }
+ }
- if (!expandable || !largeScreens) {
+ if (!expandable || (!largeScreens && !xlargeScreens)) {
// This is a larger screen device and the app is not
// compatible with large screens, so diddle it.
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index e1116694e4a6..d577b74d5d2b 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -88,6 +88,21 @@ public final class Log {
TerribleFailure(String msg, Throwable cause) { super(msg, cause); }
}
+ /**
+ * Interface to handle terrible failures from {@link #wtf()}.
+ *
+ * @hide
+ */
+ public interface TerribleFailureHandler {
+ void onTerribleFailure(String tag, TerribleFailure what);
+ }
+
+ private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() {
+ public void onTerribleFailure(String tag, TerribleFailure what) {
+ RuntimeInit.wtf(tag, what);
+ }
+ };
+
private Log() {
}
@@ -257,13 +272,29 @@ public final class Log {
* @param tr An exception to log. May be null.
*/
public static int wtf(String tag, String msg, Throwable tr) {
- tr = new TerribleFailure(msg, tr);
+ TerribleFailure what = new TerribleFailure(msg, tr);
int bytes = println_native(LOG_ID_MAIN, ASSERT, tag, getStackTraceString(tr));
- RuntimeInit.wtf(tag, tr);
+ sWtfHandler.onTerribleFailure(tag, what);
return bytes;
}
/**
+ * Sets the terrible failure handler, for testing.
+ *
+ * @return the old handler
+ *
+ * @hide
+ */
+ public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("handler == null");
+ }
+ TerribleFailureHandler oldHandler = sWtfHandler;
+ sWtfHandler = handler;
+ return oldHandler;
+ }
+
+ /**
* Handy function to get a loggable stack trace from a Throwable
* @param tr An exception to log
*/
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 0fc70d52b878..60ca3849d6f9 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.TimeZone;
import java.util.Date;
@@ -130,4 +131,153 @@ public class TimeUtils {
public static String getTimeZoneDatabaseVersion() {
return ZoneInfoDB.getVersion();
}
+
+ /** @hide Field length that can hold 999 days of time */
+ public static final int HUNDRED_DAY_FIELD_LEN = 19;
+
+ private static final int SECONDS_PER_MINUTE = 60;
+ private static final int SECONDS_PER_HOUR = 60 * 60;
+ private static final int SECONDS_PER_DAY = 24 * 60 * 60;
+
+ private static final Object sFormatSync = new Object();
+ private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5];
+
+ static private int accumField(int amt, int suffix, boolean always, int zeropad) {
+ if (amt > 99 || (always && zeropad >= 3)) {
+ return 3+suffix;
+ }
+ if (amt > 9 || (always && zeropad >= 2)) {
+ return 2+suffix;
+ }
+ if (always || amt > 0) {
+ return 1+suffix;
+ }
+ return 0;
+ }
+
+ static private int printField(char[] formatStr, int amt, char suffix, int pos,
+ boolean always, int zeropad) {
+ if (always || amt > 0) {
+ if ((always && zeropad >= 3) || amt > 99) {
+ int dig = amt/100;
+ formatStr[pos] = (char)(dig + '0');
+ pos++;
+ always = true;
+ amt -= (dig*100);
+ }
+ if ((always && zeropad >= 2) || amt > 9) {
+ int dig = amt/10;
+ formatStr[pos] = (char)(dig + '0');
+ pos++;
+ always = true;
+ amt -= (dig*10);
+ }
+ formatStr[pos] = (char)(amt + '0');
+ pos++;
+ formatStr[pos] = suffix;
+ pos++;
+ }
+ return pos;
+ }
+
+ private static int formatDurationLocked(long duration, int fieldLen) {
+ if (sFormatStr.length < fieldLen) {
+ sFormatStr = new char[fieldLen];
+ }
+
+ char[] formatStr = sFormatStr;
+
+ if (duration == 0) {
+ int pos = 0;
+ fieldLen -= 1;
+ while (pos < fieldLen) {
+ formatStr[pos] = ' ';
+ }
+ formatStr[pos] = '0';
+ return pos+1;
+ }
+
+ char prefix;
+ if (duration > 0) {
+ prefix = '+';
+ } else {
+ prefix = '-';
+ duration = -duration;
+ }
+
+ int millis = (int)(duration%1000);
+ int seconds = (int) Math.floor(duration / 1000);
+ int days = 0, hours = 0, minutes = 0;
+
+ if (seconds > SECONDS_PER_DAY) {
+ days = seconds / SECONDS_PER_DAY;
+ seconds -= days * SECONDS_PER_DAY;
+ }
+ if (seconds > SECONDS_PER_HOUR) {
+ hours = seconds / SECONDS_PER_HOUR;
+ seconds -= hours * SECONDS_PER_HOUR;
+ }
+ if (seconds > SECONDS_PER_MINUTE) {
+ minutes = seconds / SECONDS_PER_MINUTE;
+ seconds -= minutes * SECONDS_PER_MINUTE;
+ }
+
+ int pos = 0;
+
+ if (fieldLen != 0) {
+ int myLen = accumField(days, 1, false, 0);
+ myLen += accumField(hours, 1, myLen > 0, 2);
+ myLen += accumField(minutes, 1, myLen > 0, 2);
+ myLen += accumField(seconds, 1, myLen > 0, 2);
+ myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
+ while (myLen < fieldLen) {
+ formatStr[pos] = ' ';
+ pos++;
+ myLen++;
+ }
+ }
+
+ formatStr[pos] = prefix;
+ pos++;
+
+ int start = pos;
+ boolean zeropad = fieldLen != 0;
+ pos = printField(formatStr, days, 'd', pos, false, 0);
+ pos = printField(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
+ pos = printField(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
+ pos = printField(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
+ pos = printField(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
+ formatStr[pos] = 's';
+ return pos + 1;
+ }
+
+ /** @hide Just for debugging; not internationalized. */
+ public static void formatDuration(long duration, StringBuilder builder) {
+ synchronized (sFormatSync) {
+ int len = formatDurationLocked(duration, 0);
+ builder.append(sFormatStr, 0, len);
+ }
+ }
+
+ /** @hide Just for debugging; not internationalized. */
+ public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
+ synchronized (sFormatSync) {
+ int len = formatDurationLocked(duration, fieldLen);
+ pw.print(new String(sFormatStr, 0, len));
+ }
+ }
+
+ /** @hide Just for debugging; not internationalized. */
+ public static void formatDuration(long duration, PrintWriter pw) {
+ formatDuration(duration, pw, 0);
+ }
+
+ /** @hide Just for debugging; not internationalized. */
+ public static void formatDuration(long time, long now, PrintWriter pw) {
+ if (time == 0) {
+ pw.print("--");
+ return;
+ }
+ formatDuration(time-now, pw, 0);
+ }
}
diff --git a/core/java/android/view/AbsSavedState.java b/core/java/android/view/AbsSavedState.java
index 840d7c170bd8..6ad33dd25d45 100644
--- a/core/java/android/view/AbsSavedState.java
+++ b/core/java/android/view/AbsSavedState.java
@@ -54,7 +54,7 @@ public abstract class AbsSavedState implements Parcelable {
*/
protected AbsSavedState(Parcel source) {
// FIXME need class loader
- Parcelable superState = (Parcelable) source.readParcelable(null);
+ Parcelable superState = source.readParcelable(null);
mSuperState = superState != null ? superState : EMPTY_STATE;
}
@@ -75,7 +75,7 @@ public abstract class AbsSavedState implements Parcelable {
= new Parcelable.Creator<AbsSavedState>() {
public AbsSavedState createFromParcel(Parcel in) {
- Parcelable superState = (Parcelable) in.readParcelable(null);
+ Parcelable superState = in.readParcelable(null);
if (superState != null) {
throw new IllegalStateException("superState must be null");
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index fabe5c893335..34d7935dd2f9 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -27,7 +27,8 @@ public class Display
/**
- * Use the WindowManager interface to create a Display object.
+ * Use {@link android.view.WindowManager#getDefaultDisplay()
+ * WindowManager.getDefaultDisplay()} to create a Display object.
* Display gives you access to some information about a particular display
* connected to the device.
*/
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 3b0980875e42..921018a899ad 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -46,9 +46,6 @@ oneway interface IWindow {
void resized(int w, int h, in Rect coveredInsets, in Rect visibleInsets,
boolean reportDraw, in Configuration newConfig);
- void dispatchKey(in KeyEvent event);
- void dispatchPointer(in MotionEvent event, long eventTime, boolean callWhenDone);
- void dispatchTrackball(in MotionEvent event, long eventTime, boolean callWhenDone);
void dispatchAppVisibility(boolean visible);
void dispatchGetNewSurface();
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9b7b2f40fd3c..d4dd05c19adc 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -26,7 +26,10 @@ import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
import android.view.KeyEvent;
+import android.view.InputEvent;
import android.view.MotionEvent;
+import android.view.InputChannel;
+import android.view.InputDevice;
/**
* System private interface to the window manager.
@@ -50,10 +53,13 @@ interface IWindowManager
boolean inputMethodClientHasFocus(IInputMethodClient client);
// These can only be called when injecting events to your own window,
- // or by holding the INJECT_EVENTS permission.
+ // or by holding the INJECT_EVENTS permission. These methods may block
+ // until pending input events are finished being dispatched even when 'sync' is false.
+ // Avoid calling these methods on your UI thread or use the 'NoWait' version instead.
boolean injectKeyEvent(in KeyEvent ev, boolean sync);
boolean injectPointerEvent(in MotionEvent ev, boolean sync);
boolean injectTrackballEvent(in MotionEvent ev, boolean sync);
+ boolean injectInputEventNoWait(in InputEvent ev);
// These can only be called when holding the MANAGE_APP_TOKENS permission.
void pauseKeyDispatching(IBinder token);
@@ -115,10 +121,15 @@ interface IWindowManager
int getKeycodeStateForDevice(int devid, int sw);
int getTrackballKeycodeState(int sw);
int getDPadKeycodeState(int sw);
+ InputChannel monitorInput(String inputChannelName);
// Report whether the hardware supports the given keys; returns true if successful
boolean hasKeys(in int[] keycodes, inout boolean[] keyExists);
+ // Get input device information.
+ InputDevice getInputDevice(int deviceId);
+ int[] getInputDeviceIds();
+
// For testing
void setInTouchMode(boolean showFocus);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 01f07d694eb1..7f10b7686dd7 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -21,6 +21,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
+import android.view.InputChannel;
import android.view.IWindow;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -33,6 +34,9 @@ import android.view.Surface;
*/
interface IWindowSession {
int add(IWindow window, in WindowManager.LayoutParams attrs,
+ in int viewVisibility, out Rect outContentInsets,
+ out InputChannel outInputChannel);
+ int addWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, out Rect outContentInsets);
void remove(IWindow window);
@@ -105,10 +109,6 @@ interface IWindowSession {
void getDisplayFrame(IWindow window, out Rect outDisplayFrame);
void finishDrawing(IWindow window);
-
- void finishKey(IWindow window);
- MotionEvent getPendingPointerMove(IWindow window);
- MotionEvent getPendingTrackballMove(IWindow window);
void setInTouchMode(boolean showFocus);
boolean getInTouchMode();
diff --git a/core/java/android/hardware/ISensorService.aidl b/core/java/android/view/InputChannel.aidl
index 67180bd90064..74c0aa463239 100644
--- a/core/java/android/hardware/ISensorService.aidl
+++ b/core/java/android/view/InputChannel.aidl
@@ -1,6 +1,6 @@
-/* //device/java/android/android/hardware/ISensorService.aidl
+/* //device/java/android/android/view/InputChannel.aidl
**
-** Copyright 2008, The Android Open Source Project
+** Copyright 2010, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -15,15 +15,6 @@
** limitations under the License.
*/
-package android.hardware;
+package android.view;
-import android.os.Bundle;
-
-/**
- * {@hide}
- */
-interface ISensorService
-{
- Bundle getDataChannel();
- boolean enableSensor(IBinder listener, String name, int sensor, int enable);
-}
+parcelable InputChannel;
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
new file mode 100644
index 000000000000..f2cad2f94d1d
--- /dev/null
+++ b/core/java/android/view/InputChannel.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+/**
+ * An input channel specifies the file descriptors used to send input events to
+ * a window in another process. It is Parcelable so that it can be sent
+ * to the process that is to receive events. Only one thread should be reading
+ * from an InputChannel at a time.
+ * @hide
+ */
+public final class InputChannel implements Parcelable {
+ private static final String TAG = "InputChannel";
+
+ private static final boolean DEBUG = false;
+
+ public static final Parcelable.Creator<InputChannel> CREATOR
+ = new Parcelable.Creator<InputChannel>() {
+ public InputChannel createFromParcel(Parcel source) {
+ InputChannel result = new InputChannel();
+ result.readFromParcel(source);
+ return result;
+ }
+
+ public InputChannel[] newArray(int size) {
+ return new InputChannel[size];
+ }
+ };
+
+ @SuppressWarnings("unused")
+ private int mPtr; // used by native code
+
+ private boolean mDisposeAfterWriteToParcel;
+
+ private static native InputChannel[] nativeOpenInputChannelPair(String name);
+
+ private native void nativeDispose(boolean finalized);
+ private native void nativeTransferTo(InputChannel other);
+ private native void nativeReadFromParcel(Parcel parcel);
+ private native void nativeWriteToParcel(Parcel parcel);
+
+ private native String nativeGetName();
+
+ /**
+ * Creates an uninitialized input channel.
+ * It can be initialized by reading from a Parcel or by transferring the state of
+ * another input channel into this one.
+ */
+ public InputChannel() {
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ nativeDispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Creates a new input channel pair. One channel should be provided to the input
+ * dispatcher and the other to the application's input queue.
+ * @param name The descriptive (non-unique) name of the channel pair.
+ * @return A pair of input channels. They are symmetric and indistinguishable.
+ */
+ public static InputChannel[] openInputChannelPair(String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG, "Opening input channel pair '" + name + "'");
+ }
+ return nativeOpenInputChannelPair(name);
+ }
+
+ /**
+ * Gets the name of the input channel.
+ * @return The input channel name.
+ */
+ public String getName() {
+ String name = nativeGetName();
+ return name != null ? name : "uninitialized";
+ }
+
+ /**
+ * Disposes the input channel.
+ * Explicitly releases the reference this object is holding on the input channel.
+ * When all references are released, the input channel will be closed.
+ */
+ public void dispose() {
+ nativeDispose(false);
+ }
+
+ /**
+ * Transfers ownership of the internal state of the input channel to another
+ * instance and invalidates this instance. This is used to pass an input channel
+ * as an out parameter in a binder call.
+ * @param other The other input channel instance.
+ */
+ public void transferToBinderOutParameter(InputChannel outParameter) {
+ if (outParameter == null) {
+ throw new IllegalArgumentException("outParameter must not be null");
+ }
+
+ nativeTransferTo(outParameter);
+ outParameter.mDisposeAfterWriteToParcel = true;
+ }
+
+ public int describeContents() {
+ return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+ }
+
+ public void readFromParcel(Parcel in) {
+ if (in == null) {
+ throw new IllegalArgumentException("in must not be null");
+ }
+
+ nativeReadFromParcel(in);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ if (out == null) {
+ throw new IllegalArgumentException("out must not be null");
+ }
+
+ nativeWriteToParcel(out);
+
+ if (mDisposeAfterWriteToParcel) {
+ dispose();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+}
diff --git a/core/java/android/view/InputDevice.aidl b/core/java/android/view/InputDevice.aidl
new file mode 100644
index 000000000000..dbc40c131642
--- /dev/null
+++ b/core/java/android/view/InputDevice.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android.view.InputDevice.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable InputDevice;
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
new file mode 100755
index 000000000000..7468579cb9ae
--- /dev/null
+++ b/core/java/android/view/InputDevice.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * Describes the capabilities of a particular input device.
+ * <p>
+ * Each input device may support multiple classes of input. For example, a multifunction
+ * keyboard may compose the capabilities of a standard keyboard together with a track pad mouse
+ * or other pointing device.
+ * </p><p>
+ * Some input devices present multiple distinguishable sources of input. For example, a
+ * game pad may have two analog joysticks, a directional pad and a full complement of buttons.
+ * Applications can query the framework about the characteristics of each distinct source.
+ * </p><p>
+ * As a further wrinkle, different kinds of input sources uses different coordinate systems
+ * to describe motion events. Refer to the comments on the input source constants for
+ * the appropriate interpretation.
+ * </p>
+ */
+public final class InputDevice implements Parcelable {
+ private int mId;
+ private String mName;
+ private int mSources;
+ private int mKeyboardType;
+
+ private MotionRange[] mMotionRanges;
+
+ /**
+ * A mask for input source classes.
+ *
+ * Each distinct input source constant has one or more input source class bits set to
+ * specify the desired interpretation for its input events.
+ */
+ public static final int SOURCE_CLASS_MASK = 0x000000ff;
+
+ /**
+ * The input source has buttons or keys.
+ * Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_GAMEPAD}, {@link #SOURCE_DPAD}.
+ *
+ * A {@link KeyEvent} should be interpreted as a button or key press.
+ *
+ * Use {@link #getKeyCharacterMap} to query the device's button and key mappings.
+ */
+ public static final int SOURCE_CLASS_BUTTON = 0x00000001;
+
+ /**
+ * The input source is a pointing device associated with a display.
+ * Examples: {@link #SOURCE_TOUCHSCREEN}, {@link #SOURCE_MOUSE}.
+ *
+ * A {@link MotionEvent} should be interpreted as absolute coordinates in
+ * display units according to the {@link View} hierarchy. Pointer down/up indicated when
+ * the finger touches the display or when the selection button is pressed/released.
+ *
+ * Use {@link #getMotionRange} to query the range of the pointing device. Some devices permit
+ * touches outside the display area so the effective range may be somewhat smaller or larger
+ * than the actual display size.
+ */
+ public static final int SOURCE_CLASS_POINTER = 0x00000002;
+
+ /**
+ * The input source is a trackball navigation device.
+ * Examples: {@link #SOURCE_TRACKBALL}.
+ *
+ * A {@link MotionEvent} should be interpreted as relative movements in device-specific
+ * units used for navigation purposes. Pointer down/up indicates when the selection button
+ * is pressed/released.
+ *
+ * Use {@link #getMotionRange} to query the range of motion.
+ */
+ public static final int SOURCE_CLASS_TRACKBALL = 0x00000004;
+
+ /**
+ * The input source is an absolute positioning device not associated with a display
+ * (unlike {@link #SOURCE_CLASS_POINTER}).
+ *
+ * A {@link MotionEvent} should be interpreted as absolute coordinates in
+ * device-specific surface units.
+ *
+ * Use {@link #getMotionRange} to query the range of positions.
+ */
+ public static final int SOURCE_CLASS_POSITION = 0x00000008;
+
+ /**
+ * The input source is a joystick.
+ *
+ * A {@link KeyEvent} should be interpreted as a joystick button press.
+ *
+ * A {@link MotionEvent} should be interpreted in absolute coordinates as a joystick
+ * position in normalized device-specific units nominally between -1.0 and 1.0.
+ *
+ * Use {@link #getMotionRange} to query the range and precision of motion.
+ */
+ public static final int SOURCE_CLASS_JOYSTICK = 0x00000010;
+
+ /**
+ * The input source is unknown.
+ */
+ public static final int SOURCE_UNKNOWN = 0x00000000;
+
+ /**
+ * The input source is a keyboard.
+ *
+ * @see #SOURCE_CLASS_BUTTON
+ */
+ public static final int SOURCE_KEYBOARD = 0x00000100 | SOURCE_CLASS_BUTTON;
+
+ /**
+ * The input source is a DPad.
+ *
+ * @see #SOURCE_CLASS_BUTTON
+ */
+ public static final int SOURCE_DPAD = 0x00000200 | SOURCE_CLASS_BUTTON;
+
+ /**
+ * The input source is a gamepad.
+ *
+ * @see #SOURCE_CLASS_BUTTON
+ */
+ public static final int SOURCE_GAMEPAD = 0x00000400 | SOURCE_CLASS_BUTTON;
+
+ /**
+ * The input source is a touch screen pointing device.
+ *
+ * @see #SOURCE_CLASS_POINTER
+ */
+ public static final int SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER;
+
+ /**
+ * The input source is a mouse pointing device.
+ * This code is also used for other mouse-like pointing devices such as trackpads
+ * and trackpoints.
+ *
+ * @see #SOURCE_CLASS_POINTER
+ */
+ public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER;
+
+ /**
+ * The input source is a trackball.
+ *
+ * @see #SOURCE_CLASS_TRACKBALL
+ */
+ public static final int SOURCE_TRACKBALL = 0x00010000 | SOURCE_CLASS_TRACKBALL;
+
+ /**
+ * The input source is a touch pad or digitizer tablet that is not
+ * associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}).
+ *
+ * @see #SOURCE_CLASS_POSITION
+ */
+ public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION;
+
+ /**
+ * The input source is a joystick mounted on the left or is a standalone joystick.
+ *
+ * @see #SOURCE_CLASS_JOYSTICK
+ */
+ public static final int SOURCE_JOYSTICK_LEFT = 0x01000000 | SOURCE_CLASS_JOYSTICK;
+
+ /**
+ * The input source is a joystick mounted on the right.
+ *
+ * @see #SOURCE_CLASS_JOYSTICK
+ */
+ public static final int SOURCE_JOYSTICK_RIGHT = 0x02000000 | SOURCE_CLASS_JOYSTICK;
+
+ /**
+ * A special input source constant that is used when filtering input devices
+ * to match devices that provide any type of input source.
+ */
+ public static final int SOURCE_ANY = 0xffffff00;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#x}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_X = 0;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#y}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_Y = 1;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#pressure}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_PRESSURE = 2;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#size}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_SIZE = 3;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#touchMajor}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_TOUCH_MAJOR = 4;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#touchMinor}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_TOUCH_MINOR = 5;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#toolMajor}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_TOOL_MAJOR = 6;
+
+ /**
+ * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#toolMinor}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_TOOL_MINOR = 7;
+
+ /**
+ * Constant for retrieving the range of values for
+ * {@link MotionEvent.PointerCoords#orientation}.
+ *
+ * @see #getMotionRange
+ */
+ public static final int MOTION_RANGE_ORIENTATION = 8;
+
+ private static final int MOTION_RANGE_LAST = MOTION_RANGE_ORIENTATION;
+
+ /**
+ * There is no keyboard.
+ */
+ public static final int KEYBOARD_TYPE_NONE = 0;
+
+ /**
+ * The keyboard is not fully alphabetic. It may be a numeric keypad or an assortment
+ * of buttons that are not mapped as alphabetic keys suitable for text input.
+ */
+ public static final int KEYBOARD_TYPE_NON_ALPHABETIC = 1;
+
+ /**
+ * The keyboard supports a complement of alphabetic keys.
+ */
+ public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
+
+ // Called by native code.
+ private InputDevice() {
+ mMotionRanges = new MotionRange[MOTION_RANGE_LAST + 1];
+ }
+
+ /**
+ * Gets information about the input device with the specified id.
+ * @param id The device id.
+ * @return The input device or null if not found.
+ */
+ public static InputDevice getDevice(int id) {
+ IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ try {
+ return wm.getInputDevice(id);
+ } catch (RemoteException ex) {
+ throw new RuntimeException(
+ "Could not get input device information from Window Manager.", ex);
+ }
+ }
+
+ /**
+ * Gets the ids of all input devices in the system.
+ * @return The input device ids.
+ */
+ public static int[] getDeviceIds() {
+ IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+ try {
+ return wm.getInputDeviceIds();
+ } catch (RemoteException ex) {
+ throw new RuntimeException(
+ "Could not get input device ids from Window Manager.", ex);
+ }
+ }
+
+ /**
+ * Gets the input device id.
+ * @return The input device id.
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Gets the name of this input device.
+ * @return The input device name.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the input sources supported by this input device as a combined bitfield.
+ * @return The supported input sources.
+ */
+ public int getSources() {
+ return mSources;
+ }
+
+ /**
+ * Gets the keyboard type.
+ * @return The keyboard type.
+ */
+ public int getKeyboardType() {
+ return mKeyboardType;
+ }
+
+ /**
+ * Gets the key character map associated with this input device.
+ * @return The key character map.
+ */
+ public KeyCharacterMap getKeyCharacterMap() {
+ return KeyCharacterMap.load(mId);
+ }
+
+ /**
+ * Gets information about the range of values for a particular {@link MotionEvent}
+ * coordinate.
+ * @param rangeType The motion range constant.
+ * @return The range of values, or null if the requested coordinate is not
+ * supported by the device.
+ */
+ public MotionRange getMotionRange(int rangeType) {
+ if (rangeType < 0 || rangeType > MOTION_RANGE_LAST) {
+ throw new IllegalArgumentException("Requested range is out of bounds.");
+ }
+
+ return mMotionRanges[rangeType];
+ }
+
+ private void addMotionRange(int rangeType, float min, float max, float flat, float fuzz) {
+ if (rangeType >= 0 && rangeType <= MOTION_RANGE_LAST) {
+ MotionRange range = new MotionRange(min, max, flat, fuzz);
+ mMotionRanges[rangeType] = range;
+ }
+ }
+
+ /**
+ * Provides information about the range of values for a particular {@link MotionEvent}
+ * coordinate.
+ */
+ public static final class MotionRange {
+ private float mMin;
+ private float mMax;
+ private float mFlat;
+ private float mFuzz;
+
+ private MotionRange(float min, float max, float flat, float fuzz) {
+ mMin = min;
+ mMax = max;
+ mFlat = flat;
+ mFuzz = fuzz;
+ }
+
+ /**
+ * Gets the minimum value for the coordinate.
+ * @return The minimum value.
+ */
+ public float getMin() {
+ return mMin;
+ }
+
+ /**
+ * Gets the maximum value for the coordinate.
+ * @return The minimum value.
+ */
+ public float getMax() {
+ return mMax;
+ }
+
+ /**
+ * Gets the range of the coordinate (difference between maximum and minimum).
+ * @return The range of values.
+ */
+ public float getRange() {
+ return mMax - mMin;
+ }
+
+ /**
+ * Gets the extent of the center flat position with respect to this coordinate.
+ * For example, a flat value of 8 means that the center position is between -8 and +8.
+ * This value is mainly useful for calibrating joysticks.
+ * @return The extent of the center flat position.
+ */
+ public float getFlat() {
+ return mFlat;
+ }
+
+ /**
+ * Gets the error tolerance for input device measurements with respect to this coordinate.
+ * For example, a value of 2 indicates that the measured value may be up to +/- 2 units
+ * away from the actual value due to noise and device sensitivity limitations.
+ * @return The error tolerance.
+ */
+ public float getFuzz() {
+ return mFuzz;
+ }
+ }
+
+ public static final Parcelable.Creator<InputDevice> CREATOR
+ = new Parcelable.Creator<InputDevice>() {
+ public InputDevice createFromParcel(Parcel in) {
+ InputDevice result = new InputDevice();
+ result.readFromParcel(in);
+ return result;
+ }
+
+ public InputDevice[] newArray(int size) {
+ return new InputDevice[size];
+ }
+ };
+
+ private void readFromParcel(Parcel in) {
+ mId = in.readInt();
+ mName = in.readString();
+ mSources = in.readInt();
+ mKeyboardType = in.readInt();
+
+ for (;;) {
+ int rangeType = in.readInt();
+ if (rangeType < 0) {
+ break;
+ }
+
+ addMotionRange(rangeType,
+ in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mId);
+ out.writeString(mName);
+ out.writeInt(mSources);
+ out.writeInt(mKeyboardType);
+
+ for (int i = 0; i <= MOTION_RANGE_LAST; i++) {
+ MotionRange range = mMotionRanges[i];
+ if (range != null) {
+ out.writeInt(i);
+ out.writeFloat(range.mMin);
+ out.writeFloat(range.mMax);
+ out.writeFloat(range.mFlat);
+ out.writeFloat(range.mFuzz);
+ }
+ }
+ out.writeInt(-1);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder description = new StringBuilder();
+ description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
+
+ description.append(" Keyboard Type: ");
+ switch (mKeyboardType) {
+ case KEYBOARD_TYPE_NONE:
+ description.append("none");
+ break;
+ case KEYBOARD_TYPE_NON_ALPHABETIC:
+ description.append("non-alphabetic");
+ break;
+ case KEYBOARD_TYPE_ALPHABETIC:
+ description.append("alphabetic");
+ break;
+ }
+ description.append("\n");
+
+ description.append(" Sources:");
+ appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
+ appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
+ appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
+ appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHSCREEN, "touchscreen");
+ appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
+ appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
+ appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
+ appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_LEFT, "joystick_left");
+ appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_RIGHT, "joystick_right");
+ description.append("\n");
+
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_X, "x");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_Y, "y");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_PRESSURE, "pressure");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_SIZE, "size");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOUCH_MAJOR, "touchMajor");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOUCH_MINOR, "touchMinor");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOOL_MAJOR, "toolMajor");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOOL_MINOR, "toolMinor");
+ appendRangeDescriptionIfApplicable(description, MOTION_RANGE_ORIENTATION, "orientation");
+
+ return description.toString();
+ }
+
+ private void appendSourceDescriptionIfApplicable(StringBuilder description, int source,
+ String sourceName) {
+ if ((mSources & source) == source) {
+ description.append(" ");
+ description.append(sourceName);
+ }
+ }
+
+ private void appendRangeDescriptionIfApplicable(StringBuilder description,
+ int rangeType, String rangeName) {
+ MotionRange range = mMotionRanges[rangeType];
+ if (range != null) {
+ description.append(" Range[").append(rangeName);
+ description.append("]: min=").append(range.mMin);
+ description.append(" max=").append(range.mMax);
+ description.append(" flat=").append(range.mFlat);
+ description.append(" fuzz=").append(range.mFuzz);
+ description.append("\n");
+ }
+ }
+}
diff --git a/core/java/android/view/InputEvent.aidl b/core/java/android/view/InputEvent.aidl
new file mode 100644
index 000000000000..61acaaf1fa8d
--- /dev/null
+++ b/core/java/android/view/InputEvent.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android.view.InputEvent.aidl
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable InputEvent;
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
new file mode 100755
index 000000000000..184e0fc8daba
--- /dev/null
+++ b/core/java/android/view/InputEvent.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Common base class for input events.
+ */
+public abstract class InputEvent implements Parcelable {
+ /** @hide */
+ protected int mDeviceId;
+ /** @hide */
+ protected int mSource;
+
+ /** @hide */
+ protected static final int PARCEL_TOKEN_MOTION_EVENT = 1;
+ /** @hide */
+ protected static final int PARCEL_TOKEN_KEY_EVENT = 2;
+
+ /*package*/ InputEvent() {
+ }
+
+ /**
+ * Gets the id for the device that this event came from. An id of
+ * zero indicates that the event didn't come from a physical device
+ * and maps to the default keymap. The other numbers are arbitrary and
+ * you shouldn't depend on the values.
+ *
+ * @return The device id.
+ * @see InputDevice#getDevice
+ */
+ public final int getDeviceId() {
+ return mDeviceId;
+ }
+
+ /**
+ * Gets the device that this event came from.
+ *
+ * @return The device, or null if unknown.
+ */
+ public final InputDevice getDevice() {
+ return InputDevice.getDevice(mDeviceId);
+ }
+
+ /**
+ * Gets the source of the event.
+ *
+ * @return The event source or {@link InputDevice#SOURCE_UNKNOWN} if unknown.
+ * @see InputDevice#getSourceInfo
+ */
+ public final int getSource() {
+ return mSource;
+ }
+
+ /**
+ * Modifies the source of the event.
+ * @param source The source.
+ *
+ * @hide
+ */
+ public final void setSource(int source) {
+ mSource = source;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /** @hide */
+ protected final void readBaseFromParcel(Parcel in) {
+ mDeviceId = in.readInt();
+ mSource = in.readInt();
+ }
+
+ /** @hide */
+ protected final void writeBaseToParcel(Parcel out) {
+ out.writeInt(mDeviceId);
+ out.writeInt(mSource);
+ }
+
+ public static final Parcelable.Creator<InputEvent> CREATOR
+ = new Parcelable.Creator<InputEvent>() {
+ public InputEvent createFromParcel(Parcel in) {
+ int token = in.readInt();
+ if (token == PARCEL_TOKEN_KEY_EVENT) {
+ return KeyEvent.createFromParcelBody(in);
+ } else if (token == PARCEL_TOKEN_MOTION_EVENT) {
+ return MotionEvent.createFromParcelBody(in);
+ } else {
+ throw new IllegalStateException("Unexpected input event type token in parcel.");
+ }
+ }
+
+ public InputEvent[] newArray(int size) {
+ return new InputEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java
new file mode 100644
index 000000000000..41a152dd9577
--- /dev/null
+++ b/core/java/android/view/InputHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Handles input messages that arrive on an input channel.
+ * @hide
+ */
+public interface InputHandler {
+ /**
+ * Handle a key event.
+ * It is the responsibility of the callee to ensure that the finished callback is
+ * eventually invoked when the event processing is finished and the input system
+ * can send the next event.
+ * @param event The key event data.
+ * @param finishedCallback The callback to invoke when event processing is finished.
+ */
+ public void handleKey(KeyEvent event, Runnable finishedCallback);
+
+ /**
+ * Handle a motion event.
+ * It is the responsibility of the callee to ensure that the finished callback is
+ * eventually invoked when the event processing is finished and the input system
+ * can send the next event.
+ * @param event The motion event data.
+ * @param finishedCallback The callback to invoke when event processing is finished.
+ */
+ public void handleMotion(MotionEvent event, Runnable finishedCallback);
+}
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
new file mode 100644
index 000000000000..43c957adebb6
--- /dev/null
+++ b/core/java/android/view/InputQueue.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.MessageQueue;
+import android.util.Slog;
+
+/**
+ * An input queue provides a mechanism for an application to receive incoming
+ * input events. Currently only usable from native code.
+ */
+public final class InputQueue {
+ private static final String TAG = "InputQueue";
+
+ private static final boolean DEBUG = false;
+
+ public static interface Callback {
+ void onInputQueueCreated(InputQueue queue);
+ void onInputQueueDestroyed(InputQueue queue);
+ }
+
+ final InputChannel mChannel;
+
+ private static final Object sLock = new Object();
+
+ private static native void nativeRegisterInputChannel(InputChannel inputChannel,
+ InputHandler inputHandler, MessageQueue messageQueue);
+ private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
+ private static native void nativeFinished(long finishedToken);
+
+ /** @hide */
+ public InputQueue(InputChannel channel) {
+ mChannel = channel;
+ }
+
+ /** @hide */
+ public InputChannel getInputChannel() {
+ return mChannel;
+ }
+
+ /**
+ * Registers an input channel and handler.
+ * @param inputChannel The input channel to register.
+ * @param inputHandler The input handler to input events send to the target.
+ * @param messageQueue The message queue on whose thread the handler should be invoked.
+ * @hide
+ */
+ public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,
+ MessageQueue messageQueue) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+ if (inputHandler == null) {
+ throw new IllegalArgumentException("inputHandler must not be null");
+ }
+ if (messageQueue == null) {
+ throw new IllegalArgumentException("messageQueue must not be null");
+ }
+
+ synchronized (sLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Registering input channel '" + inputChannel + "'");
+ }
+
+ nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);
+ }
+ }
+
+ /**
+ * Unregisters an input channel.
+ * Does nothing if the channel is not currently registered.
+ * @param inputChannel The input channel to unregister.
+ * @hide
+ */
+ public static void unregisterInputChannel(InputChannel inputChannel) {
+ if (inputChannel == null) {
+ throw new IllegalArgumentException("inputChannel must not be null");
+ }
+
+ synchronized (sLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'");
+ }
+
+ nativeUnregisterInputChannel(inputChannel);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static void dispatchKeyEvent(InputHandler inputHandler,
+ KeyEvent event, long finishedToken) {
+ Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
+ inputHandler.handleKey(event, finishedCallback);
+ }
+
+ @SuppressWarnings("unused")
+ private static void dispatchMotionEvent(InputHandler inputHandler,
+ MotionEvent event, long finishedToken) {
+ Runnable finishedCallback = FinishedCallback.obtain(finishedToken);
+ inputHandler.handleMotion(event, finishedCallback);
+ }
+
+ private static class FinishedCallback implements Runnable {
+ private static final boolean DEBUG_RECYCLING = false;
+
+ private static final int RECYCLE_MAX_COUNT = 4;
+
+ private static FinishedCallback sRecycleHead;
+ private static int sRecycleCount;
+
+ private FinishedCallback mRecycleNext;
+ private long mFinishedToken;
+
+ private FinishedCallback() {
+ }
+
+ public static FinishedCallback obtain(long finishedToken) {
+ synchronized (sLock) {
+ FinishedCallback callback = sRecycleHead;
+ if (callback != null) {
+ sRecycleHead = callback.mRecycleNext;
+ sRecycleCount -= 1;
+ callback.mRecycleNext = null;
+ } else {
+ callback = new FinishedCallback();
+ }
+ callback.mFinishedToken = finishedToken;
+ return callback;
+ }
+ }
+
+ public void run() {
+ synchronized (sLock) {
+ if (mFinishedToken == -1) {
+ throw new IllegalStateException("Event finished callback already invoked.");
+ }
+
+ nativeFinished(mFinishedToken);
+ mFinishedToken = -1;
+
+ if (sRecycleCount < RECYCLE_MAX_COUNT) {
+ mRecycleNext = sRecycleHead;
+ sRecycleHead = this;
+ sRecycleCount += 1;
+
+ if (DEBUG_RECYCLING) {
+ Slog.d(TAG, "Recycled finished callbacks: " + sRecycleCount);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 25958aa46314..9981d877ce02 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -26,6 +26,9 @@ import android.util.SparseArray;
import java.lang.Character;
import java.lang.ref.WeakReference;
+/**
+ * Describes the keys provided by a device and their associated labels.
+ */
public class KeyCharacterMap
{
/**
@@ -59,6 +62,11 @@ public class KeyCharacterMap
private static SparseArray<WeakReference<KeyCharacterMap>> sInstances
= new SparseArray<WeakReference<KeyCharacterMap>>();
+ /**
+ * Loads the key character maps for the keyboard with the specified device id.
+ * @param keyboard The device id of the keyboard.
+ * @return The associated key character map.
+ */
public static KeyCharacterMap load(int keyboard)
{
synchronized (sLock) {
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index d4f978756f4d..ed10e412ac51 100644..100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -26,7 +26,7 @@ import android.view.KeyCharacterMap.KeyData;
/**
* Contains constants for key events.
*/
-public class KeyEvent implements Parcelable {
+public class KeyEvent extends InputEvent implements Parcelable {
// key codes
public static final int KEYCODE_UNKNOWN = 0;
public static final int KEYCODE_SOFT_LEFT = 1;
@@ -120,10 +120,31 @@ public class KeyEvent implements Parcelable {
public static final int KEYCODE_MEDIA_REWIND = 89;
public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
public static final int KEYCODE_MUTE = 91;
+ public static final int KEYCODE_PAGE_UP = 92;
+ public static final int KEYCODE_PAGE_DOWN = 93;
+ public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji)
+ public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
+ public static final int KEYCODE_BUTTON_A = 96;
+ public static final int KEYCODE_BUTTON_B = 97;
+ public static final int KEYCODE_BUTTON_C = 98;
+ public static final int KEYCODE_BUTTON_X = 99;
+ public static final int KEYCODE_BUTTON_Y = 100;
+ public static final int KEYCODE_BUTTON_Z = 101;
+ public static final int KEYCODE_BUTTON_L1 = 102;
+ public static final int KEYCODE_BUTTON_R1 = 103;
+ public static final int KEYCODE_BUTTON_L2 = 104;
+ public static final int KEYCODE_BUTTON_R2 = 105;
+ public static final int KEYCODE_BUTTON_THUMBL = 106;
+ public static final int KEYCODE_BUTTON_THUMBR = 107;
+ public static final int KEYCODE_BUTTON_START = 108;
+ public static final int KEYCODE_BUTTON_SELECT = 109;
+ public static final int KEYCODE_BUTTON_MODE = 110;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
+ // native/include/android/keycodes.h
// frameworks/base/include/ui/KeycodeLabels.h
+ // external/webkit/WebKit/android/plugins/ANPKeyCodes.h
// tools/puppet_master/PuppetMaster/nav_keys.py
// frameworks/base/core/res/res/values/attrs.xml
// commands/monkey/Monkey.java
@@ -135,7 +156,7 @@ public class KeyEvent implements Parcelable {
// those new codes. This is intended to maintain a consistent
// set of key code definitions across all Android devices.
- private static final int LAST_KEYCODE = KEYCODE_MUTE;
+ private static final int LAST_KEYCODE = KEYCODE_BUTTON_MODE;
/**
* @deprecated There are now more than MAX_KEYCODE keycodes.
@@ -158,7 +179,7 @@ public class KeyEvent implements Parcelable {
* key code is not {#link {@link #KEYCODE_UNKNOWN} then the
* {#link {@link #getRepeatCount()} method returns the number of times
* the given key code should be executed.
- * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then
+ * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then
* this is a sequence of characters as returned by {@link #getCharacters}.
*/
public static final int ACTION_MULTIPLE = 2;
@@ -326,9 +347,8 @@ public class KeyEvent implements Parcelable {
private int mMetaState;
private int mAction;
private int mKeyCode;
- private int mScancode;
+ private int mScanCode;
private int mRepeatCount;
- private int mDeviceId;
private int mFlags;
private long mDownTime;
private long mEventTime;
@@ -463,20 +483,20 @@ public class KeyEvent implements Parcelable {
* @param repeat A repeat count for down events (> 0 if this is after the
* initial down) or event count for multiple events.
* @param metaState Flags indicating which meta keys are currently pressed.
- * @param device The device ID that generated the key event.
+ * @param deviceId The device ID that generated the key event.
* @param scancode Raw device scan code of the event.
*/
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat, int metaState,
- int device, int scancode) {
+ int deviceId, int scancode) {
mDownTime = downTime;
mEventTime = eventTime;
mAction = action;
mKeyCode = code;
mRepeatCount = repeat;
mMetaState = metaState;
- mDeviceId = device;
- mScancode = scancode;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
}
/**
@@ -492,44 +512,79 @@ public class KeyEvent implements Parcelable {
* @param repeat A repeat count for down events (> 0 if this is after the
* initial down) or event count for multiple events.
* @param metaState Flags indicating which meta keys are currently pressed.
- * @param device The device ID that generated the key event.
+ * @param deviceId The device ID that generated the key event.
* @param scancode Raw device scan code of the event.
* @param flags The flags for this key event
*/
public KeyEvent(long downTime, long eventTime, int action,
int code, int repeat, int metaState,
- int device, int scancode, int flags) {
+ int deviceId, int scancode, int flags) {
mDownTime = downTime;
mEventTime = eventTime;
mAction = action;
mKeyCode = code;
mRepeatCount = repeat;
mMetaState = metaState;
- mDeviceId = device;
- mScancode = scancode;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
mFlags = flags;
}
/**
+ * Create a new key event.
+ *
+ * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this key code originally went down.
+ * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis})
+ * at which this event happened.
+ * @param action Action code: either {@link #ACTION_DOWN},
+ * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}.
+ * @param code The key code.
+ * @param repeat A repeat count for down events (> 0 if this is after the
+ * initial down) or event count for multiple events.
+ * @param metaState Flags indicating which meta keys are currently pressed.
+ * @param deviceId The device ID that generated the key event.
+ * @param scancode Raw device scan code of the event.
+ * @param flags The flags for this key event
+ * @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}.
+ */
+ public KeyEvent(long downTime, long eventTime, int action,
+ int code, int repeat, int metaState,
+ int deviceId, int scancode, int flags, int source) {
+ mDownTime = downTime;
+ mEventTime = eventTime;
+ mAction = action;
+ mKeyCode = code;
+ mRepeatCount = repeat;
+ mMetaState = metaState;
+ mDeviceId = deviceId;
+ mScanCode = scancode;
+ mFlags = flags;
+ mSource = source;
+ }
+
+ /**
* Create a new key event for a string of characters. The key code,
- * action, and repeat could will automatically be set to
- * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, and 0 for you.
+ * action, repeat count and source will automatically be set to
+ * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and
+ * {@link InputDevice#SOURCE_KEYBOARD} for you.
*
* @param time The time (in {@link android.os.SystemClock#uptimeMillis})
* at which this event occured.
* @param characters The string of characters.
- * @param device The device ID that generated the key event.
+ * @param deviceId The device ID that generated the key event.
* @param flags The flags for this key event
*/
- public KeyEvent(long time, String characters, int device, int flags) {
+ public KeyEvent(long time, String characters, int deviceId, int flags) {
mDownTime = time;
mEventTime = time;
mCharacters = characters;
mAction = ACTION_MULTIPLE;
mKeyCode = KEYCODE_UNKNOWN;
mRepeatCount = 0;
- mDeviceId = device;
+ mDeviceId = deviceId;
mFlags = flags;
+ mSource = InputDevice.SOURCE_KEYBOARD;
}
/**
@@ -543,7 +598,8 @@ public class KeyEvent implements Parcelable {
mRepeatCount = origEvent.mRepeatCount;
mMetaState = origEvent.mMetaState;
mDeviceId = origEvent.mDeviceId;
- mScancode = origEvent.mScancode;
+ mSource = origEvent.mSource;
+ mScanCode = origEvent.mScanCode;
mFlags = origEvent.mFlags;
mCharacters = origEvent.mCharacters;
}
@@ -568,7 +624,8 @@ public class KeyEvent implements Parcelable {
mRepeatCount = newRepeat;
mMetaState = origEvent.mMetaState;
mDeviceId = origEvent.mDeviceId;
- mScancode = origEvent.mScancode;
+ mSource = origEvent.mSource;
+ mScanCode = origEvent.mScanCode;
mFlags = origEvent.mFlags;
mCharacters = origEvent.mCharacters;
}
@@ -621,7 +678,8 @@ public class KeyEvent implements Parcelable {
mRepeatCount = origEvent.mRepeatCount;
mMetaState = origEvent.mMetaState;
mDeviceId = origEvent.mDeviceId;
- mScancode = origEvent.mScancode;
+ mSource = origEvent.mSource;
+ mScanCode = origEvent.mScanCode;
mFlags = origEvent.mFlags;
// Don't copy mCharacters, since one way or the other we'll lose it
// when changing the action.
@@ -671,31 +729,12 @@ public class KeyEvent implements Parcelable {
* TODO: should the dpad keys be here? arguably, because they also shouldn't be menu shortcuts
*/
public final boolean isSystem() {
- switch (mKeyCode) {
- case KEYCODE_MENU:
- case KEYCODE_SOFT_RIGHT:
- case KEYCODE_HOME:
- case KEYCODE_BACK:
- case KEYCODE_CALL:
- case KEYCODE_ENDCALL:
- case KEYCODE_VOLUME_UP:
- case KEYCODE_VOLUME_DOWN:
- case KEYCODE_MUTE:
- case KEYCODE_POWER:
- case KEYCODE_HEADSETHOOK:
- case KEYCODE_MEDIA_PLAY_PAUSE:
- case KEYCODE_MEDIA_STOP:
- case KEYCODE_MEDIA_NEXT:
- case KEYCODE_MEDIA_PREVIOUS:
- case KEYCODE_MEDIA_REWIND:
- case KEYCODE_MEDIA_FAST_FORWARD:
- case KEYCODE_CAMERA:
- case KEYCODE_FOCUS:
- case KEYCODE_SEARCH:
- return true;
- default:
- return false;
- }
+ return native_isSystemKey(mKeyCode);
+ }
+
+ /** @hide */
+ public final boolean hasDefaultAction() {
+ return native_hasDefaultAction(mKeyCode);
}
@@ -853,7 +892,7 @@ public class KeyEvent implements Parcelable {
* Mostly this is here for debugging purposes.
*/
public final int getScanCode() {
- return mScancode;
+ return mScanCode;
}
/**
@@ -895,18 +934,6 @@ public class KeyEvent implements Parcelable {
}
/**
- * Return the id for the keyboard that this event came from. A device
- * id of 0 indicates the event didn't come from a physical device and
- * maps to the default keymap. The other numbers are arbitrary and
- * you shouldn't depend on the values.
- *
- * @see KeyCharacterMap#load
- */
- public final int getDeviceId() {
- return mDeviceId;
- }
-
- /**
* Renamed to {@link #getDeviceId}.
*
* @hide
@@ -1177,46 +1204,55 @@ public class KeyEvent implements Parcelable {
public String toString() {
return "KeyEvent{action=" + mAction + " code=" + mKeyCode
+ " repeat=" + mRepeatCount
- + " meta=" + mMetaState + " scancode=" + mScancode
+ + " meta=" + mMetaState + " scancode=" + mScanCode
+ " mFlags=" + mFlags + "}";
}
public static final Parcelable.Creator<KeyEvent> CREATOR
= new Parcelable.Creator<KeyEvent>() {
public KeyEvent createFromParcel(Parcel in) {
- return new KeyEvent(in);
+ in.readInt(); // skip token, we already know this is a KeyEvent
+ return KeyEvent.createFromParcelBody(in);
}
public KeyEvent[] newArray(int size) {
return new KeyEvent[size];
}
};
-
- public int describeContents() {
- return 0;
+
+ /** @hide */
+ public static KeyEvent createFromParcelBody(Parcel in) {
+ return new KeyEvent(in);
+ }
+
+ private KeyEvent(Parcel in) {
+ readBaseFromParcel(in);
+
+ mAction = in.readInt();
+ mKeyCode = in.readInt();
+ mRepeatCount = in.readInt();
+ mMetaState = in.readInt();
+ mScanCode = in.readInt();
+ mFlags = in.readInt();
+ mDownTime = in.readLong();
+ mEventTime = in.readLong();
}
public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(PARCEL_TOKEN_KEY_EVENT);
+
+ writeBaseToParcel(out);
+
out.writeInt(mAction);
out.writeInt(mKeyCode);
out.writeInt(mRepeatCount);
out.writeInt(mMetaState);
- out.writeInt(mDeviceId);
- out.writeInt(mScancode);
+ out.writeInt(mScanCode);
out.writeInt(mFlags);
out.writeLong(mDownTime);
out.writeLong(mEventTime);
}
- private KeyEvent(Parcel in) {
- mAction = in.readInt();
- mKeyCode = in.readInt();
- mRepeatCount = in.readInt();
- mMetaState = in.readInt();
- mDeviceId = in.readInt();
- mScancode = in.readInt();
- mFlags = in.readInt();
- mDownTime = in.readLong();
- mEventTime = in.readLong();
- }
+ private native boolean native_isSystemKey(int keyCode);
+ private native boolean native_hasDefaultAction(int keyCode);
}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index e5985c179e9d..194c01357613 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -348,6 +348,7 @@ public abstract class LayoutInflater {
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
+ Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root;
@@ -432,6 +433,10 @@ public abstract class LayoutInflater {
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
+ } finally {
+ // Don't retain static reference on context.
+ mConstructorArgs[0] = lastContext;
+ mConstructorArgs[1] = null;
}
return result;
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index d648e96693c7..78b9b5d8a84a 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -19,15 +19,18 @@ package android.view;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
-import android.util.Log;
/**
* Object used to report movement (mouse, pen, finger, trackball) events. This
* class may hold either absolute or relative movements, depending on what
* it is being used for.
+ *
+ * Refer to {@link InputDevice} for information about how different kinds of
+ * input devices and sources represent pointer coordinates.
*/
-public final class MotionEvent implements Parcelable {
- static final boolean DEBUG_POINTERS = false;
+public final class MotionEvent extends InputEvent implements Parcelable {
+ private static final long MS_PER_NS = 1000000;
+ private static final boolean TRACK_RECYCLED_LOCATION = false;
/**
* Bit mask of the parts of the action code that are the action itself.
@@ -153,7 +156,17 @@ public final class MotionEvent implements Parcelable {
@Deprecated
public static final int ACTION_POINTER_ID_SHIFT = 8;
- private static final boolean TRACK_RECYCLED_LOCATION = false;
+ /**
+ * This flag indicates that the window that received this motion event is partly
+ * or wholly obscured by another visible window above it. This flag is set to true
+ * even if the event did not directly pass through the obscured area.
+ * A security sensitive application can check this flag to identify situations in which
+ * a malicious application may have covered up part of its content for the purpose
+ * of misleading the user or hijacking touches. An appropriate response might be
+ * to drop the suspect touches or to take additional precautions to confirm the user's
+ * actual intent.
+ */
+ public static final int FLAG_WINDOW_IS_OBSCURED = 0x1;
/**
* Flag indicating the motion event intersected the top edge of the screen.
@@ -175,42 +188,65 @@ public final class MotionEvent implements Parcelable {
*/
public static final int EDGE_RIGHT = 0x00000008;
- /**
+ /*
* Offset for the sample's X coordinate.
- * @hide
*/
- static public final int SAMPLE_X = 0;
+ static private final int SAMPLE_X = 0;
- /**
+ /*
* Offset for the sample's Y coordinate.
- * @hide
*/
- static public final int SAMPLE_Y = 1;
+ static private final int SAMPLE_Y = 1;
- /**
- * Offset for the sample's X coordinate.
- * @hide
+ /*
+ * Offset for the sample's pressure.
*/
- static public final int SAMPLE_PRESSURE = 2;
+ static private final int SAMPLE_PRESSURE = 2;
- /**
- * Offset for the sample's X coordinate.
- * @hide
+ /*
+ * Offset for the sample's size
*/
- static public final int SAMPLE_SIZE = 3;
+ static private final int SAMPLE_SIZE = 3;
- /**
+ /*
+ * Offset for the sample's touch major axis length.
+ */
+ static private final int SAMPLE_TOUCH_MAJOR = 4;
+
+ /*
+ * Offset for the sample's touch minor axis length.
+ */
+ static private final int SAMPLE_TOUCH_MINOR = 5;
+
+ /*
+ * Offset for the sample's tool major axis length.
+ */
+ static private final int SAMPLE_TOOL_MAJOR = 6;
+
+ /*
+ * Offset for the sample's tool minor axis length.
+ */
+ static private final int SAMPLE_TOOL_MINOR = 7;
+
+ /*
+ * Offset for the sample's orientation.
+ */
+ static private final int SAMPLE_ORIENTATION = 8;
+
+ /*
* Number of data items for each sample.
- * @hide
*/
- static public final int NUM_SAMPLE_DATA = 4;
+ static private final int NUM_SAMPLE_DATA = 9;
- /**
- * Number of possible pointers.
- * @hide
+ /*
+ * Minimum number of pointers for which to reserve space when allocating new
+ * motion events. This is explicitly not a bound on the maximum number of pointers.
*/
- static public final int BASE_AVAIL_POINTERS = 5;
+ static private final int BASE_AVAIL_POINTERS = 5;
+ /*
+ * Minimum number of samples for which to reserve space when allocating new motion events.
+ */
static private final int BASE_AVAIL_SAMPLES = 8;
static private final int MAX_RECYCLED = 10;
@@ -218,74 +254,95 @@ public final class MotionEvent implements Parcelable {
static private int gRecyclerUsed = 0;
static private MotionEvent gRecyclerTop = null;
- private long mDownTime;
- private long mEventTimeNano;
+ private long mDownTimeNano;
private int mAction;
- private float mRawX;
- private float mRawY;
+ private float mXOffset;
+ private float mYOffset;
private float mXPrecision;
private float mYPrecision;
- private int mDeviceId;
private int mEdgeFlags;
private int mMetaState;
-
- // Here is the actual event data. Note that the order of the array
- // is a little odd: the first entry is the most recent, and the ones
- // following it are the historical data from oldest to newest. This
- // allows us to easily retrieve the most recent data, without having
- // to copy the arrays every time a new sample is added.
+ private int mFlags;
private int mNumPointers;
private int mNumSamples;
+
+ private int mLastDataSampleIndex;
+ private int mLastEventTimeNanoSampleIndex;
+
// Array of mNumPointers size of identifiers for each pointer of data.
private int[] mPointerIdentifiers;
+
// Array of (mNumSamples * mNumPointers * NUM_SAMPLE_DATA) size of event data.
+ // Samples are ordered from oldest to newest.
private float[] mDataSamples;
- // Array of mNumSamples size of time stamps.
- private long[] mTimeSamples;
+
+ // Array of mNumSamples size of event time stamps in nanoseconds.
+ // Samples are ordered from oldest to newest.
+ private long[] mEventTimeNanoSamples;
private MotionEvent mNext;
private RuntimeException mRecycledLocation;
private boolean mRecycled;
- private MotionEvent() {
- mPointerIdentifiers = new int[BASE_AVAIL_POINTERS];
- mDataSamples = new float[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES*NUM_SAMPLE_DATA];
- mTimeSamples = new long[BASE_AVAIL_SAMPLES];
+ private MotionEvent(int pointerCount, int sampleCount) {
+ mPointerIdentifiers = new int[pointerCount];
+ mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
+ mEventTimeNanoSamples = new long[sampleCount];
}
- static private MotionEvent obtain() {
+ static private MotionEvent obtain(int pointerCount, int sampleCount) {
+ final MotionEvent ev;
synchronized (gRecyclerLock) {
if (gRecyclerTop == null) {
- return new MotionEvent();
+ if (pointerCount < BASE_AVAIL_POINTERS) {
+ pointerCount = BASE_AVAIL_POINTERS;
+ }
+ if (sampleCount < BASE_AVAIL_SAMPLES) {
+ sampleCount = BASE_AVAIL_SAMPLES;
+ }
+ return new MotionEvent(pointerCount, sampleCount);
}
- MotionEvent ev = gRecyclerTop;
+ ev = gRecyclerTop;
gRecyclerTop = ev.mNext;
- gRecyclerUsed--;
- ev.mRecycledLocation = null;
- ev.mRecycled = false;
- return ev;
+ gRecyclerUsed -= 1;
+ }
+ ev.mRecycledLocation = null;
+ ev.mRecycled = false;
+ ev.mNext = null;
+
+ if (ev.mPointerIdentifiers.length < pointerCount) {
+ ev.mPointerIdentifiers = new int[pointerCount];
+ }
+
+ if (ev.mEventTimeNanoSamples.length < sampleCount) {
+ ev.mEventTimeNanoSamples = new long[sampleCount];
+ }
+
+ final int neededDataSamplesLength = pointerCount * sampleCount * NUM_SAMPLE_DATA;
+ if (ev.mDataSamples.length < neededDataSamplesLength) {
+ ev.mDataSamples = new float[neededDataSamplesLength];
}
+
+ return ev;
}
-
+
/**
* Create a new MotionEvent, filling in all of the basic values that
* define the motion.
*
* @param downTime The time (in ms) when the user originally pressed down to start
* a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}.
- * @param eventTime The the time (in ms) when this specific event was generated. This
+ * @param eventTime The the time (in ms) when this specific event was generated. This
* must be obtained from {@link SystemClock#uptimeMillis()}.
- * @param eventTimeNano The the time (in ns) when this specific event was generated. This
- * must be obtained from {@link System#nanoTime()}.
* @param action The kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
* {@link #ACTION_CANCEL}.
* @param pointers The number of points that will be in this event.
- * @param inPointerIds An array of <em>pointers</em> values providing
+ * @param pointerIds An array of <em>pointers</em> values providing
* an identifier for each pointer.
- * @param inData An array of <em>pointers*NUM_SAMPLE_DATA</em> of initial
- * data samples for the event.
+ * @param pointerCoords An array of <em>pointers</em> values providing
+ * a {@link PointerCoords} coordinate object for each pointer.
* @param metaState The state of any meta / modifier keys that were in effect when
* the event was generated.
* @param xPrecision The precision of the X coordinate being reported.
@@ -293,57 +350,39 @@ public final class MotionEvent implements Parcelable {
* @param deviceId The id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
- * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
* MotionEvent.
- *
- * @hide
- */
- static public MotionEvent obtainNano(long downTime, long eventTime, long eventTimeNano,
- int action, int pointers, int[] inPointerIds, float[] inData, int metaState,
- float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
- MotionEvent ev = obtain();
+ * @param source The source of this event.
+ * @param flags The motion event flags.
+ */
+ static public MotionEvent obtain(long downTime, long eventTime,
+ int action, int pointers, int[] pointerIds, PointerCoords[] pointerCoords,
+ int metaState, float xPrecision, float yPrecision, int deviceId,
+ int edgeFlags, int source, int flags) {
+ MotionEvent ev = obtain(pointers, 1);
ev.mDeviceId = deviceId;
+ ev.mSource = source;
ev.mEdgeFlags = edgeFlags;
- ev.mDownTime = downTime;
- ev.mEventTimeNano = eventTimeNano;
+ ev.mDownTimeNano = downTime * MS_PER_NS;
ev.mAction = action;
+ ev.mFlags = flags;
ev.mMetaState = metaState;
- ev.mRawX = inData[SAMPLE_X];
- ev.mRawY = inData[SAMPLE_Y];
+ ev.mXOffset = 0;
+ ev.mYOffset = 0;
ev.mXPrecision = xPrecision;
ev.mYPrecision = yPrecision;
+
ev.mNumPointers = pointers;
ev.mNumSamples = 1;
- int[] pointerIdentifiers = ev.mPointerIdentifiers;
- if (pointerIdentifiers.length < pointers) {
- ev.mPointerIdentifiers = pointerIdentifiers = new int[pointers];
- }
- System.arraycopy(inPointerIds, 0, pointerIdentifiers, 0, pointers);
+ ev.mLastDataSampleIndex = 0;
+ ev.mLastEventTimeNanoSampleIndex = 0;
- final int ND = pointers * NUM_SAMPLE_DATA;
- float[] dataSamples = ev.mDataSamples;
- if (dataSamples.length < ND) {
- ev.mDataSamples = dataSamples = new float[ND];
- }
- System.arraycopy(inData, 0, dataSamples, 0, ND);
+ System.arraycopy(pointerIds, 0, ev.mPointerIdentifiers, 0, pointers);
- ev.mTimeSamples[0] = eventTime;
-
- if (DEBUG_POINTERS) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("New:");
- for (int i=0; i<pointers; i++) {
- sb.append(" #");
- sb.append(ev.mPointerIdentifiers[i]);
- sb.append("(");
- sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
- sb.append(",");
- sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
- sb.append(")");
- }
- Log.v("MotionEvent", sb.toString());
- }
+ ev.mEventTimeNanoSamples[0] = eventTime * MS_PER_NS;
+
+ ev.setPointerCoordsAtSampleIndex(0, pointerCoords);
return ev;
}
@@ -376,33 +415,36 @@ public final class MotionEvent implements Parcelable {
* @param deviceId The id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
- * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
* MotionEvent.
*/
static public MotionEvent obtain(long downTime, long eventTime, int action,
float x, float y, float pressure, float size, int metaState,
float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
- MotionEvent ev = obtain();
+ MotionEvent ev = obtain(1, 1);
ev.mDeviceId = deviceId;
+ ev.mSource = InputDevice.SOURCE_UNKNOWN;
ev.mEdgeFlags = edgeFlags;
- ev.mDownTime = downTime;
- ev.mEventTimeNano = eventTime * 1000000;
+ ev.mDownTimeNano = downTime * MS_PER_NS;
ev.mAction = action;
+ ev.mFlags = 0;
ev.mMetaState = metaState;
+ ev.mXOffset = 0;
+ ev.mYOffset = 0;
ev.mXPrecision = xPrecision;
ev.mYPrecision = yPrecision;
-
+
ev.mNumPointers = 1;
ev.mNumSamples = 1;
- int[] pointerIds = ev.mPointerIdentifiers;
- pointerIds[0] = 0;
- float[] data = ev.mDataSamples;
- data[SAMPLE_X] = ev.mRawX = x;
- data[SAMPLE_Y] = ev.mRawY = y;
- data[SAMPLE_PRESSURE] = pressure;
- data[SAMPLE_SIZE] = size;
- ev.mTimeSamples[0] = eventTime;
-
+
+ ev.mLastDataSampleIndex = 0;
+ ev.mLastEventTimeNanoSampleIndex = 0;
+
+ ev.mPointerIdentifiers[0] = 0;
+
+ ev.mEventTimeNanoSamples[0] = eventTime * MS_PER_NS;
+
+ ev.setPointerCoordsAtSampleIndex(0, x, y, pressure, size);
return ev;
}
@@ -435,35 +477,18 @@ public final class MotionEvent implements Parcelable {
* @param deviceId The id for the device that this event came from. An id of
* zero indicates that the event didn't come from a physical device; other
* numbers are arbitrary and you shouldn't depend on the values.
- * @param edgeFlags A bitfield indicating which edges, if any, where touched by this
+ * @param edgeFlags A bitfield indicating which edges, if any, were touched by this
* MotionEvent.
+ *
+ * @deprecated Use {@link #obtain(long, long, int, float, float, float, float, int, float, float, int, int)}
+ * instead.
*/
+ @Deprecated
static public MotionEvent obtain(long downTime, long eventTime, int action,
int pointers, float x, float y, float pressure, float size, int metaState,
float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
- MotionEvent ev = obtain();
- ev.mDeviceId = deviceId;
- ev.mEdgeFlags = edgeFlags;
- ev.mDownTime = downTime;
- ev.mEventTimeNano = eventTime * 1000000;
- ev.mAction = action;
- ev.mNumPointers = pointers;
- ev.mMetaState = metaState;
- ev.mXPrecision = xPrecision;
- ev.mYPrecision = yPrecision;
-
- ev.mNumPointers = 1;
- ev.mNumSamples = 1;
- int[] pointerIds = ev.mPointerIdentifiers;
- pointerIds[0] = 0;
- float[] data = ev.mDataSamples;
- data[SAMPLE_X] = ev.mRawX = x;
- data[SAMPLE_Y] = ev.mRawY = y;
- data[SAMPLE_PRESSURE] = pressure;
- data[SAMPLE_SIZE] = size;
- ev.mTimeSamples[0] = eventTime;
-
- return ev;
+ return obtain(downTime, eventTime, action, x, y, pressure, size,
+ metaState, xPrecision, yPrecision, deviceId, edgeFlags);
}
/**
@@ -485,89 +510,38 @@ public final class MotionEvent implements Parcelable {
*/
static public MotionEvent obtain(long downTime, long eventTime, int action,
float x, float y, int metaState) {
- MotionEvent ev = obtain();
- ev.mDeviceId = 0;
- ev.mEdgeFlags = 0;
- ev.mDownTime = downTime;
- ev.mEventTimeNano = eventTime * 1000000;
- ev.mAction = action;
- ev.mNumPointers = 1;
- ev.mMetaState = metaState;
- ev.mXPrecision = 1.0f;
- ev.mYPrecision = 1.0f;
-
- ev.mNumPointers = 1;
- ev.mNumSamples = 1;
- int[] pointerIds = ev.mPointerIdentifiers;
- pointerIds[0] = 0;
- float[] data = ev.mDataSamples;
- data[SAMPLE_X] = ev.mRawX = x;
- data[SAMPLE_Y] = ev.mRawY = y;
- data[SAMPLE_PRESSURE] = 1.0f;
- data[SAMPLE_SIZE] = 1.0f;
- ev.mTimeSamples[0] = eventTime;
-
- return ev;
- }
-
- /**
- * Scales down the coordination of this event by the given scale.
- *
- * @hide
- */
- public void scale(float scale) {
- mRawX *= scale;
- mRawY *= scale;
- mXPrecision *= scale;
- mYPrecision *= scale;
- float[] history = mDataSamples;
- final int length = mNumPointers * mNumSamples * NUM_SAMPLE_DATA;
- for (int i = 0; i < length; i += NUM_SAMPLE_DATA) {
- history[i + SAMPLE_X] *= scale;
- history[i + SAMPLE_Y] *= scale;
- // no need to scale pressure
- history[i + SAMPLE_SIZE] *= scale; // TODO: square this?
- }
+ return obtain(downTime, eventTime, action, x, y, 1.0f, 1.0f,
+ metaState, 1.0f, 1.0f, 0, 0);
}
/**
* Create a new MotionEvent, copying from an existing one.
*/
static public MotionEvent obtain(MotionEvent o) {
- MotionEvent ev = obtain();
+ MotionEvent ev = obtain(o.mNumPointers, o.mNumSamples);
ev.mDeviceId = o.mDeviceId;
+ ev.mSource = o.mSource;
ev.mEdgeFlags = o.mEdgeFlags;
- ev.mDownTime = o.mDownTime;
- ev.mEventTimeNano = o.mEventTimeNano;
+ ev.mDownTimeNano = o.mDownTimeNano;
ev.mAction = o.mAction;
- ev.mNumPointers = o.mNumPointers;
- ev.mRawX = o.mRawX;
- ev.mRawY = o.mRawY;
+ ev.mFlags = o.mFlags;
ev.mMetaState = o.mMetaState;
+ ev.mXOffset = o.mXOffset;
+ ev.mYOffset = o.mYOffset;
ev.mXPrecision = o.mXPrecision;
ev.mYPrecision = o.mYPrecision;
+ int numPointers = ev.mNumPointers = o.mNumPointers;
+ int numSamples = ev.mNumSamples = o.mNumSamples;
- final int NS = ev.mNumSamples = o.mNumSamples;
- if (ev.mTimeSamples.length >= NS) {
- System.arraycopy(o.mTimeSamples, 0, ev.mTimeSamples, 0, NS);
- } else {
- ev.mTimeSamples = (long[])o.mTimeSamples.clone();
- }
+ ev.mLastDataSampleIndex = o.mLastDataSampleIndex;
+ ev.mLastEventTimeNanoSampleIndex = o.mLastEventTimeNanoSampleIndex;
- final int NP = (ev.mNumPointers=o.mNumPointers);
- if (ev.mPointerIdentifiers.length >= NP) {
- System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP);
- } else {
- ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone();
- }
+ System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, numPointers);
- final int ND = NP * NS * NUM_SAMPLE_DATA;
- if (ev.mDataSamples.length >= ND) {
- System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND);
- } else {
- ev.mDataSamples = (float[])o.mDataSamples.clone();
- }
+ System.arraycopy(o.mEventTimeNanoSamples, 0, ev.mEventTimeNanoSamples, 0, numSamples);
+ System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0,
+ numPointers * numSamples * NUM_SAMPLE_DATA);
return ev;
}
@@ -576,36 +550,31 @@ public final class MotionEvent implements Parcelable {
* any historical point information.
*/
static public MotionEvent obtainNoHistory(MotionEvent o) {
- MotionEvent ev = obtain();
+ MotionEvent ev = obtain(o.mNumPointers, 1);
ev.mDeviceId = o.mDeviceId;
+ ev.mSource = o.mSource;
ev.mEdgeFlags = o.mEdgeFlags;
- ev.mDownTime = o.mDownTime;
- ev.mEventTimeNano = o.mEventTimeNano;
+ ev.mDownTimeNano = o.mDownTimeNano;
ev.mAction = o.mAction;
- ev.mNumPointers = o.mNumPointers;
- ev.mRawX = o.mRawX;
- ev.mRawY = o.mRawY;
+ o.mFlags = o.mFlags;
ev.mMetaState = o.mMetaState;
+ ev.mXOffset = o.mXOffset;
+ ev.mYOffset = o.mYOffset;
ev.mXPrecision = o.mXPrecision;
ev.mYPrecision = o.mYPrecision;
+ int numPointers = ev.mNumPointers = o.mNumPointers;
ev.mNumSamples = 1;
- ev.mTimeSamples[0] = o.mTimeSamples[0];
- final int NP = (ev.mNumPointers=o.mNumPointers);
- if (ev.mPointerIdentifiers.length >= NP) {
- System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP);
- } else {
- ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone();
- }
+ ev.mLastDataSampleIndex = 0;
+ ev.mLastEventTimeNanoSampleIndex = 0;
- final int ND = NP * NUM_SAMPLE_DATA;
- if (ev.mDataSamples.length >= ND) {
- System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND);
- } else {
- ev.mDataSamples = (float[])o.mDataSamples.clone();
- }
+ System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, numPointers);
+ ev.mEventTimeNanoSamples[0] = o.mEventTimeNanoSamples[o.mLastEventTimeNanoSampleIndex];
+
+ System.arraycopy(o.mDataSamples, o.mLastDataSampleIndex, ev.mDataSamples, 0,
+ numPointers * NUM_SAMPLE_DATA);
return ev;
}
@@ -613,18 +582,21 @@ public final class MotionEvent implements Parcelable {
* Recycle the MotionEvent, to be re-used by a later caller. After calling
* this function you must not ever touch the event again.
*/
- public void recycle() {
+ public final void recycle() {
// Ensure recycle is only called once!
if (TRACK_RECYCLED_LOCATION) {
if (mRecycledLocation != null) {
throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
}
mRecycledLocation = new RuntimeException("Last recycled here");
- } else if (mRecycled) {
- throw new RuntimeException(toString() + " recycled twice!");
+ //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation);
+ } else {
+ if (mRecycled) {
+ throw new RuntimeException(toString() + " recycled twice!");
+ }
+ mRecycled = true;
}
- //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation);
synchronized (gRecyclerLock) {
if (gRecyclerUsed < MAX_RECYCLED) {
gRecyclerUsed++;
@@ -634,6 +606,31 @@ public final class MotionEvent implements Parcelable {
}
}
}
+
+ /**
+ * Scales down the coordination of this event by the given scale.
+ *
+ * @hide
+ */
+ public final void scale(float scale) {
+ mXOffset *= scale;
+ mYOffset *= scale;
+ mXPrecision *= scale;
+ mYPrecision *= scale;
+
+ float[] history = mDataSamples;
+ final int length = mNumPointers * mNumSamples * NUM_SAMPLE_DATA;
+ for (int i = 0; i < length; i += NUM_SAMPLE_DATA) {
+ history[i + SAMPLE_X] *= scale;
+ history[i + SAMPLE_Y] *= scale;
+ // no need to scale pressure
+ history[i + SAMPLE_SIZE] *= scale; // TODO: square this?
+ history[i + SAMPLE_TOUCH_MAJOR] *= scale;
+ history[i + SAMPLE_TOUCH_MINOR] *= scale;
+ history[i + SAMPLE_TOOL_MAJOR] *= scale;
+ history[i + SAMPLE_TOOL_MINOR] *= scale;
+ }
+ }
/**
* Return the kind of action being performed -- one of either
@@ -671,18 +668,27 @@ public final class MotionEvent implements Parcelable {
}
/**
+ * Gets the motion event flags.
+ *
+ * @see #FLAG_WINDOW_IS_OBSCURED
+ */
+ public final int getFlags() {
+ return mFlags;
+ }
+
+ /**
* Returns the time (in ms) when the user originally pressed down to start
* a stream of position events.
*/
public final long getDownTime() {
- return mDownTime;
+ return mDownTimeNano / MS_PER_NS;
}
/**
* Returns the time (in ms) when this specific event was generated.
*/
public final long getEventTime() {
- return mTimeSamples[0];
+ return mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] / MS_PER_NS;
}
/**
@@ -692,7 +698,7 @@ public final class MotionEvent implements Parcelable {
* @hide
*/
public final long getEventTimeNano() {
- return mEventTimeNano;
+ return mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex];
}
/**
@@ -700,7 +706,7 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getX() {
- return mDataSamples[SAMPLE_X];
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_X] + mXOffset;
}
/**
@@ -708,7 +714,7 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getY() {
- return mDataSamples[SAMPLE_Y];
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_Y] + mYOffset;
}
/**
@@ -716,7 +722,7 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getPressure() {
- return mDataSamples[SAMPLE_PRESSURE];
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_PRESSURE];
}
/**
@@ -724,7 +730,47 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getSize() {
- return mDataSamples[SAMPLE_SIZE];
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_SIZE];
+ }
+
+ /**
+ * {@link #getTouchMajor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getTouchMajor() {
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_TOUCH_MAJOR];
+ }
+
+ /**
+ * {@link #getTouchMinor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getTouchMinor() {
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_TOUCH_MINOR];
+ }
+
+ /**
+ * {@link #getToolMajor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getToolMajor() {
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_TOOL_MAJOR];
+ }
+
+ /**
+ * {@link #getToolMinor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getToolMinor() {
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_TOOL_MINOR];
+ }
+
+ /**
+ * {@link #getOrientation(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getOrientation() {
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_ORIENTATION];
}
/**
@@ -752,7 +798,7 @@ public final class MotionEvent implements Parcelable {
*
* @param pointerId The identifier of the pointer to be found.
* @return Returns either the index of the pointer (for use with
- * {@link #getX(int) et al.), or -1 if there is no data available for
+ * {@link #getX(int)} et al.), or -1 if there is no data available for
* that pointer identifier.
*/
public final int findPointerIndex(int pointerId) {
@@ -776,7 +822,8 @@ public final class MotionEvent implements Parcelable {
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*/
public final float getX(int pointerIndex) {
- return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_X];
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset;
}
/**
@@ -789,7 +836,8 @@ public final class MotionEvent implements Parcelable {
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*/
public final float getY(int pointerIndex) {
- return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_Y];
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset;
}
/**
@@ -804,7 +852,8 @@ public final class MotionEvent implements Parcelable {
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*/
public final float getPressure(int pointerIndex) {
- return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_PRESSURE];
}
/**
@@ -820,7 +869,95 @@ public final class MotionEvent implements Parcelable {
* (the first pointer that is down) to {@link #getPointerCount()}-1.
*/
public final float getSize(int pointerIndex) {
- return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_SIZE];
+ }
+
+ /**
+ * Returns the length of the major axis of an ellipse that describes the touch
+ * area at the point of contact for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ */
+ public final float getTouchMajor(int pointerIndex) {
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MAJOR];
+ }
+
+ /**
+ * Returns the length of the minor axis of an ellipse that describes the touch
+ * area at the point of contact for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ */
+ public final float getTouchMinor(int pointerIndex) {
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MINOR];
+ }
+
+ /**
+ * Returns the length of the major axis of an ellipse that describes the size of
+ * the approaching tool for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ */
+ public final float getToolMajor(int pointerIndex) {
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOOL_MAJOR];
+ }
+
+ /**
+ * Returns the length of the minor axis of an ellipse that describes the size of
+ * the approaching tool for the given pointer
+ * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ */
+ public final float getToolMinor(int pointerIndex) {
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOOL_MINOR];
+ }
+
+ /**
+ * Returns the orientation of the touch area and tool area in radians clockwise from vertical
+ * for the given pointer <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+ * identifier for this index).
+ * An angle of 0 degrees indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right).
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ */
+ public final float getOrientation(int pointerIndex) {
+ return mDataSamples[mLastDataSampleIndex
+ + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_ORIENTATION];
+ }
+
+ /**
+ * Populates a {@link PointerCoords} object with pointer coordinate data for
+ * the specified pointer index.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param outPointerCoords The pointer coordinate object to populate.
+ */
+ public final void getPointerCoords(int pointerIndex, PointerCoords outPointerCoords) {
+ final int sampleIndex = mLastDataSampleIndex + pointerIndex * NUM_SAMPLE_DATA;
+ getPointerCoordsAtSampleIndex(sampleIndex, outPointerCoords);
}
/**
@@ -844,9 +981,9 @@ public final class MotionEvent implements Parcelable {
* and views.
*/
public final float getRawX() {
- return mRawX;
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_X];
}
-
+
/**
* Returns the original raw Y coordinate of this event. For touch
* events on the screen, this is the original location of the event
@@ -854,7 +991,7 @@ public final class MotionEvent implements Parcelable {
* and views.
*/
public final float getRawY() {
- return mRawY;
+ return mDataSamples[mLastDataSampleIndex + SAMPLE_Y];
}
/**
@@ -886,7 +1023,7 @@ public final class MotionEvent implements Parcelable {
* @return Returns the number of historical points in the event.
*/
public final int getHistorySize() {
- return mNumSamples - 1;
+ return mLastEventTimeNanoSampleIndex;
}
/**
@@ -900,7 +1037,7 @@ public final class MotionEvent implements Parcelable {
* @see #getEventTime
*/
public final long getHistoricalEventTime(int pos) {
- return mTimeSamples[pos + 1];
+ return mEventTimeNanoSamples[pos] / MS_PER_NS;
}
/**
@@ -908,7 +1045,7 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getHistoricalX(int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_X];
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset;
}
/**
@@ -916,7 +1053,7 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getHistoricalY(int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_Y];
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset;
}
/**
@@ -924,7 +1061,7 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getHistoricalPressure(int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_PRESSURE];
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_PRESSURE];
}
/**
@@ -932,10 +1069,50 @@ public final class MotionEvent implements Parcelable {
* arbitrary pointer identifier).
*/
public final float getHistoricalSize(int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_SIZE];
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_SIZE];
+ }
+
+ /**
+ * {@link #getHistoricalTouchMajor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getHistoricalTouchMajor(int pos) {
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MAJOR];
+ }
+
+ /**
+ * {@link #getHistoricalTouchMinor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getHistoricalTouchMinor(int pos) {
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MINOR];
+ }
+
+ /**
+ * {@link #getHistoricalToolMajor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getHistoricalToolMajor(int pos) {
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOOL_MAJOR];
}
/**
+ * {@link #getHistoricalToolMinor(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getHistoricalToolMinor(int pos) {
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOOL_MINOR];
+ }
+
+ /**
+ * {@link #getHistoricalOrientation(int)} for the first pointer index (may be an
+ * arbitrary pointer identifier).
+ */
+ public final float getHistoricalOrientation(int pos) {
+ return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_ORIENTATION];
+ }
+
+ /**
* Returns a historical X coordinate, as per {@link #getX(int)}, that
* occurred between this event and the previous event for the given pointer.
* Only applies to ACTION_MOVE events.
@@ -949,8 +1126,8 @@ public final class MotionEvent implements Parcelable {
* @see #getX
*/
public final float getHistoricalX(int pointerIndex, int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
- + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_X];
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset;
}
/**
@@ -967,8 +1144,8 @@ public final class MotionEvent implements Parcelable {
* @see #getY
*/
public final float getHistoricalY(int pointerIndex, int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
- + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_Y];
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset;
}
/**
@@ -985,8 +1162,8 @@ public final class MotionEvent implements Parcelable {
* @see #getPressure
*/
public final float getHistoricalPressure(int pointerIndex, int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
- + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_PRESSURE];
}
/**
@@ -1003,21 +1180,123 @@ public final class MotionEvent implements Parcelable {
* @see #getSize
*/
public final float getHistoricalSize(int pointerIndex, int pos) {
- return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
- + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_SIZE];
+ }
+
+ /**
+ * Returns a historical touch major axis coordinate, as per {@link #getTouchMajor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getTouchMajor
+ */
+ public final float getHistoricalTouchMajor(int pointerIndex, int pos) {
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MAJOR];
}
/**
- * Return the id for the device that this event came from. An id of
- * zero indicates that the event didn't come from a physical device; other
- * numbers are arbitrary and you shouldn't depend on the values.
+ * Returns a historical touch minor axis coordinate, as per {@link #getTouchMinor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getTouchMinor
+ */
+ public final float getHistoricalTouchMinor(int pointerIndex, int pos) {
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MINOR];
+ }
+
+ /**
+ * Returns a historical tool major axis coordinate, as per {@link #getToolMajor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getToolMajor
*/
- public final int getDeviceId() {
- return mDeviceId;
+ public final float getHistoricalToolMajor(int pointerIndex, int pos) {
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_TOOL_MAJOR];
}
/**
- * Returns a bitfield indicating which edges, if any, where touched by this
+ * Returns a historical tool minor axis coordinate, as per {@link #getToolMinor(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getToolMinor
+ */
+ public final float getHistoricalToolMinor(int pointerIndex, int pos) {
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_TOOL_MINOR];
+ }
+
+ /**
+ * Returns a historical orientation coordinate, as per {@link #getOrientation(int)}, that
+ * occurred between this event and the previous event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ *
+ * @see #getHistorySize
+ * @see #getOrientation
+ */
+ public final float getHistoricalOrientation(int pointerIndex, int pos) {
+ return mDataSamples[(pos * mNumPointers + pointerIndex)
+ * NUM_SAMPLE_DATA + SAMPLE_ORIENTATION];
+ }
+
+ /**
+ * Populates a {@link PointerCoords} object with historical pointer coordinate data,
+ * as per {@link #getPointerCoords}, that occurred between this event and the previous
+ * event for the given pointer.
+ * Only applies to ACTION_MOVE events.
+ *
+ * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0
+ * (the first pointer that is down) to {@link #getPointerCount()}-1.
+ * @param pos Which historical value to return; must be less than
+ * {@link #getHistorySize}
+ * @param outPointerCoords The pointer coordinate object to populate.
+ *
+ * @see #getHistorySize
+ * @see #getPointerCoords
+ */
+ public final void getHistoricalPointerCoords(int pointerIndex, int pos,
+ PointerCoords outPointerCoords) {
+ final int sampleIndex = (pos * mNumPointers + pointerIndex) * NUM_SAMPLE_DATA;
+ getPointerCoordsAtSampleIndex(sampleIndex, outPointerCoords);
+ }
+
+ /**
+ * Returns a bitfield indicating which edges, if any, were touched by this
* MotionEvent. For touch events, clients can use this to determine if the
* user's finger was touching the edge of the display.
*
@@ -1032,7 +1311,7 @@ public final class MotionEvent implements Parcelable {
/**
- * Sets the bitfield indicating which edges, if any, where touched by this
+ * Sets the bitfield indicating which edges, if any, were touched by this
* MotionEvent.
*
* @see #getEdgeFlags()
@@ -1054,12 +1333,8 @@ public final class MotionEvent implements Parcelable {
* @param deltaY Amount to add to the current Y coordinate of the event.
*/
public final void offsetLocation(float deltaX, float deltaY) {
- final int N = mNumPointers*mNumSamples*4;
- final float[] pos = mDataSamples;
- for (int i=0; i<N; i+=NUM_SAMPLE_DATA) {
- pos[i+SAMPLE_X] += deltaX;
- pos[i+SAMPLE_Y] += deltaY;
- }
+ mXOffset += deltaX;
+ mYOffset += deltaY;
}
/**
@@ -1070,20 +1345,91 @@ public final class MotionEvent implements Parcelable {
* @param y New absolute Y location.
*/
public final void setLocation(float x, float y) {
- float deltaX = x-mDataSamples[SAMPLE_X];
- float deltaY = y-mDataSamples[SAMPLE_Y];
- if (deltaX != 0 || deltaY != 0) {
- offsetLocation(deltaX, deltaY);
+ final float[] dataSamples = mDataSamples;
+ final int lastDataSampleIndex = mLastDataSampleIndex;
+ mXOffset = x - dataSamples[lastDataSampleIndex + SAMPLE_X];
+ mYOffset = y - dataSamples[lastDataSampleIndex + SAMPLE_Y];
+ }
+
+ private final void getPointerCoordsAtSampleIndex(int sampleIndex,
+ PointerCoords outPointerCoords) {
+ final float[] dataSamples = mDataSamples;
+ outPointerCoords.x = dataSamples[sampleIndex + SAMPLE_X] + mXOffset;
+ outPointerCoords.y = dataSamples[sampleIndex + SAMPLE_Y] + mYOffset;
+ outPointerCoords.pressure = dataSamples[sampleIndex + SAMPLE_PRESSURE];
+ outPointerCoords.size = dataSamples[sampleIndex + SAMPLE_SIZE];
+ outPointerCoords.touchMajor = dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR];
+ outPointerCoords.touchMinor = dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR];
+ outPointerCoords.toolMajor = dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR];
+ outPointerCoords.toolMinor = dataSamples[sampleIndex + SAMPLE_TOOL_MINOR];
+ outPointerCoords.orientation = dataSamples[sampleIndex + SAMPLE_ORIENTATION];
+ }
+
+ private final void setPointerCoordsAtSampleIndex(int sampleIndex,
+ PointerCoords[] pointerCoords) {
+ final int numPointers = mNumPointers;
+ for (int i = 0; i < numPointers; i++) {
+ setPointerCoordsAtSampleIndex(sampleIndex, pointerCoords[i]);
+ sampleIndex += NUM_SAMPLE_DATA;
+ }
+ }
+
+ private final void setPointerCoordsAtSampleIndex(int sampleIndex,
+ PointerCoords pointerCoords) {
+ final float[] dataSamples = mDataSamples;
+ dataSamples[sampleIndex + SAMPLE_X] = pointerCoords.x - mXOffset;
+ dataSamples[sampleIndex + SAMPLE_Y] = pointerCoords.y - mYOffset;
+ dataSamples[sampleIndex + SAMPLE_PRESSURE] = pointerCoords.pressure;
+ dataSamples[sampleIndex + SAMPLE_SIZE] = pointerCoords.size;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pointerCoords.touchMajor;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pointerCoords.touchMinor;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = pointerCoords.toolMajor;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = pointerCoords.toolMinor;
+ dataSamples[sampleIndex + SAMPLE_ORIENTATION] = pointerCoords.orientation;
+ }
+
+ private final void setPointerCoordsAtSampleIndex(int sampleIndex,
+ float x, float y, float pressure, float size) {
+ final float[] dataSamples = mDataSamples;
+ dataSamples[sampleIndex + SAMPLE_X] = x - mXOffset;
+ dataSamples[sampleIndex + SAMPLE_Y] = y - mYOffset;
+ dataSamples[sampleIndex + SAMPLE_PRESSURE] = pressure;
+ dataSamples[sampleIndex + SAMPLE_SIZE] = size;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pressure;
+ dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pressure;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = size;
+ dataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = size;
+ dataSamples[sampleIndex + SAMPLE_ORIENTATION] = 0;
+ }
+
+ private final void incrementNumSamplesAndReserveStorage(int dataSampleStride) {
+ if (mNumSamples == mEventTimeNanoSamples.length) {
+ long[] newEventTimeNanoSamples = new long[mNumSamples + BASE_AVAIL_SAMPLES];
+ System.arraycopy(mEventTimeNanoSamples, 0, newEventTimeNanoSamples, 0, mNumSamples);
+ mEventTimeNanoSamples = newEventTimeNanoSamples;
+ }
+
+ int nextDataSampleIndex = mLastDataSampleIndex + dataSampleStride;
+ if (nextDataSampleIndex + dataSampleStride > mDataSamples.length) {
+ float[] newDataSamples = new float[nextDataSampleIndex
+ + BASE_AVAIL_SAMPLES * dataSampleStride];
+ System.arraycopy(mDataSamples, 0, newDataSamples, 0, nextDataSampleIndex);
+ mDataSamples = newDataSamples;
}
+
+ mLastEventTimeNanoSampleIndex = mNumSamples;
+ mLastDataSampleIndex = nextDataSampleIndex;
+ mNumSamples += 1;
}
/**
* Add a new movement to the batch of movements in this event. The event's
- * current location, position and size is updated to the new values. In
- * the future, the current values in the event will be added to a list of
- * historic values.
+ * current location, position and size is updated to the new values.
+ * The current values in the event are added to a list of historical values.
+ *
+ * Only applies to {@link #ACTION_MOVE} events.
*
- * @param eventTime The time stamp for this data.
+ * @param eventTime The time stamp (in ms) for this data.
* @param x The new X position.
* @param y The new Y position.
* @param pressure The new pressure.
@@ -1092,103 +1438,33 @@ public final class MotionEvent implements Parcelable {
*/
public final void addBatch(long eventTime, float x, float y,
float pressure, float size, int metaState) {
- float[] data = mDataSamples;
- long[] times = mTimeSamples;
+ incrementNumSamplesAndReserveStorage(NUM_SAMPLE_DATA);
- final int NP = mNumPointers;
- final int NS = mNumSamples;
- final int NI = NP*NS;
- final int ND = NI * NUM_SAMPLE_DATA;
- if (data.length <= ND) {
- final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA));
- float[] newData = new float[NEW_ND];
- System.arraycopy(data, 0, newData, 0, ND);
- mDataSamples = data = newData;
- }
- if (times.length <= NS) {
- final int NEW_NS = NS + BASE_AVAIL_SAMPLES;
- long[] newHistoryTimes = new long[NEW_NS];
- System.arraycopy(times, 0, newHistoryTimes, 0, NS);
- mTimeSamples = times = newHistoryTimes;
- }
-
- times[NS] = times[0];
- times[0] = eventTime;
+ mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] = eventTime * MS_PER_NS;
+ setPointerCoordsAtSampleIndex(mLastDataSampleIndex, x, y, pressure, size);
- final int pos = NS*NUM_SAMPLE_DATA;
- data[pos+SAMPLE_X] = data[SAMPLE_X];
- data[pos+SAMPLE_Y] = data[SAMPLE_Y];
- data[pos+SAMPLE_PRESSURE] = data[SAMPLE_PRESSURE];
- data[pos+SAMPLE_SIZE] = data[SAMPLE_SIZE];
- data[SAMPLE_X] = x;
- data[SAMPLE_Y] = y;
- data[SAMPLE_PRESSURE] = pressure;
- data[SAMPLE_SIZE] = size;
- mNumSamples = NS+1;
-
- mRawX = x;
- mRawY = y;
mMetaState |= metaState;
}
/**
- * Add a new movement to the batch of movements in this event. The
- * input data must contain (NUM_SAMPLE_DATA * {@link #getPointerCount()})
- * samples of data.
+ * Add a new movement to the batch of movements in this event. The event's
+ * current location, position and size is updated to the new values.
+ * The current values in the event are added to a list of historical values.
+ *
+ * Only applies to {@link #ACTION_MOVE} events.
*
- * @param eventTime The time stamp for this data.
- * @param inData The actual data.
+ * @param eventTime The time stamp (in ms) for this data.
+ * @param pointerCoords The new pointer coordinates.
* @param metaState Meta key state.
- *
- * @hide
*/
- public final void addBatch(long eventTime, float[] inData, int metaState) {
- float[] data = mDataSamples;
- long[] times = mTimeSamples;
-
- final int NP = mNumPointers;
- final int NS = mNumSamples;
- final int NI = NP*NS;
- final int ND = NI * NUM_SAMPLE_DATA;
- if (data.length < (ND+(NP*NUM_SAMPLE_DATA))) {
- final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA));
- float[] newData = new float[NEW_ND];
- System.arraycopy(data, 0, newData, 0, ND);
- mDataSamples = data = newData;
- }
- if (times.length < (NS+1)) {
- final int NEW_NS = NS + BASE_AVAIL_SAMPLES;
- long[] newHistoryTimes = new long[NEW_NS];
- System.arraycopy(times, 0, newHistoryTimes, 0, NS);
- mTimeSamples = times = newHistoryTimes;
- }
-
- times[NS] = times[0];
- times[0] = eventTime;
+ public final void addBatch(long eventTime, PointerCoords[] pointerCoords, int metaState) {
+ final int dataSampleStride = mNumPointers * NUM_SAMPLE_DATA;
+ incrementNumSamplesAndReserveStorage(dataSampleStride);
- System.arraycopy(data, 0, data, ND, mNumPointers*NUM_SAMPLE_DATA);
- System.arraycopy(inData, 0, data, 0, mNumPointers*NUM_SAMPLE_DATA);
+ mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] = eventTime * MS_PER_NS;
+ setPointerCoordsAtSampleIndex(mLastDataSampleIndex, pointerCoords);
- mNumSamples = NS+1;
-
- mRawX = inData[SAMPLE_X];
- mRawY = inData[SAMPLE_Y];
mMetaState |= metaState;
-
- if (DEBUG_POINTERS) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Add:");
- for (int i=0; i<mNumPointers; i++) {
- sb.append(" #");
- sb.append(mPointerIdentifiers[i]);
- sb.append("(");
- sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
- sb.append(",");
- sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
- sb.append(")");
- }
- Log.v("MotionEvent", sb.toString());
- }
}
@Override
@@ -1201,9 +1477,8 @@ public final class MotionEvent implements Parcelable {
public static final Parcelable.Creator<MotionEvent> CREATOR
= new Parcelable.Creator<MotionEvent>() {
public MotionEvent createFromParcel(Parcel in) {
- MotionEvent ev = obtain();
- ev.readFromParcel(in);
- return ev;
+ in.readInt(); // skip token, we already know this is a MotionEvent
+ return MotionEvent.createFromParcelBody(in);
}
public MotionEvent[] newArray(int size) {
@@ -1211,84 +1486,187 @@ public final class MotionEvent implements Parcelable {
}
};
- public int describeContents() {
- return 0;
- }
+ /** @hide */
+ public static MotionEvent createFromParcelBody(Parcel in) {
+ final int NP = in.readInt();
+ final int NS = in.readInt();
+ final int NI = NP * NS * NUM_SAMPLE_DATA;
+
+ MotionEvent ev = obtain(NP, NS);
+ ev.mNumPointers = NP;
+ ev.mNumSamples = NS;
+
+ ev.readBaseFromParcel(in);
+
+ ev.mDownTimeNano = in.readLong();
+ ev.mAction = in.readInt();
+ ev.mXOffset = in.readFloat();
+ ev.mYOffset = in.readFloat();
+ ev.mXPrecision = in.readFloat();
+ ev.mYPrecision = in.readFloat();
+ ev.mEdgeFlags = in.readInt();
+ ev.mMetaState = in.readInt();
+ ev.mFlags = in.readInt();
+
+ final int[] pointerIdentifiers = ev.mPointerIdentifiers;
+ for (int i = 0; i < NP; i++) {
+ pointerIdentifiers[i] = in.readInt();
+ }
+
+ final long[] eventTimeNanoSamples = ev.mEventTimeNanoSamples;
+ for (int i = 0; i < NS; i++) {
+ eventTimeNanoSamples[i] = in.readLong();
+ }
+ final float[] dataSamples = ev.mDataSamples;
+ for (int i = 0; i < NI; i++) {
+ dataSamples[i] = in.readFloat();
+ }
+
+ ev.mLastEventTimeNanoSampleIndex = NS - 1;
+ ev.mLastDataSampleIndex = (NS - 1) * NP * NUM_SAMPLE_DATA;
+ return ev;
+ }
+
public void writeToParcel(Parcel out, int flags) {
- out.writeLong(mDownTime);
- out.writeLong(mEventTimeNano);
- out.writeInt(mAction);
- out.writeInt(mMetaState);
- out.writeFloat(mRawX);
- out.writeFloat(mRawY);
+ out.writeInt(PARCEL_TOKEN_MOTION_EVENT);
+
final int NP = mNumPointers;
- out.writeInt(NP);
final int NS = mNumSamples;
+ final int NI = NP * NS * NUM_SAMPLE_DATA;
+
+ out.writeInt(NP);
out.writeInt(NS);
- final int NI = NP*NS;
- if (NI > 0) {
- int i;
- int[] state = mPointerIdentifiers;
- for (i=0; i<NP; i++) {
- out.writeInt(state[i]);
- }
- final int ND = NI*NUM_SAMPLE_DATA;
- float[] history = mDataSamples;
- for (i=0; i<ND; i++) {
- out.writeFloat(history[i]);
- }
- long[] times = mTimeSamples;
- for (i=0; i<NS; i++) {
- out.writeLong(times[i]);
- }
- }
+
+ writeBaseToParcel(out);
+
+ out.writeLong(mDownTimeNano);
+ out.writeInt(mAction);
+ out.writeFloat(mXOffset);
+ out.writeFloat(mYOffset);
out.writeFloat(mXPrecision);
out.writeFloat(mYPrecision);
- out.writeInt(mDeviceId);
out.writeInt(mEdgeFlags);
- }
+ out.writeInt(mMetaState);
+ out.writeInt(mFlags);
+
+ final int[] pointerIdentifiers = mPointerIdentifiers;
+ for (int i = 0; i < NP; i++) {
+ out.writeInt(pointerIdentifiers[i]);
+ }
+
+ final long[] eventTimeNanoSamples = mEventTimeNanoSamples;
+ for (int i = 0; i < NS; i++) {
+ out.writeLong(eventTimeNanoSamples[i]);
+ }
- private void readFromParcel(Parcel in) {
- mDownTime = in.readLong();
- mEventTimeNano = in.readLong();
- mAction = in.readInt();
- mMetaState = in.readInt();
- mRawX = in.readFloat();
- mRawY = in.readFloat();
- final int NP = in.readInt();
- mNumPointers = NP;
- final int NS = in.readInt();
- mNumSamples = NS;
- final int NI = NP*NS;
- if (NI > 0) {
- int[] ids = mPointerIdentifiers;
- if (ids.length < NP) {
- mPointerIdentifiers = ids = new int[NP];
- }
- for (int i=0; i<NP; i++) {
- ids[i] = in.readInt();
- }
- float[] history = mDataSamples;
- final int ND = NI*NUM_SAMPLE_DATA;
- if (history.length < ND) {
- mDataSamples = history = new float[ND];
- }
- for (int i=0; i<ND; i++) {
- history[i] = in.readFloat();
- }
- long[] times = mTimeSamples;
- if (times == null || times.length < NS) {
- mTimeSamples = times = new long[NS];
- }
- for (int i=0; i<NS; i++) {
- times[i] = in.readLong();
- }
+ final float[] dataSamples = mDataSamples;
+ for (int i = 0; i < NI; i++) {
+ out.writeFloat(dataSamples[i]);
}
- mXPrecision = in.readFloat();
- mYPrecision = in.readFloat();
- mDeviceId = in.readInt();
- mEdgeFlags = in.readInt();
}
-
+
+ /**
+ * Transfer object for pointer coordinates.
+ *
+ * Objects of this type can be used to manufacture new {@link MotionEvent} objects
+ * and to query pointer coordinate information in bulk.
+ *
+ * Refer to {@link InputDevice} for information about how different kinds of
+ * input devices and sources represent pointer coordinates.
+ */
+ public static final class PointerCoords {
+ /**
+ * The X coordinate of the pointer movement.
+ * The interpretation varies by input source and may represent the position of
+ * the center of the contact area, a relative displacement in device-specific units
+ * or something else.
+ */
+ public float x;
+
+ /**
+ * The Y coordinate of the pointer movement.
+ * The interpretation varies by input source and may represent the position of
+ * the center of the contact area, a relative displacement in device-specific units
+ * or something else.
+ */
+ public float y;
+
+ /**
+ * A scaled value that describes the pressure applied to the pointer.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * however values higher than 1 may be generated depending on the calibration of
+ * the input device.
+ */
+ public float pressure;
+
+ /**
+ * A scaled value of the approximate size of the pointer touch area.
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1. The value of size can be used to
+ * determine fat touch events.
+ */
+ public float size;
+
+ /**
+ * The length of the major axis of an ellipse that describes the touch area at
+ * the point of contact.
+ */
+ public float touchMajor;
+
+ /**
+ * The length of the minor axis of an ellipse that describes the touch area at
+ * the point of contact.
+ */
+ public float touchMinor;
+
+ /**
+ * The length of the major axis of an ellipse that describes the size of
+ * the approaching tool.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ */
+ public float toolMajor;
+
+ /**
+ * The length of the minor axis of an ellipse that describes the size of
+ * the approaching tool.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact.
+ */
+ public float toolMinor;
+
+ /**
+ * The orientation of the touch area and tool area in radians clockwise from vertical.
+ * An angle of 0 degrees indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right).
+ */
+ public float orientation;
+
+ /*
+ private static final float PI_4 = (float) (Math.PI / 4);
+
+ public float getTouchWidth() {
+ return Math.abs(orientation) > PI_4 ? touchMajor : touchMinor;
+ }
+
+ public float getTouchHeight() {
+ return Math.abs(orientation) > PI_4 ? touchMinor : touchMajor;
+ }
+
+ public float getToolWidth() {
+ return Math.abs(orientation) > PI_4 ? toolMajor : toolMinor;
+ }
+
+ public float getToolHeight() {
+ return Math.abs(orientation) > PI_4 ? toolMinor : toolMajor;
+ }
+ */
+ }
}
diff --git a/core/java/android/view/RawInputEvent.java b/core/java/android/view/RawInputEvent.java
deleted file mode 100644
index 3bbfea8a975f..000000000000
--- a/core/java/android/view/RawInputEvent.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of 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.view;
-
-/**
- * @hide
- * This really belongs in services.jar; WindowManagerPolicy should go there too.
- */
-public class RawInputEvent {
- // Event class as defined by EventHub.
- public static final int CLASS_KEYBOARD = 0x00000001;
- public static final int CLASS_ALPHAKEY = 0x00000002;
- public static final int CLASS_TOUCHSCREEN = 0x00000004;
- public static final int CLASS_TRACKBALL = 0x00000008;
- public static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
- public static final int CLASS_DPAD = 0x00000020;
-
- // More special classes for QueuedEvent below.
- public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000;
-
- // Event types.
-
- public static final int EV_SYN = 0x00;
- public static final int EV_KEY = 0x01;
- public static final int EV_REL = 0x02;
- public static final int EV_ABS = 0x03;
- public static final int EV_MSC = 0x04;
- public static final int EV_SW = 0x05;
- public static final int EV_LED = 0x11;
- public static final int EV_SND = 0x12;
- public static final int EV_REP = 0x14;
- public static final int EV_FF = 0x15;
- public static final int EV_PWR = 0x16;
- public static final int EV_FF_STATUS = 0x17;
-
- // Platform-specific event types.
-
- public static final int EV_DEVICE_ADDED = 0x10000000;
- public static final int EV_DEVICE_REMOVED = 0x20000000;
-
- // Special key (EV_KEY) scan codes for pointer buttons.
-
- public static final int BTN_FIRST = 0x100;
-
- public static final int BTN_MISC = 0x100;
- public static final int BTN_0 = 0x100;
- public static final int BTN_1 = 0x101;
- public static final int BTN_2 = 0x102;
- public static final int BTN_3 = 0x103;
- public static final int BTN_4 = 0x104;
- public static final int BTN_5 = 0x105;
- public static final int BTN_6 = 0x106;
- public static final int BTN_7 = 0x107;
- public static final int BTN_8 = 0x108;
- public static final int BTN_9 = 0x109;
-
- public static final int BTN_MOUSE = 0x110;
- public static final int BTN_LEFT = 0x110;
- public static final int BTN_RIGHT = 0x111;
- public static final int BTN_MIDDLE = 0x112;
- public static final int BTN_SIDE = 0x113;
- public static final int BTN_EXTRA = 0x114;
- public static final int BTN_FORWARD = 0x115;
- public static final int BTN_BACK = 0x116;
- public static final int BTN_TASK = 0x117;
-
- public static final int BTN_JOYSTICK = 0x120;
- public static final int BTN_TRIGGER = 0x120;
- public static final int BTN_THUMB = 0x121;
- public static final int BTN_THUMB2 = 0x122;
- public static final int BTN_TOP = 0x123;
- public static final int BTN_TOP2 = 0x124;
- public static final int BTN_PINKIE = 0x125;
- public static final int BTN_BASE = 0x126;
- public static final int BTN_BASE2 = 0x127;
- public static final int BTN_BASE3 = 0x128;
- public static final int BTN_BASE4 = 0x129;
- public static final int BTN_BASE5 = 0x12a;
- public static final int BTN_BASE6 = 0x12b;
- public static final int BTN_DEAD = 0x12f;
-
- public static final int BTN_GAMEPAD = 0x130;
- public static final int BTN_A = 0x130;
- public static final int BTN_B = 0x131;
- public static final int BTN_C = 0x132;
- public static final int BTN_X = 0x133;
- public static final int BTN_Y = 0x134;
- public static final int BTN_Z = 0x135;
- public static final int BTN_TL = 0x136;
- public static final int BTN_TR = 0x137;
- public static final int BTN_TL2 = 0x138;
- public static final int BTN_TR2 = 0x139;
- public static final int BTN_SELECT = 0x13a;
- public static final int BTN_START = 0x13b;
- public static final int BTN_MODE = 0x13c;
- public static final int BTN_THUMBL = 0x13d;
- public static final int BTN_THUMBR = 0x13e;
-
- public static final int BTN_DIGI = 0x140;
- public static final int BTN_TOOL_PEN = 0x140;
- public static final int BTN_TOOL_RUBBER = 0x141;
- public static final int BTN_TOOL_BRUSH = 0x142;
- public static final int BTN_TOOL_PENCIL = 0x143;
- public static final int BTN_TOOL_AIRBRUSH = 0x144;
- public static final int BTN_TOOL_FINGER = 0x145;
- public static final int BTN_TOOL_MOUSE = 0x146;
- public static final int BTN_TOOL_LENS = 0x147;
- public static final int BTN_TOUCH = 0x14a;
- public static final int BTN_STYLUS = 0x14b;
- public static final int BTN_STYLUS2 = 0x14c;
- public static final int BTN_TOOL_DOUBLETAP = 0x14d;
- public static final int BTN_TOOL_TRIPLETAP = 0x14e;
-
- public static final int BTN_WHEEL = 0x150;
- public static final int BTN_GEAR_DOWN = 0x150;
- public static final int BTN_GEAR_UP = 0x151;
-
- public static final int BTN_LAST = 0x15f;
-
- // Relative axes (EV_REL) scan codes.
-
- public static final int REL_X = 0x00;
- public static final int REL_Y = 0x01;
- public static final int REL_Z = 0x02;
- public static final int REL_RX = 0x03;
- public static final int REL_RY = 0x04;
- public static final int REL_RZ = 0x05;
- public static final int REL_HWHEEL = 0x06;
- public static final int REL_DIAL = 0x07;
- public static final int REL_WHEEL = 0x08;
- public static final int REL_MISC = 0x09;
- public static final int REL_MAX = 0x0f;
-
- // Absolute axes (EV_ABS) scan codes.
-
- public static final int ABS_X = 0x00;
- public static final int ABS_Y = 0x01;
- public static final int ABS_Z = 0x02;
- public static final int ABS_RX = 0x03;
- public static final int ABS_RY = 0x04;
- public static final int ABS_RZ = 0x05;
- public static final int ABS_THROTTLE = 0x06;
- public static final int ABS_RUDDER = 0x07;
- public static final int ABS_WHEEL = 0x08;
- public static final int ABS_GAS = 0x09;
- public static final int ABS_BRAKE = 0x0a;
- public static final int ABS_HAT0X = 0x10;
- public static final int ABS_HAT0Y = 0x11;
- public static final int ABS_HAT1X = 0x12;
- public static final int ABS_HAT1Y = 0x13;
- public static final int ABS_HAT2X = 0x14;
- public static final int ABS_HAT2Y = 0x15;
- public static final int ABS_HAT3X = 0x16;
- public static final int ABS_HAT3Y = 0x17;
- public static final int ABS_PRESSURE = 0x18;
- public static final int ABS_DISTANCE = 0x19;
- public static final int ABS_TILT_X = 0x1a;
- public static final int ABS_TILT_Y = 0x1b;
- public static final int ABS_TOOL_WIDTH = 0x1c;
- public static final int ABS_VOLUME = 0x20;
- public static final int ABS_MISC = 0x28;
- public static final int ABS_MT_TOUCH_MAJOR = 0x30;
- public static final int ABS_MT_TOUCH_MINOR = 0x31;
- public static final int ABS_MT_WIDTH_MAJOR = 0x32;
- public static final int ABS_MT_WIDTH_MINOR = 0x33;
- public static final int ABS_MT_ORIENTATION = 0x34;
- public static final int ABS_MT_POSITION_X = 0x35;
- public static final int ABS_MT_POSITION_Y = 0x36;
- public static final int ABS_MT_TOOL_TYPE = 0x37;
- public static final int ABS_MT_BLOB_ID = 0x38;
- public static final int ABS_MAX = 0x3f;
-
- // Switch events
- public static final int SW_LID = 0x00;
-
- public static final int SYN_REPORT = 0;
- public static final int SYN_CONFIG = 1;
- public static final int SYN_MT_REPORT = 2;
-
- public int deviceId;
- public int type;
- public int scancode;
- public int keycode;
- public int flags;
- public int value;
- public long when;
-}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index ff34f4add39d..09995987e65d 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -312,7 +312,7 @@ public class ScaleGestureDetector {
* MotionEvent has no getRawX(int) method; simulate it pending future API approval.
*/
private static float getRawX(MotionEvent event, int pointerIndex) {
- float offset = event.getX() - event.getRawX();
+ float offset = event.getRawX() - event.getX();
return event.getX(pointerIndex) + offset;
}
@@ -320,7 +320,7 @@ public class ScaleGestureDetector {
* MotionEvent has no getRawY(int) method; simulate it pending future API approval.
*/
private static float getRawY(MotionEvent event, int pointerIndex) {
- float offset = event.getY() - event.getRawY();
+ float offset = event.getRawY() - event.getY();
return event.getY(pointerIndex) + offset;
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 83ef8ba51747..cd0ae3b02fbb 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -140,13 +140,13 @@ public class Surface implements Parcelable {
public static final int FLAGS_ORIENTATION_ANIMATION_DISABLE = 0x000000001;
@SuppressWarnings("unused")
- private int mSurface;
- @SuppressWarnings("unused")
private int mSurfaceControl;
@SuppressWarnings("unused")
private int mSaveCount;
@SuppressWarnings("unused")
private Canvas mCanvas;
+ @SuppressWarnings("unused")
+ private int mNativeSurface;
private String mName;
// The display metrics used to provide the pseudo canvas size for applications
@@ -422,13 +422,13 @@ public class Surface implements Parcelable {
/* no user serviceable parts here ... */
@Override
protected void finalize() throws Throwable {
- if (mSurface != 0 || mSurfaceControl != 0) {
+ if (mNativeSurface != 0 || mSurfaceControl != 0) {
if (DEBUG_RELEASE) {
Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
- + mSurface + ", " + mSurfaceControl + ")", mCreationStack);
+ + mNativeSurface + ", " + mSurfaceControl + ")", mCreationStack);
} else {
Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() ("
- + mSurface + ", " + mSurfaceControl + ")");
+ + mNativeSurface + ", " + mSurfaceControl + ")");
}
}
release();
diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java
index 64a10d194f5e..0d38f7bffa26 100644
--- a/core/java/android/view/SurfaceHolder.java
+++ b/core/java/android/view/SurfaceHolder.java
@@ -119,6 +119,23 @@ public interface SurfaceHolder {
}
/**
+ * Additional callbacks that can be received for {@link Callback}.
+ */
+ public interface Callback2 extends Callback {
+ /**
+ * Called when the application needs to redraw the content of its
+ * surface, after it is resized or for some other reason. By not
+ * returning here until the redraw is complete, you can ensure that
+ * the user will not see your surface in a bad state (at its new
+ * size before it has been correctly drawn that way). This will
+ * typically be preceeded by a call to {@link #surfaceChanged}.
+ *
+ * @param holder The SurfaceHolder whose surface has changed.
+ */
+ public void surfaceRedrawNeeded(SurfaceHolder holder);
+ }
+
+ /**
* Add a Callback interface for this holder. There can several Callback
* interfaces associated to a holder.
*
@@ -182,7 +199,6 @@ public interface SurfaceHolder {
/**
* Enable or disable option to keep the screen turned on while this
* surface is displayed. The default is false, allowing it to turn off.
- * Enabling the option effectivelty.
* This is safe to call from any thread.
*
* @param screenOn Supply to true to force the screen to stay on, false
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 53f0c2e5f917..54cb4ca8cbfa 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -123,7 +123,7 @@ public class SurfaceView extends View {
handleGetNewSurface();
} break;
case UPDATE_WINDOW_MSG: {
- updateWindow(false);
+ updateWindow(false, false);
} break;
}
}
@@ -132,7 +132,7 @@ public class SurfaceView extends View {
final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener
= new ViewTreeObserver.OnScrollChangedListener() {
public void onScrollChanged() {
- updateWindow(false);
+ updateWindow(false, false);
}
};
@@ -141,7 +141,10 @@ public class SurfaceView extends View {
boolean mViewVisibility = false;
int mRequestedWidth = -1;
int mRequestedHeight = -1;
- int mRequestedFormat = PixelFormat.OPAQUE;
+ /* Set SurfaceView's format to 565 by default to maintain backward
+ * compatibility with applications assuming this format.
+ */
+ int mRequestedFormat = PixelFormat.RGB_565;
int mRequestedType = -1;
boolean mHaveFrame = false;
@@ -164,16 +167,20 @@ public class SurfaceView extends View {
public SurfaceView(Context context) {
super(context);
- setWillNotDraw(true);
+ init();
}
public SurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
- setWillNotDraw(true);
+ init();
}
public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
setWillNotDraw(true);
}
@@ -203,7 +210,7 @@ public class SurfaceView extends View {
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
- updateWindow(false);
+ updateWindow(false, false);
}
@Override
@@ -211,7 +218,7 @@ public class SurfaceView extends View {
super.setVisibility(visibility);
mViewVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
- updateWindow(false);
+ updateWindow(false, false);
}
/**
@@ -225,7 +232,7 @@ public class SurfaceView extends View {
*/
protected void showSurface() {
if (mSession != null) {
- updateWindow(true);
+ updateWindow(true, false);
}
}
@@ -258,7 +265,7 @@ public class SurfaceView extends View {
protected void onDetachedFromWindow() {
getViewTreeObserver().removeOnScrollChangedListener(mScrollChangedListener);
mRequestedVisible = false;
- updateWindow(false);
+ updateWindow(false, false);
mHaveFrame = false;
if (mWindow != null) {
try {
@@ -283,7 +290,7 @@ public class SurfaceView extends View {
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
- updateWindow(false);
+ updateWindow(false, false);
}
@Override
@@ -336,7 +343,7 @@ public class SurfaceView extends View {
}
// reposition ourselves where the surface is
mHaveFrame = true;
- updateWindow(false);
+ updateWindow(false, false);
super.dispatchDraw(canvas);
}
@@ -390,7 +397,7 @@ public class SurfaceView extends View {
mWindowType = type;
}
- private void updateWindow(boolean force) {
+ private void updateWindow(boolean force, boolean redrawNeeded) {
if (!mHaveFrame) {
return;
}
@@ -418,7 +425,7 @@ public class SurfaceView extends View {
final boolean typeChanged = mType != mRequestedType;
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]
- || mUpdateWindowNeeded || mReportDrawNeeded) {
+ || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
if (localLOGV) Log.i(TAG, "Changes: creating=" + creating
+ " format=" + formatChanged + " size=" + sizeChanged
@@ -464,7 +471,7 @@ public class SurfaceView extends View {
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
mLayout.gravity = Gravity.LEFT|Gravity.TOP;
- mSession.add(mWindow, mLayout,
+ mSession.addWithoutInputChannel(mWindow, mLayout,
mVisible ? VISIBLE : GONE, mContentInsets);
}
@@ -517,6 +524,8 @@ public class SurfaceView extends View {
}
try {
+ redrawNeeded |= creating | reportDrawNeeded;
+
if (visible) {
mDestroyReportNeeded = true;
@@ -538,12 +547,20 @@ public class SurfaceView extends View {
c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
+ if (redrawNeeded) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ if (c instanceof SurfaceHolder.Callback2) {
+ ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
+ mSurfaceHolder);
+ }
+ }
+ }
} else {
mSurface.release();
}
} finally {
mIsCreating = false;
- if (creating || reportDrawNeeded) {
+ if (redrawNeeded) {
mSession.finishDrawing(mWindow);
}
}
@@ -573,7 +590,7 @@ public class SurfaceView extends View {
void handleGetNewSurface() {
mNewSurfaceNeeded = true;
- updateWindow(false);
+ updateWindow(false, false);
}
/**
@@ -618,41 +635,6 @@ public class SurfaceView extends View {
}
}
- public void dispatchKey(KeyEvent event) {
- SurfaceView surfaceView = mSurfaceView.get();
- if (surfaceView != null) {
- //Log.w("SurfaceView", "Unexpected key event in surface: " + event);
- if (surfaceView.mSession != null && surfaceView.mSurface != null) {
- try {
- surfaceView.mSession.finishKey(surfaceView.mWindow);
- } catch (RemoteException ex) {
- }
- }
- }
- }
-
- public void dispatchPointer(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- Log.w("SurfaceView", "Unexpected pointer event in surface: " + event);
- //if (mSession != null && mSurface != null) {
- // try {
- // //mSession.finishKey(mWindow);
- // } catch (RemoteException ex) {
- // }
- //}
- }
-
- public void dispatchTrackball(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- Log.w("SurfaceView", "Unexpected trackball event in surface: " + event);
- //if (mSession != null && mSurface != null) {
- // try {
- // //mSession.finishKey(mWindow);
- // } catch (RemoteException ex) {
- // }
- //}
- }
-
public void dispatchAppVisibility(boolean visible) {
// The point of SurfaceView is to let the app control the surface.
}
@@ -679,7 +661,6 @@ public class SurfaceView extends View {
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
- private int mSaveCount;
public boolean isCreating() {
return mIsCreating;
@@ -717,9 +698,15 @@ public class SurfaceView extends View {
}
public void setFormat(int format) {
+
+ // for backward compatibility reason, OPAQUE always
+ // means 565 for SurfaceView
+ if (format == PixelFormat.OPAQUE)
+ format = PixelFormat.RGB_565;
+
mRequestedFormat = format;
if (mWindow != null) {
- updateWindow(false);
+ updateWindow(false, false);
}
}
@@ -736,7 +723,7 @@ public class SurfaceView extends View {
case SURFACE_TYPE_PUSH_BUFFERS:
mRequestedType = type;
if (mWindow != null) {
- updateWindow(false);
+ updateWindow(false, false);
}
break;
}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index aab76c4fdfcf..fb88c7135ba6 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -33,14 +33,15 @@ import android.util.PoolableManager;
* and {@link #getXVelocity()}.
*/
public final class VelocityTracker implements Poolable<VelocityTracker> {
- static final String TAG = "VelocityTracker";
- static final boolean DEBUG = false;
- static final boolean localLOGV = DEBUG || Config.LOGV;
+ private static final String TAG = "VelocityTracker";
+ private static final boolean DEBUG = false;
+ private static final boolean localLOGV = DEBUG || Config.LOGV;
- static final int NUM_PAST = 10;
- static final int LONGEST_PAST_TIME = 200;
+ private static final int NUM_PAST = 10;
+ private static final int MAX_AGE_MILLISECONDS = 200;
+
+ private static final int POINTER_POOL_CAPACITY = 20;
- static final VelocityTracker[] mPool = new VelocityTracker[1];
private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
Pools.finitePool(new PoolableManager<VelocityTracker>() {
public VelocityTracker newInstance() {
@@ -48,20 +49,33 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
}
public void onAcquired(VelocityTracker element) {
- element.clear();
}
public void onReleased(VelocityTracker element) {
+ element.clear();
}
}, 2));
-
- final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
- final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
- final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST];
-
- float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
- float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
- int mLastTouch;
+
+ private static Pointer sRecycledPointerListHead;
+ private static int sRecycledPointerCount;
+
+ private static final class Pointer {
+ public Pointer next;
+
+ public int id;
+ public float xVelocity;
+ public float yVelocity;
+
+ public final float[] pastX = new float[NUM_PAST];
+ public final float[] pastY = new float[NUM_PAST];
+ public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
+
+ public int generation;
+ }
+
+ private Pointer mPointerListHead; // sorted by id in increasing order
+ private int mLastTouchIndex;
+ private int mGeneration;
private VelocityTracker mNext;
@@ -107,12 +121,10 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
- final long[][] pastTime = mPastTime;
- for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) {
- for (int i = 0; i < NUM_PAST; i++) {
- pastTime[p][i] = Long.MIN_VALUE;
- }
- }
+ releasePointerList(mPointerListHead);
+
+ mPointerListHead = null;
+ mLastTouchIndex = 0;
}
/**
@@ -125,37 +137,96 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @param ev The MotionEvent you received and would like to track.
*/
public void addMovement(MotionEvent ev) {
- final int N = ev.getHistorySize();
+ final int historySize = ev.getHistorySize();
final int pointerCount = ev.getPointerCount();
- int touchIndex = (mLastTouch + 1) % NUM_PAST;
- for (int i=0; i<N; i++) {
- for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
- mPastTime[id][touchIndex] = Long.MIN_VALUE;
+ final int lastTouchIndex = mLastTouchIndex;
+ final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
+ final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
+ final int generation = mGeneration++;
+
+ mLastTouchIndex = finalTouchIndex;
+
+ // Update pointer data.
+ Pointer previousPointer = null;
+ for (int i = 0; i < pointerCount; i++){
+ final int pointerId = ev.getPointerId(i);
+
+ // Find the pointer data for this pointer id.
+ // This loop is optimized for the common case where pointer ids in the event
+ // are in sorted order. However, we check for this case explicitly and
+ // perform a full linear scan from the start if needed.
+ Pointer nextPointer;
+ if (previousPointer == null || pointerId < previousPointer.id) {
+ previousPointer = null;
+ nextPointer = mPointerListHead;
+ } else {
+ nextPointer = previousPointer.next;
}
- for (int p = 0; p < pointerCount; p++) {
- int id = ev.getPointerId(p);
- mPastX[id][touchIndex] = ev.getHistoricalX(p, i);
- mPastY[id][touchIndex] = ev.getHistoricalY(p, i);
- mPastTime[id][touchIndex] = ev.getHistoricalEventTime(i);
+
+ final Pointer pointer;
+ for (;;) {
+ if (nextPointer != null) {
+ final int nextPointerId = nextPointer.id;
+ if (nextPointerId == pointerId) {
+ pointer = nextPointer;
+ break;
+ }
+ if (nextPointerId < pointerId) {
+ nextPointer = nextPointer.next;
+ continue;
+ }
+ }
+
+ // Pointer went down. Add it to the list.
+ // Write a sentinel at the end of the pastTime trace so we will be able to
+ // tell when the trace started.
+ pointer = obtainPointer();
+ pointer.id = pointerId;
+ pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
+ pointer.next = nextPointer;
+ if (previousPointer == null) {
+ mPointerListHead = pointer;
+ } else {
+ previousPointer.next = pointer;
+ }
+ break;
}
-
- touchIndex = (touchIndex + 1) % NUM_PAST;
- }
-
- // During calculation any pointer values with a time of MIN_VALUE are treated
- // as a break in input. Initialize all to MIN_VALUE for each new touch index.
- for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
- mPastTime[id][touchIndex] = Long.MIN_VALUE;
+
+ pointer.generation = generation;
+ previousPointer = pointer;
+
+ final float[] pastX = pointer.pastX;
+ final float[] pastY = pointer.pastY;
+ final long[] pastTime = pointer.pastTime;
+
+ for (int j = 0; j < historySize; j++) {
+ final int touchIndex = (nextTouchIndex + j) % NUM_PAST;
+ pastX[touchIndex] = ev.getHistoricalX(i, j);
+ pastY[touchIndex] = ev.getHistoricalY(i, j);
+ pastTime[touchIndex] = ev.getHistoricalEventTime(j);
+ }
+ pastX[finalTouchIndex] = ev.getX(i);
+ pastY[finalTouchIndex] = ev.getY(i);
+ pastTime[finalTouchIndex] = ev.getEventTime();
}
- final long time = ev.getEventTime();
- for (int p = 0; p < pointerCount; p++) {
- int id = ev.getPointerId(p);
- mPastX[id][touchIndex] = ev.getX(p);
- mPastY[id][touchIndex] = ev.getY(p);
- mPastTime[id][touchIndex] = time;
+
+ // Find removed pointers.
+ previousPointer = null;
+ for (Pointer pointer = mPointerListHead; pointer != null; ) {
+ final Pointer nextPointer = pointer.next;
+ if (pointer.generation != generation) {
+ // Pointer went up. Remove it from the list.
+ if (previousPointer == null) {
+ mPointerListHead = nextPointer;
+ } else {
+ previousPointer.next = nextPointer;
+ }
+ releasePointer(pointer);
+ } else {
+ previousPointer = pointer;
+ }
+ pointer = nextPointer;
}
-
- mLastTouch = touchIndex;
}
/**
@@ -182,54 +253,77 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* must be positive.
*/
public void computeCurrentVelocity(int units, float maxVelocity) {
- for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) {
- final float[] pastX = mPastX[pos];
- final float[] pastY = mPastY[pos];
- final long[] pastTime = mPastTime[pos];
- final int lastTouch = mLastTouch;
+ final int lastTouchIndex = mLastTouchIndex;
- // find oldest acceptable time
- int oldestTouch = lastTouch;
- if (pastTime[lastTouch] != Long.MIN_VALUE) { // cleared ?
- final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME;
- int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
- while (pastTime[nextOldestTouch] >= acceptableTime &&
- nextOldestTouch != lastTouch) {
- oldestTouch = nextOldestTouch;
- nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST;
+ for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
+ final long[] pastTime = pointer.pastTime;
+
+ // Search backwards in time for oldest acceptable time.
+ // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE.
+ int oldestTouchIndex = lastTouchIndex;
+ int numTouches = 1;
+ final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
+ while (numTouches < NUM_PAST) {
+ final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
+ final long nextOldestTime = pastTime[nextOldestTouchIndex];
+ if (nextOldestTime < minTime) { // also handles end of trace sentinel
+ break;
}
+ oldestTouchIndex = nextOldestTouchIndex;
+ numTouches += 1;
}
-
+
+ // If we have a lot of samples, skip the last received sample since it is
+ // probably pretty noisy compared to the sum of all of the traces already acquired.
+ if (numTouches > 3) {
+ numTouches -= 1;
+ }
+
// Kind-of stupid.
- final float oldestX = pastX[oldestTouch];
- final float oldestY = pastY[oldestTouch];
- final long oldestTime = pastTime[oldestTouch];
+ final float[] pastX = pointer.pastX;
+ final float[] pastY = pointer.pastY;
+
+ final float oldestX = pastX[oldestTouchIndex];
+ final float oldestY = pastY[oldestTouchIndex];
+ final long oldestTime = pastTime[oldestTouchIndex];
+
float accumX = 0;
float accumY = 0;
- float N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1;
- // Skip the last received event, since it is probably pretty noisy.
- if (N > 3) N--;
-
- for (int i=1; i < N; i++) {
- final int j = (oldestTouch + i) % NUM_PAST;
- final int dur = (int)(pastTime[j] - oldestTime);
- if (dur == 0) continue;
- float dist = pastX[j] - oldestX;
- float vel = (dist/dur) * units; // pixels/frame.
- accumX = (accumX == 0) ? vel : (accumX + vel) * .5f;
- dist = pastY[j] - oldestY;
- vel = (dist/dur) * units; // pixels/frame.
- accumY = (accumY == 0) ? vel : (accumY + vel) * .5f;
+ for (int i = 1; i < numTouches; i++) {
+ final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
+ final int duration = (int)(pastTime[touchIndex] - oldestTime);
+
+ if (duration == 0) continue;
+
+ float delta = pastX[touchIndex] - oldestX;
+ float velocity = (delta / duration) * units; // pixels/frame.
+ accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;
+
+ delta = pastY[touchIndex] - oldestY;
+ velocity = (delta / duration) * units; // pixels/frame.
+ accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
}
- mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity)
- : Math.min(accumX, maxVelocity);
- mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity)
- : Math.min(accumY, maxVelocity);
-
- if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
- + mXVelocity + " N=" + N);
+ if (accumX < -maxVelocity) {
+ accumX = - maxVelocity;
+ } else if (accumX > maxVelocity) {
+ accumX = maxVelocity;
+ }
+
+ if (accumY < -maxVelocity) {
+ accumY = - maxVelocity;
+ } else if (accumY > maxVelocity) {
+ accumY = maxVelocity;
+ }
+
+ pointer.xVelocity = accumX;
+ pointer.yVelocity = accumY;
+
+ if (localLOGV) {
+ Log.v(TAG, "Pointer " + pointer.id
+ + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
+ }
}
}
@@ -240,7 +334,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed X velocity.
*/
public float getXVelocity() {
- return mXVelocity[0];
+ Pointer pointer = getPointer(0);
+ return pointer != null ? pointer.xVelocity : 0;
}
/**
@@ -250,7 +345,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed Y velocity.
*/
public float getYVelocity() {
- return mYVelocity[0];
+ Pointer pointer = getPointer(0);
+ return pointer != null ? pointer.yVelocity : 0;
}
/**
@@ -261,7 +357,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed X velocity.
*/
public float getXVelocity(int id) {
- return mXVelocity[id];
+ Pointer pointer = getPointer(id);
+ return pointer != null ? pointer.xVelocity : 0;
}
/**
@@ -272,6 +369,68 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return The previously computed Y velocity.
*/
public float getYVelocity(int id) {
- return mYVelocity[id];
+ Pointer pointer = getPointer(id);
+ return pointer != null ? pointer.yVelocity : 0;
+ }
+
+ private final Pointer getPointer(int id) {
+ for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
+ if (pointer.id == id) {
+ return pointer;
+ }
+ }
+ return null;
+ }
+
+ private static final Pointer obtainPointer() {
+ synchronized (sPool) {
+ if (sRecycledPointerCount != 0) {
+ Pointer element = sRecycledPointerListHead;
+ sRecycledPointerCount -= 1;
+ sRecycledPointerListHead = element.next;
+ element.next = null;
+ return element;
+ }
+ }
+ return new Pointer();
+ }
+
+ private static final void releasePointer(Pointer pointer) {
+ synchronized (sPool) {
+ if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
+ pointer.next = sRecycledPointerListHead;
+ sRecycledPointerCount += 1;
+ sRecycledPointerListHead = pointer;
+ }
+ }
+ }
+
+ private static final void releasePointerList(Pointer pointer) {
+ if (pointer != null) {
+ synchronized (sPool) {
+ int count = sRecycledPointerCount;
+ if (count >= POINTER_POOL_CAPACITY) {
+ return;
+ }
+
+ Pointer tail = pointer;
+ for (;;) {
+ count += 1;
+ if (count >= POINTER_POOL_CAPACITY) {
+ break;
+ }
+
+ Pointer next = tail.next;
+ if (next == null) {
+ break;
+ }
+ tail = next;
+ }
+
+ tail.next = sRecycledPointerListHead;
+ sRecycledPointerCount = count;
+ sRecycledPointerListHead = pointer;
+ }
+ }
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f9abe60b47c6..b794a6a1e406 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -542,6 +542,28 @@ import java.util.WeakHashMap;
* take care of redrawing the appropriate views until the animation completes.
* </p>
*
+ * <a name="Security"></a>
+ * <h3>Security</h3>
+ * <p>
+ * Sometimes it is essential that an application be able to verify that an action
+ * is being performed with the full knowledge and consent of the user, such as
+ * granting a permission request, making a purchase or clicking on an advertisement.
+ * Unfortunately, a malicious application could try to spoof the user into
+ * performing these actions, unaware, by concealing the intended purpose of the view.
+ * As a remedy, the framework offers a touch filtering mechanism that can be used to
+ * improve the security of views that provide access to sensitive functionality.
+ * </p><p>
+ * To enable touch filtering, call {@link #setFilterTouchesWhenObscured} or set the
+ * andoird:filterTouchesWhenObscured attribute to true. When enabled, the framework
+ * will discard touches that are received whenever the view's window is obscured by
+ * another visible window. As a result, the view will not receive touches whenever a
+ * toast, dialog or other window appears above the view's window.
+ * </p><p>
+ * For more fine-grained control over security, consider overriding the
+ * {@link #onFilterTouchEventForSecurity} method to implement your own security policy.
+ * See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}.
+ * </p>
+ *
* @attr ref android.R.styleable#View_background
* @attr ref android.R.styleable#View_clickable
* @attr ref android.R.styleable#View_contentDescription
@@ -550,6 +572,7 @@ import java.util.WeakHashMap;
* @attr ref android.R.styleable#View_id
* @attr ref android.R.styleable#View_fadingEdge
* @attr ref android.R.styleable#View_fadingEdgeLength
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
* @attr ref android.R.styleable#View_fitsSystemWindows
* @attr ref android.R.styleable#View_isScrollContainer
* @attr ref android.R.styleable#View_focusable
@@ -711,7 +734,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
static final int SCROLLBARS_MASK = 0x00000300;
- // note 0x00000400 and 0x00000800 are now available for next flags...
+ /**
+ * Indicates that the view should filter touches when its window is obscured.
+ * Refer to the class comments for more information about this security feature.
+ * {@hide}
+ */
+ static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400;
+
+ // note flag value 0x00000800 is now available for next flags...
/**
* <p>This view doesn't show fading edges.</p>
@@ -1358,14 +1388,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Width as measured during measure pass.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "measurement")
protected int mMeasuredWidth;
/**
* Height as measured during measure pass.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "measurement")
protected int mMeasuredHeight;
/**
@@ -1420,8 +1450,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
static final int MEASURED_DIMENSION_SET = 0x00000800;
/** {@hide} */
static final int FORCE_LAYOUT = 0x00001000;
-
- private static final int LAYOUT_REQUIRED = 0x00002000;
+ /** {@hide} */
+ static final int LAYOUT_REQUIRED = 0x00002000;
private static final int PRESSED = 0x00004000;
@@ -1521,6 +1551,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
/**
+ * Always allow a user to overscroll this view, provided it is a
+ * view that can scroll.
+ *
+ * @see #getOverscrollMode()
+ * @see #setOverscrollMode(int)
+ */
+ public static final int OVERSCROLL_ALWAYS = 0;
+
+ /**
+ * Allow a user to overscroll this view only if the content is large
+ * enough to meaningfully scroll, provided it is a view that can scroll.
+ *
+ * @see #getOverscrollMode()
+ * @see #setOverscrollMode(int)
+ */
+ public static final int OVERSCROLL_IF_CONTENT_SCROLLS = 1;
+
+ /**
+ * Never allow a user to overscroll this view.
+ *
+ * @see #getOverscrollMode()
+ * @see #setOverscrollMode(int)
+ */
+ public static final int OVERSCROLL_NEVER = 2;
+
+ /**
+ * Controls the overscroll mode for this view.
+ * See {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)},
+ * {@link #OVERSCROLL_ALWAYS}, {@link #OVERSCROLL_IF_CONTENT_SCROLLS},
+ * and {@link #OVERSCROLL_NEVER}.
+ */
+ private int mOverscrollMode;
+
+ /**
* The parent this view is attached to.
* {@hide}
*
@@ -1575,28 +1639,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* to the left edge of this view.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
protected int mLeft;
/**
* The distance in pixels from the left edge of this view's parent
* to the right edge of this view.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
protected int mRight;
/**
* The distance in pixels from the top edge of this view's parent
* to the top edge of this view.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
protected int mTop;
/**
* The distance in pixels from the top edge of this view's parent
* to the bottom edge of this view.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
protected int mBottom;
/**
@@ -1604,14 +1668,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* horizontally.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "scrolling")
protected int mScrollX;
/**
* The offset, in pixels, by which the content of this view is scrolled
* vertically.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "scrolling")
protected int mScrollY;
/**
@@ -1619,28 +1683,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* left edge of this view and the left edge of its content.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "padding")
protected int mPaddingLeft;
/**
* The right padding in pixels, that is the distance in pixels between the
* right edge of this view and the right edge of its content.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "padding")
protected int mPaddingRight;
/**
* The top padding in pixels, that is the distance in pixels between the
* top edge of this view and the top edge of its content.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "padding")
protected int mPaddingTop;
/**
* The bottom padding in pixels, that is the distance in pixels between the
* bottom edge of this view and the bottom edge of its content.
* {@hide}
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "padding")
protected int mPaddingBottom;
/**
@@ -1651,13 +1715,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
/**
* Cache the paddingRight set by the user to append to the scrollbar's size.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "padding")
int mUserPaddingRight;
/**
* Cache the paddingBottom set by the user to append to the scrollbar's size.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "padding")
int mUserPaddingBottom;
/**
@@ -1764,14 +1828,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* The minimum height of the view. We'll try our best to have the height
* of this view to at least this amount.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "measurement")
private int mMinHeight;
/**
* The minimum width of the view. We'll try our best to have the width
* of this view to at least this amount.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "measurement")
private int mMinWidth;
/**
@@ -1812,6 +1876,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
// Used for debug only
//++sInstanceCount;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ setOverscrollMode(OVERSCROLL_ALWAYS);
}
/**
@@ -1877,6 +1942,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
+ int overscrollMode = mOverscrollMode;
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
@@ -2017,6 +2083,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
viewFlagMasks |= KEEP_SCREEN_ON;
}
break;
+ case R.styleable.View_filterTouchesWhenObscured:
+ if (a.getBoolean(attr, false)) {
+ viewFlagValues |= FILTER_TOUCHES_WHEN_OBSCURED;
+ viewFlagMasks |= FILTER_TOUCHES_WHEN_OBSCURED;
+ }
+ break;
case R.styleable.View_nextFocusLeft:
mNextFocusLeftId = a.getResourceId(attr, View.NO_ID);
break;
@@ -2076,9 +2148,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
});
}
break;
+ case R.styleable.View_overscrollMode:
+ overscrollMode = a.getInt(attr, OVERSCROLL_ALWAYS);
+ break;
}
}
+ setOverscrollMode(overscrollMode);
+
if (background != null) {
setBackgroundDrawable(background);
}
@@ -2413,11 +2490,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
- * Call this view's OnLongClickListener, if it is defined. Invokes the context menu
- * if the OnLongClickListener did not consume the event.
+ * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
+ * OnLongClickListener did not consume the event.
*
- * @return True there was an assigned OnLongClickListener that was called, false
- * otherwise is returned.
+ * @return True if one of the above receivers consumed the event, false otherwise.
*/
public boolean performLongClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
@@ -2602,7 +2678,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return True if this view has or contains focus, false otherwise.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "focus")
public boolean hasFocus() {
return (mPrivateFlags & FOCUSED) != 0;
}
@@ -2780,7 +2856,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return True if this view has focus, false otherwise.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "focus")
public boolean isFocused() {
return (mPrivateFlags & FOCUSED) != 0;
}
@@ -3005,6 +3081,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Determine if this view has the FITS_SYSTEM_WINDOWS flag set.
+ * @return True if window has FITS_SYSTEM_WINDOWS set
+ *
+ * @hide
+ */
+ public boolean isFitsSystemWindowsFlagSet() {
+ return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS;
+ }
+
+ /**
* Returns the visibility status for this view.
*
* @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}.
@@ -3181,7 +3267,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return true if this view has nothing to draw, false otherwise
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean willNotDraw() {
return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
}
@@ -3204,7 +3290,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return true if this view does not cache its drawing, false otherwise
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean willNotCacheDrawing() {
return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING;
}
@@ -3340,6 +3426,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK);
}
+ /**
+ * Gets whether the framework should discard touches when the view's
+ * window is obscured by another visible window.
+ * Refer to the {@link View} security documentation for more details.
+ *
+ * @return True if touch filtering is enabled.
+ *
+ * @see #setFilterTouchesWhenObscured(boolean)
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ */
+ @ViewDebug.ExportedProperty
+ public boolean getFilterTouchesWhenObscured() {
+ return (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0;
+ }
+
+ /**
+ * Sets whether the framework should discard touches when the view's
+ * window is obscured by another visible window.
+ * Refer to the {@link View} security documentation for more details.
+ *
+ * @param enabled True if touch filtering should be enabled.
+ *
+ * @see #getFilterTouchesWhenObscured
+ * @attr ref android.R.styleable#View_filterTouchesWhenObscured
+ */
+ public void setFilterTouchesWhenObscured(boolean enabled) {
+ setFlags(enabled ? 0 : FILTER_TOUCHES_WHEN_OBSCURED,
+ FILTER_TOUCHES_WHEN_OBSCURED);
+ }
/**
* Returns whether this View is able to take focus.
@@ -3347,7 +3462,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if this view can take focus, or false otherwise.
* @attr ref android.R.styleable#View_focusable
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "focus")
public final boolean isFocusable() {
return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);
}
@@ -3759,6 +3874,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
+ if (!onFilterTouchEventForSecurity(event)) {
+ return false;
+ }
+
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
@@ -3767,6 +3886,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Filter the touch event to apply security policies.
+ *
+ * @param event The motion event to be filtered.
+ * @return True if the event should be dispatched, false if the event should be dropped.
+ *
+ * @see #getFilterTouchesWhenObscured
+ */
+ public boolean onFilterTouchEventForSecurity(MotionEvent event) {
+ if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
+ && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
+ // Window is obscured, drop this touch.
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Pass a trackball motion event down to the focused view.
*
* @param event The motion event to be dispatched.
@@ -4208,6 +4344,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* Show the context menu for this view. It is not safe to hold on to the
* menu after returning from this method.
*
+ * You should normally not overload this method. Overload
+ * {@link #onCreateContextMenu(ContextMenu)} or define an
+ * {@link OnCreateContextMenuListener} to add items to the context menu.
+ *
* @param menu The context menu to populate
*/
public void createContextMenu(ContextMenu menu) {
@@ -4656,7 +4796,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return The width of your view, in pixels.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
@@ -4666,7 +4806,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return The height of your view, in pixels.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
return mBottom - mTop;
}
@@ -5152,7 +5292,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*
* @return True if this View is guaranteed to be fully opaque, false otherwise.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean isOpaque() {
return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK;
}
@@ -6237,7 +6377,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @see #setDrawingCacheEnabled(boolean)
* @see #getDrawingCache()
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean isDrawingCacheEnabled() {
return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
}
@@ -8106,7 +8246,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @return the offset of the baseline within the widget's bounds or -1
* if baseline alignment is not supported
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public int getBaseline() {
return -1;
}
@@ -8668,6 +8808,128 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
}
/**
+ * Scroll the view with standard behavior for scrolling beyond the normal
+ * content boundaries. Views that call this method should override
+ * {@link #onOverscrolled(int, int, boolean, boolean)} to respond to the
+ * results of an overscroll operation.
+ *
+ * Views can use this method to handle any touch or fling-based scrolling.
+ *
+ * @param deltaX Change in X in pixels
+ * @param deltaY Change in Y in pixels
+ * @param scrollX Current X scroll value in pixels before applying deltaX
+ * @param scrollY Current Y scroll value in pixels before applying deltaY
+ * @param scrollRangeX Maximum content scroll range along the X axis
+ * @param scrollRangeY Maximum content scroll range along the Y axis
+ * @param maxOverscrollX Number of pixels to overscroll by in either direction
+ * along the X axis.
+ * @param maxOverscrollY Number of pixels to overscroll by in either direction
+ * along the Y axis.
+ * @param isTouchEvent true if this scroll operation is the result of a touch event.
+ * @return true if scrolling was clamped to an overscroll boundary along either
+ * axis, false otherwise.
+ */
+ protected boolean overscrollBy(int deltaX, int deltaY,
+ int scrollX, int scrollY,
+ int scrollRangeX, int scrollRangeY,
+ int maxOverscrollX, int maxOverscrollY,
+ boolean isTouchEvent) {
+ final int overscrollMode = mOverscrollMode;
+ final boolean canScrollHorizontal =
+ computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+ final boolean canScrollVertical =
+ computeVerticalScrollRange() > computeVerticalScrollExtent();
+ final boolean overscrollHorizontal = overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+ final boolean overscrollVertical = overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+ int newScrollX = scrollX + deltaX;
+ if (!overscrollHorizontal) {
+ maxOverscrollX = 0;
+ }
+
+ int newScrollY = scrollY + deltaY;
+ if (!overscrollVertical) {
+ maxOverscrollY = 0;
+ }
+
+ // Clamp values if at the limits and record
+ final int left = -maxOverscrollX;
+ final int right = maxOverscrollX + scrollRangeX;
+ final int top = -maxOverscrollY;
+ final int bottom = maxOverscrollY + scrollRangeY;
+
+ boolean clampedX = false;
+ if (newScrollX > right) {
+ newScrollX = right;
+ clampedX = true;
+ } else if (newScrollX < left) {
+ newScrollX = left;
+ clampedX = true;
+ }
+
+ boolean clampedY = false;
+ if (newScrollY > bottom) {
+ newScrollY = bottom;
+ clampedY = true;
+ } else if (newScrollY < top) {
+ newScrollY = top;
+ clampedY = true;
+ }
+
+ onOverscrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+ return clampedX || clampedY;
+ }
+
+ /**
+ * Called by {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)} to
+ * respond to the results of an overscroll operation.
+ *
+ * @param scrollX New X scroll value in pixels
+ * @param scrollY New Y scroll value in pixels
+ * @param clampedX True if scrollX was clamped to an overscroll boundary
+ * @param clampedY True if scrollY was clamped to an overscroll boundary
+ */
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ // Intentionally empty.
+ }
+
+ /**
+ * Returns the overscroll mode for this view. The result will be
+ * one of {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS}
+ * (allow overscrolling only if the view content is larger than the container),
+ * or {@link #OVERSCROLL_NEVER}.
+ *
+ * @return This view's overscroll mode.
+ */
+ public int getOverscrollMode() {
+ return mOverscrollMode;
+ }
+
+ /**
+ * Set the overscroll mode for this view. Valid overscroll modes are
+ * {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS}
+ * (allow overscrolling only if the view content is larger than the container),
+ * or {@link #OVERSCROLL_NEVER}.
+ *
+ * Setting the overscroll mode of a view will have an effect only if the
+ * view is capable of scrolling.
+ *
+ * @param overscrollMode The new overscroll mode for this view.
+ */
+ public void setOverscrollMode(int overscrollMode) {
+ if (overscrollMode != OVERSCROLL_ALWAYS &&
+ overscrollMode != OVERSCROLL_IF_CONTENT_SCROLLS &&
+ overscrollMode != OVERSCROLL_NEVER) {
+ throw new IllegalArgumentException("Invalid overscroll mode " + overscrollMode);
+ }
+ mOverscrollMode = overscrollMode;
+ }
+
+ /**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
* A MeasureSpec is comprised of a size and a mode. There are three possible
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index acdfc2829da9..924c9d48f5d4 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -140,6 +140,16 @@ public class ViewConfiguration {
*/
private static float SCROLL_FRICTION = 0.015f;
+ /**
+ * Max distance to overscroll for edge effects
+ */
+ private static final int OVERSCROLL_DISTANCE = 0;
+
+ /**
+ * Max distance to overfling for edge effects
+ */
+ private static final int OVERFLING_DISTANCE = 4;
+
private final int mEdgeSlop;
private final int mFadingEdgeLength;
private final int mMinimumFlingVelocity;
@@ -150,6 +160,8 @@ public class ViewConfiguration {
private final int mDoubleTapSlop;
private final int mWindowTouchSlop;
private final int mMaximumDrawingCacheSize;
+ private final int mOverscrollDistance;
+ private final int mOverflingDistance;
private static final SparseArray<ViewConfiguration> sConfigurations =
new SparseArray<ViewConfiguration>(2);
@@ -170,6 +182,8 @@ public class ViewConfiguration {
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
+ mOverscrollDistance = OVERSCROLL_DISTANCE;
+ mOverflingDistance = OVERFLING_DISTANCE;
}
/**
@@ -198,6 +212,9 @@ public class ViewConfiguration {
// Size of the screen in bytes, in ARGB_8888 format
mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels;
+
+ mOverscrollDistance = (int) (density * OVERSCROLL_DISTANCE + 0.5f);
+ mOverflingDistance = (int) (density * OVERFLING_DISTANCE + 0.5f);
}
/**
@@ -455,6 +472,20 @@ public class ViewConfiguration {
}
/**
+ * @return The maximum distance a View should overscroll by when showing edge effects.
+ */
+ public int getScaledOverscrollDistance() {
+ return mOverscrollDistance;
+ }
+
+ /**
+ * @return The maximum distance a View should overfling by when showing edge effects.
+ */
+ public int getScaledOverflingDistance() {
+ return mOverflingDistance;
+ }
+
+ /**
* The amount of time that the zoom controls should be
* displayed on the screen expressed in milliseconds.
*
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index d2563a87713f..7b6991fed051 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -255,6 +255,14 @@ public class ViewDebug {
* @see #deepExport()
*/
String prefix() default "";
+
+ /**
+ * Specifies the category the property falls into, such as measurement,
+ * layout, drawing, etc.
+ *
+ * @return the category as String
+ */
+ String category() default "";
}
/**
@@ -916,72 +924,13 @@ public class ViewDebug {
out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
if (view != null) {
- final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
- public Void[] pre() {
- forceLayout(view);
- return null;
- }
-
- private void forceLayout(View view) {
- view.forceLayout();
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
- final int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- forceLayout(group.getChildAt(i));
- }
- }
- }
-
- public void run(Void... data) {
- view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
- }
-
- public void post(Void... data) {
- }
- });
-
- final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
- public Void[] pre() {
- return null;
- }
-
- public void run(Void... data) {
- view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
- }
-
- public void post(Void... data) {
- }
- });
-
- final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
- public Object[] pre() {
- final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
- final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels,
- metrics.heightPixels, Bitmap.Config.RGB_565);
- final Canvas canvas = new Canvas(bitmap);
- return new Object[] { bitmap, canvas };
- }
-
- public void run(Object... data) {
- view.draw((Canvas) data[1]);
- }
-
- public void post(Object... data) {
- ((Bitmap) data[0]).recycle();
- }
- });
-
- out.write(String.valueOf(durationMeasure));
- out.write(' ');
- out.write(String.valueOf(durationLayout));
- out.write(' ');
- out.write(String.valueOf(durationDraw));
- out.newLine();
+ profileViewAndChildren(view, out);
} else {
out.write("-1 -1 -1");
out.newLine();
}
+ out.write("DONE.");
+ out.newLine();
} catch (Exception e) {
android.util.Log.w("View", "Problem profiling the view:", e);
} finally {
@@ -991,6 +940,94 @@ public class ViewDebug {
}
}
+ private static void profileViewAndChildren(final View view, BufferedWriter out)
+ throws IOException {
+ profileViewAndChildren(view, out, true);
+ }
+
+ private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
+ throws IOException {
+
+ long durationMeasure =
+ (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation(
+ view, new ViewOperation<Void>() {
+ public Void[] pre() {
+ forceLayout(view);
+ return null;
+ }
+
+ private void forceLayout(View view) {
+ view.forceLayout();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ forceLayout(group.getChildAt(i));
+ }
+ }
+ }
+
+ public void run(Void... data) {
+ view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+ }
+
+ public void post(Void... data) {
+ }
+ })
+ : 0;
+ long durationLayout =
+ (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation(
+ view, new ViewOperation<Void>() {
+ public Void[] pre() {
+ return null;
+ }
+
+ public void run(Void... data) {
+ view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
+ }
+
+ public void post(Void... data) {
+ }
+ }) : 0;
+ long durationDraw =
+ (root || !view.willNotDraw() || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation(
+ view,
+ new ViewOperation<Object>() {
+ public Object[] pre() {
+ final DisplayMetrics metrics =
+ view.getResources().getDisplayMetrics();
+ final Bitmap bitmap =
+ Bitmap.createBitmap(metrics.widthPixels,
+ metrics.heightPixels, Bitmap.Config.RGB_565);
+ final Canvas canvas = new Canvas(bitmap);
+ return new Object[] {
+ bitmap, canvas
+ };
+ }
+
+ public void run(Object... data) {
+ view.draw((Canvas) data[1]);
+ }
+
+ public void post(Object... data) {
+ ((Bitmap) data[0]).recycle();
+ }
+ }) : 0;
+ out.write(String.valueOf(durationMeasure));
+ out.write(' ');
+ out.write(String.valueOf(durationLayout));
+ out.write(' ');
+ out.write(String.valueOf(durationDraw));
+ out.newLine();
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ profileViewAndChildren(group.getChildAt(i), out, false);
+ }
+ }
+ }
+
interface ViewOperation<T> {
T[] pre();
void run(T... data);
@@ -1016,7 +1053,10 @@ public class ViewDebug {
});
try {
- latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
+ if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ Log.w("View", "Could not complete the profiling of the view " + view);
+ return -1;
+ }
} catch (InterruptedException e) {
Log.w("View", "Could not complete the profiling of the view " + view);
Thread.currentThread().interrupt();
@@ -1098,22 +1138,24 @@ public class ViewDebug {
final View captureView = findView(root, parameter);
Bitmap b = performViewCapture(captureView, false);
-
- if (b != null) {
- BufferedOutputStream out = null;
- try {
- out = new BufferedOutputStream(clientStream, 32 * 1024);
- b.compress(Bitmap.CompressFormat.PNG, 100, out);
- out.flush();
- } finally {
- if (out != null) {
- out.close();
- }
- b.recycle();
- }
- } else {
+
+ if (b == null) {
Log.w("View", "Failed to create capture bitmap!");
- clientStream.close();
+ // Send an empty one so that it doesn't get stuck waiting for
+ // something.
+ b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ }
+
+ BufferedOutputStream out = null;
+ try {
+ out = new BufferedOutputStream(clientStream, 32 * 1024);
+ b.compress(Bitmap.CompressFormat.PNG, 100, out);
+ out.flush();
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ b.recycle();
}
}
@@ -1337,9 +1379,12 @@ public class ViewDebug {
// TODO: This should happen on the UI thread
Object methodValue = method.invoke(view, (Object[]) null);
final Class<?> returnType = method.getReturnType();
+ final ExportedProperty property = sAnnotations.get(method);
+ String categoryPrefix =
+ property.category().length() != 0 ? property.category() + ":" : "";
if (returnType == int.class) {
- final ExportedProperty property = sAnnotations.get(method);
+
if (property.resolveId() && context != null) {
final int id = (Integer) methodValue;
methodValue = resolveId(context, id);
@@ -1347,7 +1392,8 @@ public class ViewDebug {
final FlagToString[] flagsMapping = property.flagMapping();
if (flagsMapping.length > 0) {
final int intValue = (Integer) methodValue;
- final String valuePrefix = prefix + method.getName() + '_';
+ final String valuePrefix =
+ categoryPrefix + prefix + method.getName() + '_';
exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
}
@@ -1371,21 +1417,22 @@ public class ViewDebug {
}
}
} else if (returnType == int[].class) {
- final ExportedProperty property = sAnnotations.get(method);
final int[] array = (int[]) methodValue;
- final String valuePrefix = prefix + method.getName() + '_';
+ final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
final String suffix = "()";
exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
+
+ // Probably want to return here, same as for fields.
+ return;
} else if (!returnType.isPrimitive()) {
- final ExportedProperty property = sAnnotations.get(method);
if (property.deepExport()) {
dumpViewProperties(context, methodValue, out, prefix + property.prefix());
continue;
}
}
- writeEntry(out, prefix, method.getName(), "()", methodValue);
+ writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
@@ -1405,9 +1452,12 @@ public class ViewDebug {
try {
Object fieldValue = null;
final Class<?> type = field.getType();
+ final ExportedProperty property = sAnnotations.get(field);
+ String categoryPrefix =
+ property.category().length() != 0 ? property.category() + ":" : "";
if (type == int.class) {
- final ExportedProperty property = sAnnotations.get(field);
+
if (property.resolveId() && context != null) {
final int id = field.getInt(view);
fieldValue = resolveId(context, id);
@@ -1415,7 +1465,8 @@ public class ViewDebug {
final FlagToString[] flagsMapping = property.flagMapping();
if (flagsMapping.length > 0) {
final int intValue = field.getInt(view);
- final String valuePrefix = prefix + field.getName() + '_';
+ final String valuePrefix =
+ categoryPrefix + prefix + field.getName() + '_';
exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
}
@@ -1437,9 +1488,8 @@ public class ViewDebug {
}
}
} else if (type == int[].class) {
- final ExportedProperty property = sAnnotations.get(field);
final int[] array = (int[]) field.get(view);
- final String valuePrefix = prefix + field.getName() + '_';
+ final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
final String suffix = "";
exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
@@ -1447,10 +1497,9 @@ public class ViewDebug {
// We exit here!
return;
} else if (!type.isPrimitive()) {
- final ExportedProperty property = sAnnotations.get(field);
if (property.deepExport()) {
- dumpViewProperties(context, field.get(view), out,
- prefix + property.prefix());
+ dumpViewProperties(context, field.get(view), out, prefix
+ + property.prefix());
continue;
}
}
@@ -1459,7 +1508,7 @@ public class ViewDebug {
fieldValue = field.get(view);
}
- writeEntry(out, prefix, field.getName(), "", fieldValue);
+ writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
} catch (IllegalAccessException e) {
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index eca583f46b22..28bed3a0d489 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -223,8 +223,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* When set, this ViewGroup should not intercept touch events.
+ * {@hide}
*/
- private static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
+ protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
/**
* Indicates which types of drawing caches are to be kept in memory.
@@ -362,7 +363,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
* {@link #FOCUS_BLOCK_DESCENDANTS}.
*/
- @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.ExportedProperty(category = "focus", mapping = {
@ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"),
@ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"),
@ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS")
@@ -821,6 +822,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (!onFilterTouchEventForSecurity(ev)) {
+ return false;
+ }
+
final int action = ev.getAction();
final float xf = ev.getX();
final float yf = ev.getY();
@@ -2763,7 +2768,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @see #setChildrenDrawnWithCacheEnabled(boolean)
* @see View#setDrawingCacheEnabled(boolean)
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
public boolean isAlwaysDrawnWithCacheEnabled() {
return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE;
}
@@ -2798,7 +2803,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @see #setAlwaysDrawnWithCacheEnabled(boolean)
* @see #setChildrenDrawnWithCacheEnabled(boolean)
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
protected boolean isChildrenDrawnWithCacheEnabled() {
return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE;
}
@@ -2830,7 +2835,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @see #setChildrenDrawingOrderEnabled(boolean)
* @see #getChildDrawingOrder(int, int)
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
protected boolean isChildrenDrawingOrderEnabled() {
return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER;
}
@@ -2867,7 +2872,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
* and {@link #PERSISTENT_ALL_CACHES}
*/
- @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.ExportedProperty(category = "drawing", mapping = {
@ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"),
@ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES, to = "ANIMATION"),
@ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"),
@@ -3500,7 +3505,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* constants FILL_PARENT (replaced by MATCH_PARENT ,
* in API Level 8) or WRAP_CONTENT. or an exact size.
*/
- @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
@@ -3511,7 +3516,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* constants FILL_PARENT (replaced by MATCH_PARENT ,
* in API Level 8) or WRAP_CONTENT. or an exact size.
*/
- @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
@ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
})
@@ -3636,25 +3641,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* The left margin in pixels of the child.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public int leftMargin;
/**
* The top margin in pixels of the child.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public int topMargin;
/**
* The right margin in pixels of the child.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public int rightMargin;
/**
* The bottom margin in pixels of the child.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public int bottomMargin;
/**
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 03efea964c26..57c9055552b0 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -16,8 +16,10 @@
package android.view;
+import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.IInputMethodCallback;
import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.RootViewSurfaceTaker;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
@@ -26,12 +28,12 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.os.*;
import android.os.Process;
-import android.os.SystemProperties;
import android.util.AndroidRuntimeException;
import android.util.Config;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.EventLog;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
@@ -76,6 +78,7 @@ public final class ViewRoot extends Handler implements ViewParent,
/** @noinspection PointlessBooleanExpression*/
private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV;
+ private static final boolean DEBUG_INPUT = true || LOCAL_LOGV;
private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV;
private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV;
private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
@@ -133,6 +136,11 @@ public final class ViewRoot extends Handler implements ViewParent,
int mViewVisibility;
boolean mAppVisible = true;
+ SurfaceHolder.Callback2 mSurfaceHolderCallback;
+ BaseSurfaceHolder mSurfaceHolder;
+ boolean mIsCreating;
+ boolean mDrawingAllowed;
+
final Region mTransparentRegion;
final Region mPreviousTransparentRegion;
@@ -144,7 +152,10 @@ public final class ViewRoot extends Handler implements ViewParent,
CompatibilityInfo.Translator mTranslator;
final View.AttachInfo mAttachInfo;
-
+ InputChannel mInputChannel;
+ InputQueue.Callback mInputQueueCallback;
+ InputQueue mInputQueue;
+
final Rect mTempRect; // used in the transaction to not thrash the heap.
final Rect mVisRect; // used to retrieve visible rect of focused view.
@@ -210,7 +221,7 @@ public final class ViewRoot extends Handler implements ViewParent,
AudioManager mAudioManager;
private final int mDensity;
-
+
public static IWindowSession getWindowSession(Looper mainLooper) {
synchronized (mStaticInit) {
if (!mInitialized) {
@@ -435,6 +446,14 @@ public final class ViewRoot extends Handler implements ViewParent,
mView = view;
mWindowAttributes.copyFrom(attrs);
attrs = mWindowAttributes;
+ if (view instanceof RootViewSurfaceTaker) {
+ mSurfaceHolderCallback =
+ ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
+ if (mSurfaceHolderCallback != null) {
+ mSurfaceHolder = new TakenSurfaceHolder();
+ mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
+ }
+ }
Resources resources = mView.getContext().getResources();
CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
@@ -473,13 +492,16 @@ public final class ViewRoot extends Handler implements ViewParent,
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
+ mInputChannel = new InputChannel();
try {
res = sWindowSession.add(mWindow, mWindowAttributes,
- getHostVisibility(), mAttachInfo.mContentInsets);
+ getHostVisibility(), mAttachInfo.mContentInsets,
+ mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
+ mInputChannel = null;
unscheduleTraversals();
throw new RuntimeException("Adding window failed", e);
} finally {
@@ -487,13 +509,13 @@ public final class ViewRoot extends Handler implements ViewParent,
attrs.restore();
}
}
-
+
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
- if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow);
+ if (Config.LOGV) Log.v(TAG, "Added window " + mWindow);
if (res < WindowManagerImpl.ADD_OKAY) {
mView = null;
mAttachInfo.mRootView = null;
@@ -533,6 +555,19 @@ public final class ViewRoot extends Handler implements ViewParent,
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
+
+ if (view instanceof RootViewSurfaceTaker) {
+ mInputQueueCallback =
+ ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
+ }
+ if (mInputQueueCallback != null) {
+ mInputQueue = new InputQueue(mInputChannel);
+ mInputQueueCallback.onInputQueueCreated(mInputQueue);
+ } else {
+ InputQueue.registerInputChannel(mInputChannel, mInputHandler,
+ Looper.myQueue());
+ }
+
view.assignParent(this);
mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
@@ -682,6 +717,7 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean windowResizesToFitContent = false;
boolean fullRedrawNeeded = mFullRedrawNeeded;
boolean newSurface = false;
+ boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
@@ -700,6 +736,7 @@ public final class ViewRoot extends Handler implements ViewParent,
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
+ surfaceChanged = true;
params = lp;
}
Rect frame = mWinFrame;
@@ -717,7 +754,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// object is not initialized to its backing store, but soon it
// will be (assuming the window is visible).
attachInfo.mSurface = mSurface;
- attachInfo.mTranslucentWindow = lp.format != PixelFormat.OPAQUE;
+ attachInfo.mTranslucentWindow = PixelFormat.formatHasAlpha(lp.format);
attachInfo.mHasWindowFocus = false;
attachInfo.mWindowVisibility = viewVisibility;
attachInfo.mRecomputeGlobalAttributes = false;
@@ -731,7 +768,7 @@ public final class ViewRoot extends Handler implements ViewParent,
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
- if (DEBUG_ORIENTATION) Log.v("ViewRoot",
+ if (DEBUG_ORIENTATION) Log.v(TAG,
"View " + host + " resized to: " + frame);
fullRedrawNeeded = true;
mLayoutRequested = true;
@@ -795,7 +832,7 @@ public final class ViewRoot extends Handler implements ViewParent,
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// Ask host how big it wants to be
- if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v("ViewRoot",
+ if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
@@ -886,11 +923,16 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.mSurfaceLock.lock();
+ mDrawingAllowed = true;
+ }
+
boolean initialized = false;
boolean contentInsetsChanged = false;
boolean visibleInsetsChanged;
+ boolean hadSurface = mSurface.isValid();
try {
- boolean hadSurface = mSurface.isValid();
int fl = 0;
if (params != null) {
fl = params.flags;
@@ -965,8 +1007,9 @@ public final class ViewRoot extends Handler implements ViewParent,
}
} catch (RemoteException e) {
}
+
if (DEBUG_ORIENTATION) Log.v(
- "ViewRoot", "Relayout returned: frame=" + frame + ", surface=" + mSurface);
+ TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
attachInfo.mWindowLeft = frame.left;
attachInfo.mWindowTop = frame.top;
@@ -977,6 +1020,57 @@ public final class ViewRoot extends Handler implements ViewParent,
mWidth = frame.width();
mHeight = frame.height();
+ if (mSurfaceHolder != null) {
+ // The app owns the surface; tell it about what is going on.
+ if (mSurface.isValid()) {
+ // XXX .copyFrom() doesn't work!
+ //mSurfaceHolder.mSurface.copyFrom(mSurface);
+ mSurfaceHolder.mSurface = mSurface;
+ }
+ mSurfaceHolder.mSurfaceLock.unlock();
+ if (mSurface.isValid()) {
+ if (!hadSurface) {
+ mSurfaceHolder.ungetCallbacks();
+
+ mIsCreating = true;
+ mSurfaceHolderCallback.surfaceCreated(mSurfaceHolder);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
+ }
+ surfaceChanged = true;
+ }
+ if (surfaceChanged) {
+ mSurfaceHolderCallback.surfaceChanged(mSurfaceHolder,
+ lp.format, mWidth, mHeight);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, lp.format,
+ mWidth, mHeight);
+ }
+ }
+ }
+ mIsCreating = false;
+ } else if (hadSurface) {
+ mSurfaceHolder.ungetCallbacks();
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ mSurfaceHolderCallback.surfaceDestroyed(mSurfaceHolder);
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceDestroyed(mSurfaceHolder);
+ }
+ }
+ mSurfaceHolder.mSurfaceLock.lock();
+ // Make surface invalid.
+ //mSurfaceHolder.mSurface.copyFrom(mSurface);
+ mSurfaceHolder.mSurface = new Surface();
+ mSurfaceHolder.mSurfaceLock.unlock();
+ }
+ }
+
if (initialized) {
mGlCanvas.setViewport((int) (mWidth * appScale + 0.5f),
(int) (mHeight * appScale + 0.5f));
@@ -1036,7 +1130,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mLayoutRequested = false;
mScrollMayChange = true;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(
- "ViewRoot", "Laying out " + host + " to (" +
+ TAG, "Laying out " + host + " to (" +
host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")");
long startTime = 0L;
if (Config.DEBUG && ViewDebug.profileLayout) {
@@ -1165,9 +1259,21 @@ public final class ViewRoot extends Handler implements ViewParent,
if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0
|| mReportNextDraw) {
if (LOCAL_LOGV) {
- Log.v("ViewRoot", "FINISHED DRAWING: " + mWindowAttributes.getTitle());
+ Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
}
mReportNextDraw = false;
+ if (mSurfaceHolder != null && mSurface.isValid()) {
+ mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder);
+ SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
+ if (callbacks != null) {
+ for (SurfaceHolder.Callback c : callbacks) {
+ if (c instanceof SurfaceHolder.Callback2) {
+ ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
+ mSurfaceHolder);
+ }
+ }
+ }
+ }
try {
sWindowSession.finishDrawing(mWindow);
} catch (RemoteException e) {
@@ -1268,6 +1374,12 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean scalingRequired = mAttachInfo.mScalingRequired;
Rect dirty = mDirty;
+ if (mSurfaceHolder != null) {
+ // The app owns the surface, we won't draw.
+ dirty.setEmpty();
+ return;
+ }
+
if (mUseGL) {
if (!dirty.isEmpty()) {
Canvas canvas = mGlCanvas;
@@ -1324,7 +1436,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
- Log.v("ViewRoot", "Draw " + mView + "/"
+ Log.v(TAG, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
@@ -1332,107 +1444,109 @@ public final class ViewRoot extends Handler implements ViewParent,
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
- Canvas canvas;
- try {
- int left = dirty.left;
- int top = dirty.top;
- int right = dirty.right;
- int bottom = dirty.bottom;
- canvas = surface.lockCanvas(dirty);
-
- if (left != dirty.left || top != dirty.top || right != dirty.right ||
- bottom != dirty.bottom) {
- mAttachInfo.mIgnoreDirtyState = true;
- }
-
- // TODO: Do this in native
- canvas.setDensity(mDensity);
- } catch (Surface.OutOfResourcesException e) {
- Log.e("ViewRoot", "OutOfResourcesException locking surface", e);
- // TODO: we should ask the window manager to do something!
- // for now we just do nothing
- return;
- } catch (IllegalArgumentException e) {
- Log.e("ViewRoot", "IllegalArgumentException locking surface", e);
- // TODO: we should ask the window manager to do something!
- // for now we just do nothing
- return;
- }
+ if (!dirty.isEmpty() || mIsAnimating) {
+ Canvas canvas;
+ try {
+ int left = dirty.left;
+ int top = dirty.top;
+ int right = dirty.right;
+ int bottom = dirty.bottom;
+ canvas = surface.lockCanvas(dirty);
+
+ if (left != dirty.left || top != dirty.top || right != dirty.right ||
+ bottom != dirty.bottom) {
+ mAttachInfo.mIgnoreDirtyState = true;
+ }
- try {
- if (!dirty.isEmpty() || mIsAnimating) {
- long startTime = 0L;
+ // TODO: Do this in native
+ canvas.setDensity(mDensity);
+ } catch (Surface.OutOfResourcesException e) {
+ Log.e(TAG, "OutOfResourcesException locking surface", e);
+ // TODO: we should ask the window manager to do something!
+ // for now we just do nothing
+ return;
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "IllegalArgumentException locking surface", e);
+ // TODO: we should ask the window manager to do something!
+ // for now we just do nothing
+ return;
+ }
- if (DEBUG_ORIENTATION || DEBUG_DRAW) {
- Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w="
- + canvas.getWidth() + ", h=" + canvas.getHeight());
- //canvas.drawARGB(255, 255, 0, 0);
- }
+ try {
+ if (!dirty.isEmpty() || mIsAnimating) {
+ long startTime = 0L;
- if (Config.DEBUG && ViewDebug.profileDrawing) {
- startTime = SystemClock.elapsedRealtime();
- }
+ if (DEBUG_ORIENTATION || DEBUG_DRAW) {
+ Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
+ + canvas.getWidth() + ", h=" + canvas.getHeight());
+ //canvas.drawARGB(255, 255, 0, 0);
+ }
- // If this bitmap's format includes an alpha channel, we
- // need to clear it before drawing so that the child will
- // properly re-composite its drawing on a transparent
- // background. This automatically respects the clip/dirty region
- // or
- // If we are applying an offset, we need to clear the area
- // where the offset doesn't appear to avoid having garbage
- // left in the blank areas.
- if (!canvas.isOpaque() || yoff != 0) {
- canvas.drawColor(0, PorterDuff.Mode.CLEAR);
- }
+ if (Config.DEBUG && ViewDebug.profileDrawing) {
+ startTime = SystemClock.elapsedRealtime();
+ }
- dirty.setEmpty();
- mIsAnimating = false;
- mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
- mView.mPrivateFlags |= View.DRAWN;
+ // If this bitmap's format includes an alpha channel, we
+ // need to clear it before drawing so that the child will
+ // properly re-composite its drawing on a transparent
+ // background. This automatically respects the clip/dirty region
+ // or
+ // If we are applying an offset, we need to clear the area
+ // where the offset doesn't appear to avoid having garbage
+ // left in the blank areas.
+ if (!canvas.isOpaque() || yoff != 0) {
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ }
- if (DEBUG_DRAW) {
- Context cxt = mView.getContext();
- Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
- ", metrics=" + cxt.getResources().getDisplayMetrics() +
- ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
- }
- int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
- try {
- canvas.translate(0, -yoff);
- if (mTranslator != null) {
- mTranslator.translateCanvas(canvas);
+ dirty.setEmpty();
+ mIsAnimating = false;
+ mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
+ mView.mPrivateFlags |= View.DRAWN;
+
+ if (DEBUG_DRAW) {
+ Context cxt = mView.getContext();
+ Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
+ ", metrics=" + cxt.getResources().getDisplayMetrics() +
+ ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
+ }
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ try {
+ canvas.translate(0, -yoff);
+ if (mTranslator != null) {
+ mTranslator.translateCanvas(canvas);
+ }
+ canvas.setScreenDensity(scalingRequired
+ ? DisplayMetrics.DENSITY_DEVICE : 0);
+ mView.draw(canvas);
+ } finally {
+ mAttachInfo.mIgnoreDirtyState = false;
+ canvas.restoreToCount(saveCount);
}
- canvas.setScreenDensity(scalingRequired
- ? DisplayMetrics.DENSITY_DEVICE : 0);
- mView.draw(canvas);
- } finally {
- mAttachInfo.mIgnoreDirtyState = false;
- canvas.restoreToCount(saveCount);
- }
- if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
- mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
- }
+ if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
+ mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
+ }
- if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
- int now = (int)SystemClock.elapsedRealtime();
- if (sDrawTime != 0) {
- nativeShowFPS(canvas, now - sDrawTime);
+ if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) {
+ int now = (int)SystemClock.elapsedRealtime();
+ if (sDrawTime != 0) {
+ nativeShowFPS(canvas, now - sDrawTime);
+ }
+ sDrawTime = now;
}
- sDrawTime = now;
- }
- if (Config.DEBUG && ViewDebug.profileDrawing) {
- EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+ if (Config.DEBUG && ViewDebug.profileDrawing) {
+ EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
+ }
}
- }
- } finally {
- surface.unlockCanvasAndPost(canvas);
+ } finally {
+ surface.unlockCanvasAndPost(canvas);
+ }
}
if (LOCAL_LOGV) {
- Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
+ Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
}
if (scrolling) {
@@ -1624,7 +1738,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
void dispatchDetachedFromWindow() {
- if (Config.LOGV) Log.v("ViewRoot", "Detaching in " + this + " of " + mSurface);
+ if (Config.LOGV) Log.v(TAG, "Detaching in " + this + " of " + mSurface);
if (mView != null) {
mView.dispatchDetachedFromWindow();
@@ -1639,10 +1753,26 @@ public final class ViewRoot extends Handler implements ViewParent,
}
mSurface.release();
+ if (mInputChannel != null) {
+ if (mInputQueueCallback != null) {
+ mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
+ mInputQueueCallback = null;
+ } else {
+ InputQueue.unregisterInputChannel(mInputChannel);
+ }
+ }
+
try {
sWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
+
+ // Dispose the input channel after removing the window so the Window Manager
+ // doesn't interpret the input channel being closed as an abnormal termination.
+ if (mInputChannel != null) {
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
}
void updateConfiguration(Configuration config, boolean force) {
@@ -1736,118 +1866,27 @@ public final class ViewRoot extends Handler implements ViewParent,
break;
case DISPATCH_KEY:
if (LOCAL_LOGV) Log.v(
- "ViewRoot", "Dispatching key "
+ TAG, "Dispatching key "
+ msg.obj + " to " + mView);
- deliverKeyEvent((KeyEvent)msg.obj, true);
+ deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0);
break;
case DISPATCH_POINTER: {
- MotionEvent event = (MotionEvent)msg.obj;
- boolean callWhenDone = msg.arg1 != 0;
-
- if (event == null) {
- try {
- long timeBeforeGettingEvents;
- if (MEASURE_LATENCY) {
- timeBeforeGettingEvents = System.nanoTime();
- }
-
- event = sWindowSession.getPendingPointerMove(mWindow);
-
- if (MEASURE_LATENCY && event != null) {
- lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano());
- lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano());
- }
- } catch (RemoteException e) {
- }
- callWhenDone = false;
- }
- if (event != null && mTranslator != null) {
- mTranslator.translateEventInScreenToAppWindow(event);
- }
+ MotionEvent event = (MotionEvent) msg.obj;
try {
- boolean handled;
- if (mView != null && mAdded && event != null) {
-
- // enter touch mode on the down
- boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
- if (isDown) {
- ensureTouchMode(true);
- }
- if(Config.LOGV) {
- captureMotionLog("captureDispatchPointer", event);
- }
- if (mCurScrollY != 0) {
- event.offsetLocation(0, mCurScrollY);
- }
- if (MEASURE_LATENCY) {
- lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
- }
- handled = mView.dispatchTouchEvent(event);
- if (MEASURE_LATENCY) {
- lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
- }
- if (!handled && isDown) {
- int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
-
- final int edgeFlags = event.getEdgeFlags();
- int direction = View.FOCUS_UP;
- int x = (int)event.getX();
- int y = (int)event.getY();
- final int[] deltas = new int[2];
-
- if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) {
- direction = View.FOCUS_DOWN;
- if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
- deltas[0] = edgeSlop;
- x += edgeSlop;
- } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
- deltas[0] = -edgeSlop;
- x -= edgeSlop;
- }
- } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) {
- direction = View.FOCUS_UP;
- if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
- deltas[0] = edgeSlop;
- x += edgeSlop;
- } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
- deltas[0] = -edgeSlop;
- x -= edgeSlop;
- }
- } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
- direction = View.FOCUS_RIGHT;
- } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
- direction = View.FOCUS_LEFT;
- }
-
- if (edgeFlags != 0 && mView instanceof ViewGroup) {
- View nearest = FocusFinder.getInstance().findNearestTouchable(
- ((ViewGroup) mView), x, y, direction, deltas);
- if (nearest != null) {
- event.offsetLocation(deltas[0], deltas[1]);
- event.setEdgeFlags(0);
- mView.dispatchTouchEvent(event);
- }
- }
- }
- }
+ deliverPointerEvent(event);
} finally {
- if (callWhenDone) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
- }
- if (event != null) {
- event.recycle();
- }
+ event.recycle();
if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
- // Let the exception fall through -- the looper will catch
- // it and take care of the bad app for us.
}
} break;
- case DISPATCH_TRACKBALL:
- deliverTrackballEvent((MotionEvent)msg.obj, msg.arg1 != 0);
- break;
+ case DISPATCH_TRACKBALL: {
+ MotionEvent event = (MotionEvent) msg.obj;
+ try {
+ deliverTrackballEvent(event);
+ } finally {
+ event.recycle();
+ }
+ } break;
case DISPATCH_APP_VISIBILITY:
handleAppVisibility(msg.arg1 != 0);
break;
@@ -1949,7 +1988,7 @@ public final class ViewRoot extends Handler implements ViewParent,
break;
case DISPATCH_KEY_FROM_IME: {
if (LOCAL_LOGV) Log.v(
- "ViewRoot", "Dispatching key "
+ TAG, "Dispatching key "
+ msg.obj + " from IME to " + mView);
KeyEvent event = (KeyEvent)msg.obj;
if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) {
@@ -1979,7 +2018,19 @@ public final class ViewRoot extends Handler implements ViewParent,
} break;
}
}
+
+ private void finishKeyEvent(KeyEvent event) {
+ if (LOCAL_LOGV) Log.v(TAG, "Telling window manager key is finished");
+ if (mFinishedCallback != null) {
+ mFinishedCallback.run();
+ mFinishedCallback = null;
+ } else {
+ Slog.w(TAG, "Attempted to tell the input queue that the current key event "
+ + "is finished but there is no key event actually in progress.");
+ }
+ }
+
/**
* Something in the current window tells us we need to change the touch mode. For
* example, we are not in touch mode, and the user touches the screen.
@@ -2101,50 +2152,95 @@ public final class ViewRoot extends Handler implements ViewParent,
return false;
}
+ private void deliverPointerEvent(MotionEvent event) {
+ if (mTranslator != null) {
+ mTranslator.translateEventInScreenToAppWindow(event);
+ }
+
+ boolean handled;
+ if (mView != null && mAdded) {
- private void deliverTrackballEvent(MotionEvent event, boolean callWhenDone) {
- if (event == null) {
- try {
- event = sWindowSession.getPendingTrackballMove(mWindow);
- } catch (RemoteException e) {
+ // enter touch mode on the down
+ boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
+ if (isDown) {
+ ensureTouchMode(true);
+ }
+ if(Config.LOGV) {
+ captureMotionLog("captureDispatchPointer", event);
+ }
+ if (mCurScrollY != 0) {
+ event.offsetLocation(0, mCurScrollY);
+ }
+ if (MEASURE_LATENCY) {
+ lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
+ }
+ handled = mView.dispatchTouchEvent(event);
+ if (MEASURE_LATENCY) {
+ lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano());
+ }
+ if (!handled && isDown) {
+ int edgeSlop = mViewConfiguration.getScaledEdgeSlop();
+
+ final int edgeFlags = event.getEdgeFlags();
+ int direction = View.FOCUS_UP;
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ final int[] deltas = new int[2];
+
+ if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) {
+ direction = View.FOCUS_DOWN;
+ if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+ deltas[0] = edgeSlop;
+ x += edgeSlop;
+ } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+ deltas[0] = -edgeSlop;
+ x -= edgeSlop;
+ }
+ } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) {
+ direction = View.FOCUS_UP;
+ if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+ deltas[0] = edgeSlop;
+ x += edgeSlop;
+ } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+ deltas[0] = -edgeSlop;
+ x -= edgeSlop;
+ }
+ } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) {
+ direction = View.FOCUS_RIGHT;
+ } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) {
+ direction = View.FOCUS_LEFT;
+ }
+
+ if (edgeFlags != 0 && mView instanceof ViewGroup) {
+ View nearest = FocusFinder.getInstance().findNearestTouchable(
+ ((ViewGroup) mView), x, y, direction, deltas);
+ if (nearest != null) {
+ event.offsetLocation(deltas[0], deltas[1]);
+ event.setEdgeFlags(0);
+ mView.dispatchTouchEvent(event);
+ }
+ }
}
- callWhenDone = false;
}
+ }
+ private void deliverTrackballEvent(MotionEvent event) {
if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
boolean handled = false;
- try {
- if (event == null) {
- handled = true;
- } else if (mView != null && mAdded) {
- handled = mView.dispatchTrackballEvent(event);
- if (!handled) {
- // we could do something here, like changing the focus
- // or something?
- }
- }
- } finally {
+ if (mView != null && mAdded) {
+ handled = mView.dispatchTrackballEvent(event);
if (handled) {
- if (callWhenDone) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
- }
- if (event != null) {
- event.recycle();
- }
// If we reach this, we delivered a trackball event to mView and
// mView consumed it. Because we will not translate the trackball
// event into a key event, touch mode will not exit, so we exit
// touch mode here.
ensureTouchMode(false);
- //noinspection ReturnInsideFinallyBlock
return;
}
- // Let the exception fall through -- the looper will catch
- // it and take care of the bad app for us.
+
+ // Otherwise we could do something here, like changing the focus
+ // or something?
}
final TrackballAxis x = mTrackballAxisX;
@@ -2159,100 +2255,86 @@ public final class ViewRoot extends Handler implements ViewParent,
mLastTrackballTime = curTime;
}
- try {
- final int action = event.getAction();
- final int metastate = event.getMetaState();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- x.reset(2);
- y.reset(2);
- deliverKeyEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER,
- 0, metastate), false);
- break;
- case MotionEvent.ACTION_UP:
- x.reset(2);
- y.reset(2);
- deliverKeyEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER,
- 0, metastate), false);
- break;
- }
-
- if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step="
- + x.step + " dir=" + x.dir + " acc=" + x.acceleration
- + " move=" + event.getX()
- + " / Y=" + y.position + " step="
- + y.step + " dir=" + y.dir + " acc=" + y.acceleration
- + " move=" + event.getY());
- final float xOff = x.collect(event.getX(), event.getEventTime(), "X");
- final float yOff = y.collect(event.getY(), event.getEventTime(), "Y");
-
- // Generate DPAD events based on the trackball movement.
- // We pick the axis that has moved the most as the direction of
- // the DPAD. When we generate DPAD events for one axis, then the
- // other axis is reset -- we don't want to perform DPAD jumps due
- // to slight movements in the trackball when making major movements
- // along the other axis.
- int keycode = 0;
- int movement = 0;
- float accel = 1;
- if (xOff > yOff) {
- movement = x.generate((2/event.getXPrecision()));
- if (movement != 0) {
- keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
- : KeyEvent.KEYCODE_DPAD_LEFT;
- accel = x.acceleration;
- y.reset(2);
- }
- } else if (yOff > 0) {
- movement = y.generate((2/event.getYPrecision()));
- if (movement != 0) {
- keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
- : KeyEvent.KEYCODE_DPAD_UP;
- accel = y.acceleration;
- x.reset(2);
- }
- }
-
- if (keycode != 0) {
- if (movement < 0) movement = -movement;
- int accelMovement = (int)(movement * accel);
- if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement
- + " accelMovement=" + accelMovement
- + " accel=" + accel);
- if (accelMovement > movement) {
- if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
- + keycode);
- movement--;
- deliverKeyEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_MULTIPLE, keycode,
- accelMovement-movement, metastate), false);
- }
- while (movement > 0) {
- if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
- + keycode);
- movement--;
- curTime = SystemClock.uptimeMillis();
- deliverKeyEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_DOWN, keycode, 0, event.getMetaState()), false);
- deliverKeyEvent(new KeyEvent(curTime, curTime,
- KeyEvent.ACTION_UP, keycode, 0, metastate), false);
- }
- mLastTrackballTime = curTime;
- }
- } finally {
- if (callWhenDone) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
- if (event != null) {
- event.recycle();
- }
+ final int action = event.getAction();
+ final int metastate = event.getMetaState();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ x.reset(2);
+ y.reset(2);
+ deliverKeyEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER,
+ 0, metastate), false);
+ break;
+ case MotionEvent.ACTION_UP:
+ x.reset(2);
+ y.reset(2);
+ deliverKeyEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER,
+ 0, metastate), false);
+ break;
+ }
+
+ if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step="
+ + x.step + " dir=" + x.dir + " acc=" + x.acceleration
+ + " move=" + event.getX()
+ + " / Y=" + y.position + " step="
+ + y.step + " dir=" + y.dir + " acc=" + y.acceleration
+ + " move=" + event.getY());
+ final float xOff = x.collect(event.getX(), event.getEventTime(), "X");
+ final float yOff = y.collect(event.getY(), event.getEventTime(), "Y");
+
+ // Generate DPAD events based on the trackball movement.
+ // We pick the axis that has moved the most as the direction of
+ // the DPAD. When we generate DPAD events for one axis, then the
+ // other axis is reset -- we don't want to perform DPAD jumps due
+ // to slight movements in the trackball when making major movements
+ // along the other axis.
+ int keycode = 0;
+ int movement = 0;
+ float accel = 1;
+ if (xOff > yOff) {
+ movement = x.generate((2/event.getXPrecision()));
+ if (movement != 0) {
+ keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
+ : KeyEvent.KEYCODE_DPAD_LEFT;
+ accel = x.acceleration;
+ y.reset(2);
+ }
+ } else if (yOff > 0) {
+ movement = y.generate((2/event.getYPrecision()));
+ if (movement != 0) {
+ keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
+ : KeyEvent.KEYCODE_DPAD_UP;
+ accel = y.acceleration;
+ x.reset(2);
+ }
+ }
+
+ if (keycode != 0) {
+ if (movement < 0) movement = -movement;
+ int accelMovement = (int)(movement * accel);
+ if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement
+ + " accelMovement=" + accelMovement
+ + " accel=" + accel);
+ if (accelMovement > movement) {
+ if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
+ + keycode);
+ movement--;
+ deliverKeyEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_MULTIPLE, keycode,
+ accelMovement-movement, metastate), false);
+ }
+ while (movement > 0) {
+ if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: "
+ + keycode);
+ movement--;
+ curTime = SystemClock.uptimeMillis();
+ deliverKeyEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_DOWN, keycode, 0, event.getMetaState()), false);
+ deliverKeyEvent(new KeyEvent(curTime, curTime,
+ KeyEvent.ACTION_UP, keycode, 0, metastate), false);
}
- // Let the exception fall through -- the looper will catch
- // it and take care of the bad app for us.
+ mLastTrackballTime = curTime;
}
}
@@ -2405,12 +2487,7 @@ public final class ViewRoot extends Handler implements ViewParent,
? mView.dispatchKeyEventPreIme(event) : true;
if (handled) {
if (sendDone) {
- if (LOCAL_LOGV) Log.v(
- "ViewRoot", "Telling window manager key is finished");
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishKeyEvent(event);
}
return;
}
@@ -2441,14 +2518,9 @@ public final class ViewRoot extends Handler implements ViewParent,
deliverKeyEventToViewHierarchy(event, sendDone);
return;
} else if (sendDone) {
- if (LOCAL_LOGV) Log.v(
- "ViewRoot", "Telling window manager key is finished");
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishKeyEvent(event);
} else {
- Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq
+ Log.w(TAG, "handleFinishedEvent(seq=" + seq
+ " handled=" + handled + " ev=" + event
+ ") neither delivering nor finishing key");
}
@@ -2519,12 +2591,7 @@ public final class ViewRoot extends Handler implements ViewParent,
} finally {
if (sendDone) {
- if (LOCAL_LOGV) Log.v(
- "ViewRoot", "Telling window manager key is finished");
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
+ finishKeyEvent(event);
}
// Let the exception fall through -- the looper will catch
// it and take care of the bad app for us.
@@ -2646,7 +2713,7 @@ public final class ViewRoot extends Handler implements ViewParent,
void doDie() {
checkThread();
- if (Config.LOGV) Log.v("ViewRoot", "DIE in " + this + " of " + mSurface);
+ if (Config.LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mAdded && !mFirst) {
int viewVisibility = mView.getVisibility();
@@ -2702,11 +2769,36 @@ public final class ViewRoot extends Handler implements ViewParent,
msg.obj = ri;
sendMessage(msg);
}
+
+ private Runnable mFinishedCallback;
+
+ private final InputHandler mInputHandler = new InputHandler() {
+ public void handleKey(KeyEvent event, Runnable finishedCallback) {
+ if (mFinishedCallback != null) {
+ Slog.w(TAG, "Received a new key event from the input queue but there is "
+ + "already an unfinished key event in progress.");
+ }
+
+ mFinishedCallback = finishedCallback;
+
+ dispatchKey(event, true);
+ }
+
+ public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+ finishedCallback.run();
+
+ dispatchMotion(event);
+ }
+ };
public void dispatchKey(KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- //noinspection ConstantConditions
- if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
+ dispatchKey(event, false);
+ }
+
+ private void dispatchKey(KeyEvent event, boolean sendDone) {
+ //noinspection ConstantConditions
+ if (false && event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
if (Config.LOGD) Log.d("keydisp",
"===================================================");
if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:");
@@ -2719,29 +2811,38 @@ public final class ViewRoot extends Handler implements ViewParent,
Message msg = obtainMessage(DISPATCH_KEY);
msg.obj = event;
+ msg.arg1 = sendDone ? 1 : 0;
if (LOCAL_LOGV) Log.v(
- "ViewRoot", "sending key " + event + " to " + mView);
+ TAG, "sending key " + event + " to " + mView);
sendMessageAtTime(msg, event.getEventTime());
}
+
+ public void dispatchMotion(MotionEvent event) {
+ int source = event.getSource();
+ if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+ dispatchPointer(event);
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ dispatchTrackball(event);
+ } else {
+ // TODO
+ Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event);
+ }
+ }
- public void dispatchPointer(MotionEvent event, long eventTime,
- boolean callWhenDone) {
+ public void dispatchPointer(MotionEvent event) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
- msg.arg1 = callWhenDone ? 1 : 0;
- sendMessageAtTime(msg, eventTime);
+ sendMessageAtTime(msg, event.getEventTime());
}
- public void dispatchTrackball(MotionEvent event, long eventTime,
- boolean callWhenDone) {
+ public void dispatchTrackball(MotionEvent event) {
Message msg = obtainMessage(DISPATCH_TRACKBALL);
msg.obj = event;
- msg.arg1 = callWhenDone ? 1 : 0;
- sendMessageAtTime(msg, eventTime);
+ sendMessageAtTime(msg, event.getEventTime());
}
-
+
public void dispatchAppVisibility(boolean visible) {
Message msg = obtainMessage(DISPATCH_APP_VISIBILITY);
msg.arg1 = visible ? 1 : 0;
@@ -2813,6 +2914,46 @@ public final class ViewRoot extends Handler implements ViewParent,
return scrollToRectOrFocus(rectangle, immediate);
}
+ class TakenSurfaceHolder extends BaseSurfaceHolder {
+ @Override
+ public boolean onAllowLockCanvas() {
+ return mDrawingAllowed;
+ }
+
+ @Override
+ public void onRelayoutContainer() {
+ // Not currently interesting -- from changing between fixed and layout size.
+ }
+
+ public void setFormat(int format) {
+ ((RootViewSurfaceTaker)mView).setSurfaceFormat(format);
+ }
+
+ public void setType(int type) {
+ ((RootViewSurfaceTaker)mView).setSurfaceType(type);
+ }
+
+ @Override
+ public void onUpdateSurface() {
+ // We take care of format and type changes on our own.
+ throw new IllegalStateException("Shouldn't be here");
+ }
+
+ public boolean isCreating() {
+ return mIsCreating;
+ }
+
+ @Override
+ public void setFixedSize(int width, int height) {
+ throw new UnsupportedOperationException(
+ "Currently only support sizing from layout");
+ }
+
+ public void setKeepScreenOn(boolean screenOn) {
+ ((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn);
+ }
+ }
+
static class InputMethodCallback extends IInputMethodCallback.Stub {
private WeakReference<ViewRoot> mViewRoot;
@@ -2832,71 +2973,11 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
- static class EventCompletion extends Handler {
- final IWindow mWindow;
- final KeyEvent mKeyEvent;
- final boolean mIsPointer;
- final MotionEvent mMotionEvent;
-
- EventCompletion(Looper looper, IWindow window, KeyEvent key,
- boolean isPointer, MotionEvent motion) {
- super(looper);
- mWindow = window;
- mKeyEvent = key;
- mIsPointer = isPointer;
- mMotionEvent = motion;
- sendEmptyMessage(0);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (mKeyEvent != null) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
- } else if (mIsPointer) {
- boolean didFinish;
- MotionEvent event = mMotionEvent;
- if (event == null) {
- try {
- event = sWindowSession.getPendingPointerMove(mWindow);
- } catch (RemoteException e) {
- }
- didFinish = true;
- } else {
- didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE;
- }
- if (!didFinish) {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
- }
- } else {
- MotionEvent event = mMotionEvent;
- if (event == null) {
- try {
- event = sWindowSession.getPendingTrackballMove(mWindow);
- } catch (RemoteException e) {
- }
- } else {
- try {
- sWindowSession.finishKey(mWindow);
- } catch (RemoteException e) {
- }
- }
- }
- }
- }
-
static class W extends IWindow.Stub {
private final WeakReference<ViewRoot> mViewRoot;
- private final Looper mMainLooper;
public W(ViewRoot viewRoot, Context context) {
mViewRoot = new WeakReference<ViewRoot>(viewRoot);
- mMainLooper = context.getMainLooper();
}
public void resized(int w, int h, Rect coveredInsets,
@@ -2908,40 +2989,6 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
- public void dispatchKey(KeyEvent event) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchKey(event);
- } else {
- Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!");
- new EventCompletion(mMainLooper, this, event, false, null);
- }
- }
-
- public void dispatchPointer(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- if (MEASURE_LATENCY) {
- // Note: eventTime is in milliseconds
- ViewRoot.lt.sample("* ViewRoot b4 dispatchPtr", System.nanoTime() - eventTime * 1000000);
- }
- viewRoot.dispatchPointer(event, eventTime, callWhenDone);
- } else {
- new EventCompletion(mMainLooper, this, null, true, event);
- }
- }
-
- public void dispatchTrackball(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- final ViewRoot viewRoot = mViewRoot.get();
- if (viewRoot != null) {
- viewRoot.dispatchTrackball(event, eventTime, callWhenDone);
- } else {
- new EventCompletion(mMainLooper, this, null, false, event);
- }
- }
-
public void dispatchAppVisibility(boolean visible) {
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7dd5085f1075..11c09c1b2d45 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -473,6 +473,21 @@ public abstract class Window {
}
/**
+ * Take ownership of this window's surface. The window's view hierarchy
+ * will no longer draw into the surface, though it will otherwise continue
+ * to operate (such as for receiving input events). The given SurfaceHolder
+ * callback will be used to tell you about state changes to the surface.
+ */
+ public abstract void takeSurface(SurfaceHolder.Callback2 callback);
+
+ /**
+ * Take ownership of this window's InputQueue. The window will no
+ * longer read and dispatch input events from the queue; it is your
+ * responsibility to do so.
+ */
+ public abstract void takeInputQueue(InputQueue.Callback callback);
+
+ /**
* Return whether this window is being displayed with a floating style
* (based on the {@link android.R.attr#windowIsFloating} attribute in
* the style/theme).
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index adceeb2074c2..eebbc931d0a3 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -580,9 +580,10 @@ public interface WindowManager extends ViewManager {
* If the keyguard is currently active and is secure (requires an
* unlock pattern) than the user will still need to confirm it before
* seeing this window, unless {@link #FLAG_SHOW_WHEN_LOCKED} has
- * also been set. */
+ * also been set.
+ */
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
-
+
/** Window flag: *sigh* The lock screen wants to continue running its
* animation while it is fading. A kind-of hack to allow this. Maybe
* in the future we just make this the default behavior.
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index b39cb9d903e5..76701a937886 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -74,6 +74,8 @@ public interface WindowManagerPolicy {
public final static int FLAG_MENU = 0x00000040;
public final static int FLAG_LAUNCHER = 0x00000080;
+ public final static int FLAG_INJECTED = 0x01000000;
+
public final static int FLAG_WOKE_HERE = 0x10000000;
public final static int FLAG_BRIGHT_HERE = 0x20000000;
@@ -549,23 +551,26 @@ public interface WindowManagerPolicy {
public Animation createForceHideEnterAnimation();
/**
- * Called from the key queue thread before a key is dispatched to the
- * input thread.
+ * Called from the input reader thread before a key is enqueued.
*
* <p>There are some actions that need to be handled here because they
* affect the power state of the device, for example, the power keys.
* Generally, it's best to keep as little as possible in the queue thread
* because it's the most fragile.
+ * @param whenNanos The event time in uptime nanoseconds.
+ * @param keyCode The key code.
+ * @param down True if the key is down.
+ * @param policyFlags The policy flags associated with the key.
+ * @param isScreenOn True if the screen is already on
*
- * @param event the raw input event as read from the driver
- * @param screenIsOn true if the screen is already on
* @return The bitwise or of the {@link #ACTION_PASS_TO_USER},
* {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags.
*/
- public int interceptKeyTq(RawInputEvent event, boolean screenIsOn);
+ public int interceptKeyBeforeQueueing(long whenNanos, int keyCode, boolean down, int policyFlags,
+ boolean isScreenOn);
/**
- * Called from the input thread before a key is dispatched to a window.
+ * Called from the input dispatcher thread before a key is dispatched to a window.
*
* <p>Allows you to define
* behavior for keys that can not be overridden by applications or redirect
@@ -577,16 +582,17 @@ public interface WindowManagerPolicy {
*
* @param win The window that currently has focus. This is where the key
* event will normally go.
- * @param code Key code.
- * @param metaKeys bit mask of meta keys that are held.
- * @param down Is this a key press (true) or release (false)?
+ * @param action The key event action.
+ * @param flags The key event flags.
+ * @param keyCode The key code.
+ * @param metaState bit mask of meta keys that are held.
* @param repeatCount Number of times a key down has repeated.
- * @param flags event's flags.
+ * @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.
*/
- public boolean interceptKeyTi(WindowState win, int code,
- int metaKeys, boolean down, int repeatCount, int flags);
+ public boolean interceptKeyBeforeDispatching(WindowState win, int action, int flags,
+ int keyCode, int metaState, int repeatCount, int policyFlags);
/**
* Called when layout of the windows is about to start.
@@ -695,81 +701,13 @@ public interface WindowManagerPolicy {
* Return whether the screen is currently on.
*/
public boolean isScreenOn();
-
- /**
- * Perform any initial processing of a low-level input event before the
- * window manager handles special keys and generates a high-level event
- * that is dispatched to the application.
- *
- * @param event The input event that has occurred.
- *
- * @return Return true if you have consumed the event and do not want
- * further processing to occur; return false for normal processing.
- */
- public boolean preprocessInputEventTq(RawInputEvent event);
-
- /**
- * Determine whether a given key code is used to cause an app switch
- * to occur (most often the HOME key, also often ENDCALL). If you return
- * true, then the system will go into a special key processing state
- * where it drops any pending events that it cans and adjusts timeouts to
- * try to get to this key as quickly as possible.
- *
- * <p>Note that this function is called from the low-level input queue
- * thread, with either/or the window or input lock held; be very careful
- * about what you do here. You absolutely should never acquire a lock
- * that you would ever hold elsewhere while calling out into the window
- * manager or view hierarchy.
- *
- * @param keycode The key that should be checked for performing an
- * app switch before delivering to the application.
- *
- * @return Return true if this is an app switch key and special processing
- * should happen; return false for normal processing.
- */
- public boolean isAppSwitchKeyTqTiLwLi(int keycode);
-
- /**
- * Determine whether a given key code is used for movement within a UI,
- * and does not generally cause actions to be performed (normally the DPAD
- * movement keys, NOT the DPAD center press key). This is called
- * when {@link #isAppSwitchKeyTiLi} returns true to remove any pending events
- * in the key queue that are not needed to switch applications.
- *
- * <p>Note that this function is called from the low-level input queue
- * thread; be very careful about what you do here.
- *
- * @param keycode The key that is waiting to be delivered to the
- * application.
- *
- * @return Return true if this is a purely navigation key and can be
- * dropped without negative consequences; return false to keep it.
- */
- public boolean isMovementKeyTi(int keycode);
-
- /**
- * Given the current state of the world, should this relative movement
- * wake up the device?
- *
- * @param device The device the movement came from.
- * @param classes The input classes associated with the device.
- * @param event The input event that occurred.
- * @return
- */
- public boolean isWakeRelMovementTq(int device, int classes,
- RawInputEvent event);
-
+
/**
- * Given the current state of the world, should this absolute movement
- * wake up the device?
- *
- * @param device The device the movement came from.
- * @param classes The input classes associated with the device.
- * @param event The input event that occurred.
- * @return
+ * Tell the policy that the lid switch has changed state.
+ * @param whenNanos The time when the change occurred in uptime nanoseconds.
+ * @param lidOpen True if the lid is now open.
*/
- public boolean isWakeAbsMovementTq(int device, int classes,
- RawInputEvent event);
+ public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
/**
* Tell the policy if anyone is requesting that keyguard not come on.
@@ -843,23 +781,6 @@ public interface WindowManagerPolicy {
*/
public void enableScreenAfterBoot();
- /**
- * Returns true if the user's cheek has been pressed against the phone. This is
- * determined by comparing the event's size attribute with a threshold value.
- * For example for a motion event like down or up or move, if the size exceeds
- * the threshold, it is considered as cheek press.
- * @param ev the motion event generated when the cheek is pressed
- * against the phone
- * @return Returns true if the user's cheek has been pressed against the phone
- * screen resulting in an invalid motion event
- */
- public boolean isCheekPressedAgainstScreen(MotionEvent ev);
-
- /**
- * Called every time the window manager is dispatching a pointer event.
- */
- public void dispatchedPointerEventLw(MotionEvent ev, int targetX, int targetY);
-
public void setCurrentOrientationLw(int newOrientation);
/**
@@ -868,13 +789,6 @@ public interface WindowManagerPolicy {
public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always);
/**
- * A special function that is called from the very low-level input queue
- * to provide feedback to the user. Currently only called for virtual
- * keys.
- */
- public void keyFeedbackFromInput(KeyEvent event);
-
- /**
* Called when we have stopped keeping the screen on because a window
* requesting this is no longer visible.
*/
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 25df1f41b18d..55d11bb19a95 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -68,7 +68,7 @@ public abstract class WindowOrientationListener {
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
// Create listener only if sensors do exist
- mSensorEventListener = new SensorEventListenerImpl();
+ mSensorEventListener = new SensorEventListenerImpl(this);
}
}
@@ -103,14 +103,41 @@ public abstract class WindowOrientationListener {
}
}
- public int getCurrentRotation() {
+ public int getCurrentRotation(int lastRotation) {
if (mEnabled) {
- return mSensorEventListener.getCurrentRotation();
+ return mSensorEventListener.getCurrentRotation(lastRotation);
}
- return -1;
+ return lastRotation;
}
-
- class SensorEventListenerImpl implements SensorEventListener {
+
+ /**
+ * This class filters the raw accelerometer data and tries to detect actual changes in
+ * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
+ * but here's the outline:
+ *
+ * - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're
+ * dealing with rotation of the device, this is the sensible coordinate system to work in. The
+ * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance
+ * is referred to as the magnitude below. The elevation angle is referred to as the "tilt"
+ * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
+ * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
+ *
+ * - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior.
+ *
+ * - When the orientation angle reaches a certain threshold, transition to the corresponding
+ * orientation. These thresholds have some hysteresis built-in to avoid oscillation.
+ *
+ * - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude
+ * should equal to that of gravity. When it differs significantly, we know the device is under
+ * external acceleration and we can't trust the data.
+ *
+ * - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high
+ * in magnitude, we distrust the orientation data, because when the device is nearly flat, small
+ * physical movements produce large changes in orientation angle.
+ *
+ * Details are explained below.
+ */
+ static class SensorEventListenerImpl implements SensorEventListener {
// We work with all angles in degrees in this class.
private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
@@ -125,54 +152,56 @@ public abstract class WindowOrientationListener {
private static final int ROTATION_90 = 1;
private static final int ROTATION_270 = 2;
- // Current orientation state
- private int mRotation = ROTATION_0;
-
// Mapping our internal aliases into actual Surface rotation values
- private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90,
- Surface.ROTATION_270};
+ private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] {
+ Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270};
+
+ // Mapping Surface rotation values to internal aliases.
+ // We have no constant for Surface.ROTATION_180. That should never happen, but if it
+ // does, we'll arbitrarily choose a mapping.
+ private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] {
+ ROTATION_0, ROTATION_90, ROTATION_90, ROTATION_270};
// Threshold ranges of orientation angle to transition into other orientation states.
// The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc.
// ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
// in sync with this.
- // The thresholds are nearly regular -- we generally transition about the halfway point
- // between two states with a swing of 30 degrees for hysteresis. For ROTATION_180,
- // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180.
- private final int[][][] THRESHOLDS = new int[][][] {
+ // We generally transition about the halfway point between two states with a swing of 30
+ // degrees for hysteresis.
+ private static final int[][][] THRESHOLDS = new int[][][] {
{{60, 180}, {180, 300}},
+ {{0, 30}, {195, 315}, {315, 360}},
{{0, 45}, {45, 165}, {330, 360}},
- {{0, 30}, {195, 315}, {315, 360}}
};
// See THRESHOLDS
- private final int[][] ROTATE_TO = new int[][] {
- {ROTATION_270, ROTATION_90},
+ private static final int[][] ROTATE_TO = new int[][] {
+ {ROTATION_90, ROTATION_270},
{ROTATION_0, ROTATION_270, ROTATION_0},
- {ROTATION_0, ROTATION_90, ROTATION_0}
+ {ROTATION_0, ROTATION_90, ROTATION_0},
};
- // Maximum absolute tilt angle at which to consider orientation changes. Beyond this (i.e.
- // when screen is facing the sky or ground), we refuse to make any orientation changes.
- private static final int MAX_TILT = 65;
+ // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
+ // when screen is facing the sky or ground), we completely ignore orientation data.
+ private static final int MAX_TILT = 75;
// Additional limits on tilt angle to transition to each new orientation. We ignore all
- // vectors with tilt beyond MAX_TILT, but we can set stricter limits on transition to a
+ // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
// particular orientation here.
- private final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, MAX_TILT, MAX_TILT};
+ private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65};
// Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
// with a higher time constant, making us less sensitive to change. This primarily helps
// prevent momentary orientation changes when placing a device on a table from the side (or
// picking one up).
- private static final int PARTIAL_TILT = 45;
+ private static final int PARTIAL_TILT = 50;
// Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
// in m/s^2. Beyond this, we assume the phone is under external forces and we can't trust
// the sensor data. However, under constantly vibrating conditions (think car mount), we
// still want to pick up changes, so rather than ignore the data, we filter it with a very
// high time constant.
- private static final int MAX_DEVIATION_FROM_GRAVITY = 1;
+ private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f;
// Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no
// way to get this information from SensorManager.
@@ -185,28 +214,50 @@ public abstract class WindowOrientationListener {
// background.
// When device is near-vertical (screen approximately facing the horizon)
- private static final int DEFAULT_TIME_CONSTANT_MS = 200;
+ private static final int DEFAULT_TIME_CONSTANT_MS = 50;
// When device is partially tilted towards the sky or ground
- private static final int TILTED_TIME_CONSTANT_MS = 600;
+ private static final int TILTED_TIME_CONSTANT_MS = 300;
// When device is under external acceleration, i.e. not just gravity. We heavily distrust
// such readings.
- private static final int ACCELERATING_TIME_CONSTANT_MS = 5000;
+ private static final int ACCELERATING_TIME_CONSTANT_MS = 2000;
private static final float DEFAULT_LOWPASS_ALPHA =
- (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS);
private static final float TILTED_LOWPASS_ALPHA =
- (float) SAMPLING_PERIOD_MS / (TILTED_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ computeLowpassAlpha(TILTED_TIME_CONSTANT_MS);
private static final float ACCELERATING_LOWPASS_ALPHA =
- (float) SAMPLING_PERIOD_MS / (ACCELERATING_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS);
+ computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
- // The low-pass filtered accelerometer data
- private float[] mFilteredVector = new float[] {0, 0, 0};
+ private WindowOrientationListener mOrientationListener;
+ private int mRotation = ROTATION_0; // Current orientation state
+ private float mTiltAngle = 0; // low-pass filtered
+ private float mOrientationAngle = 0; // low-pass filtered
- int getCurrentRotation() {
- return SURFACE_ROTATIONS[mRotation];
+ /*
+ * Each "distrust" counter represents our current level of distrust in the data based on
+ * a certain signal. For each data point that is deemed unreliable based on that signal,
+ * the counter increases; otherwise, the counter decreases. Exact rules vary.
+ */
+ private int mAccelerationDistrust = 0; // based on magnitude != gravity
+ private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees
+
+ public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
+ mOrientationListener = orientationListener;
+ }
+
+ private static float computeLowpassAlpha(int timeConstantMs) {
+ return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
+ }
+
+ int getCurrentRotation(int lastRotation) {
+ if (mTiltDistrust > 0) {
+ // we really don't know the current orientation, so trust what's currently displayed
+ mRotation = SURFACE_TO_INTERNAL_ROTATION[lastRotation];
+ }
+ return INTERNAL_TO_SURFACE_ROTATION[mRotation];
}
- private void calculateNewRotation(int orientation, int tiltAngle) {
+ private void calculateNewRotation(float orientation, float tiltAngle) {
if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
int thresholdRanges[][] = THRESHOLDS[mRotation];
int row = -1;
@@ -226,7 +277,7 @@ public abstract class WindowOrientationListener {
if (localLOGV) Log.i(TAG, " new rotation = " + rotation);
mRotation = rotation;
- onOrientationChanged(SURFACE_ROTATIONS[rotation]);
+ mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]);
}
private float lowpassFilter(float newValue, float oldValue, float alpha) {
@@ -238,11 +289,11 @@ public abstract class WindowOrientationListener {
}
/**
- * Absolute angle between upVector and the x-y plane (the plane of the screen), in [0, 90].
- * 90 degrees = screen facing the sky or ground.
+ * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90].
+ * +/- 90 degrees = screen facing the sky or ground.
*/
private float tiltAngle(float z, float magnitude) {
- return Math.abs((float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
+ return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES;
}
public void onSensorChanged(SensorEvent event) {
@@ -253,34 +304,122 @@ public abstract class WindowOrientationListener {
float z = event.values[_DATA_Z];
float magnitude = vectorMagnitude(x, y, z);
float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
- float tiltAngle = tiltAngle(z, magnitude);
+ handleAccelerationDistrust(deviation);
+
+ // only filter tilt when we're accelerating
+ float alpha = 1;
+ if (mAccelerationDistrust > 0) {
+ alpha = ACCELERATING_LOWPASS_ALPHA;
+ }
+ float newTiltAngle = tiltAngle(z, magnitude);
+ mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha);
+
+ float absoluteTilt = Math.abs(mTiltAngle);
+ checkFullyTilted(absoluteTilt);
+ if (mTiltDistrust > 0) {
+ return; // when fully tilted, ignore orientation entirely
+ }
+
+ float newOrientationAngle = computeNewOrientation(x, y);
+ filterOrientation(absoluteTilt, newOrientationAngle);
+ calculateNewRotation(mOrientationAngle, absoluteTilt);
+ }
+
+ /**
+ * When accelerating, increment distrust; otherwise, decrement distrust. The idea is that
+ * if a single jolt happens among otherwise good data, we should keep trusting the good
+ * data. On the other hand, if a series of many bad readings comes in (as if the phone is
+ * being rapidly shaken), we should wait until things "settle down", i.e. we get a string
+ * of good readings.
+ *
+ * @param deviation absolute difference between the current magnitude and gravity
+ */
+ private void handleAccelerationDistrust(float deviation) {
+ if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
+ if (mAccelerationDistrust < 5) {
+ mAccelerationDistrust++;
+ }
+ } else if (mAccelerationDistrust > 0) {
+ mAccelerationDistrust--;
+ }
+ }
+
+ /**
+ * Check if the phone is tilted towards the sky or ground and handle that appropriately.
+ * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we
+ * decrement it. The idea is to distrust the first few readings after the phone gets
+ * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is
+ * picked up from a table.
+ *
+ * We also reset the orientation angle to the center of the current screen orientation.
+ * Since there is no real orientation of the phone, we want to ignore the most recent sensor
+ * data and reset it to this value to avoid a premature transition when the phone starts to
+ * get un-tilted.
+ *
+ * @param absoluteTilt the absolute value of the current tilt angle
+ */
+ private void checkFullyTilted(float absoluteTilt) {
+ if (absoluteTilt > MAX_TILT) {
+ if (mRotation == ROTATION_0) {
+ mOrientationAngle = 0;
+ } else if (mRotation == ROTATION_90) {
+ mOrientationAngle = 90;
+ } else { // ROTATION_270
+ mOrientationAngle = 270;
+ }
+
+ if (mTiltDistrust < 3) {
+ mTiltDistrust = 3;
+ }
+ } else if (mTiltDistrust > 0) {
+ mTiltDistrust--;
+ }
+ }
+
+ /**
+ * Angle between the x-y projection of upVector and the +y-axis, increasing
+ * clockwise.
+ * 0 degrees = speaker end towards the sky
+ * 90 degrees = right edge of device towards the sky
+ */
+ private float computeNewOrientation(float x, float y) {
+ float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES;
+ // atan2 returns [-180, 180]; normalize to [0, 360]
+ if (orientationAngle < 0) {
+ orientationAngle += 360;
+ }
+ return orientationAngle;
+ }
+
+ /**
+ * Compute a new filtered orientation angle.
+ */
+ private void filterOrientation(float absoluteTilt, float orientationAngle) {
float alpha = DEFAULT_LOWPASS_ALPHA;
- if (tiltAngle > MAX_TILT) {
- return;
- } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
+ if (mAccelerationDistrust > 1) {
+ // when under more than a transient acceleration, distrust heavily
alpha = ACCELERATING_LOWPASS_ALPHA;
- } else if (tiltAngle > PARTIAL_TILT) {
+ } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) {
+ // when tilted partway, or under transient acceleration, distrust lightly
alpha = TILTED_LOWPASS_ALPHA;
}
- x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha);
- y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha);
- z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha);
- magnitude = vectorMagnitude(x, y, z);
- tiltAngle = tiltAngle(z, magnitude);
-
- // Angle between the x-y projection of upVector and the +y-axis, increasing
- // counter-clockwise.
- // 0 degrees = speaker end towards the sky
- // 90 degrees = left edge of device towards the sky
- float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES;
- int orientation = Math.round(orientationAngle);
- // atan2 returns (-180, 180]; normalize to [0, 360)
- if (orientation < 0) {
- orientation += 360;
+ // since we're lowpass filtering a value with periodic boundary conditions, we need to
+ // adjust the new value to filter in the right direction...
+ float deltaOrientation = orientationAngle - mOrientationAngle;
+ if (deltaOrientation > 180) {
+ orientationAngle -= 360;
+ } else if (deltaOrientation < -180) {
+ orientationAngle += 360;
+ }
+ mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha);
+ // ...and then adjust back to ensure we're in the range [0, 360]
+ if (mOrientationAngle > 360) {
+ mOrientationAngle -= 360;
+ } else if (mOrientationAngle < 0) {
+ mOrientationAngle += 360;
}
- calculateNewRotation(orientation, Math.round(tiltAngle));
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c22f991183b0..fc617003f898 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -622,6 +622,7 @@ public final class AccessibilityEvent implements Parcelable {
mPackageName = null;
mContentDescription = null;
mBeforeText = null;
+ mParcelableData = null;
mText.clear();
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 6ac16338f6d2..380194824da4 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -84,9 +84,14 @@ public class BaseInputConnection implements InputConnection {
}
}
}
-
+
public static void setComposingSpans(Spannable text) {
- final Object[] sps = text.getSpans(0, text.length(), Object.class);
+ setComposingSpans(text, 0, text.length());
+ }
+
+ /** @hide */
+ public static void setComposingSpans(Spannable text, int start, int end) {
+ final Object[] sps = text.getSpans(start, end, Object.class);
if (sps != null) {
for (int i=sps.length-1; i>=0; i--) {
final Object o = sps[i];
@@ -94,18 +99,19 @@ public class BaseInputConnection implements InputConnection {
text.removeSpan(o);
continue;
}
+
final int fl = text.getSpanFlags(o);
if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK))
!= (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
- (fl&Spanned.SPAN_POINT_MARK_MASK)
+ (fl & ~Spanned.SPAN_POINT_MARK_MASK)
| Spanned.SPAN_COMPOSING
| Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
-
- text.setSpan(COMPOSING, 0, text.length(),
+
+ text.setSpan(COMPOSING, start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
}
@@ -312,6 +318,31 @@ public class BaseInputConnection implements InputConnection {
}
/**
+ * The default implementation returns the text currently selected, or null if none is
+ * selected.
+ */
+ public CharSequence getSelectedText(int flags) {
+ final Editable content = getEditable();
+ if (content == null) return null;
+
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ if (a == b) return null;
+
+ if ((flags&GET_TEXT_WITH_STYLES) != 0) {
+ return content.subSequence(a, b);
+ }
+ return TextUtils.substring(content, a, b);
+ }
+
+ /**
* The default implementation returns the given amount of text from the
* current cursor position in the buffer.
*/
@@ -385,6 +416,38 @@ public class BaseInputConnection implements InputConnection {
return true;
}
+ public boolean setComposingRegion(int start, int end) {
+ final Editable content = getEditable();
+ if (content != null) {
+ beginBatchEdit();
+ removeComposingSpans(content);
+ int a = start;
+ int b = end;
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+ if (a < 0) a = 0;
+ if (b > content.length()) b = content.length();
+
+ ensureDefaultComposingSpans();
+ if (mDefaultComposingSpans != null) {
+ for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
+ content.setSpan(mDefaultComposingSpans[i], a, b,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+ }
+ }
+
+ content.setSpan(COMPOSING, a, b,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
+
+ endBatchEdit();
+ sendCurrentText();
+ }
+ return true;
+ }
+
/**
* The default implementation changes the selection position in the
* current editable text.
@@ -479,7 +542,32 @@ public class BaseInputConnection implements InputConnection {
content.clear();
}
}
-
+
+ private void ensureDefaultComposingSpans() {
+ if (mDefaultComposingSpans == null) {
+ Context context;
+ if (mTargetView != null) {
+ context = mTargetView.getContext();
+ } else if (mIMM.mServedView != null) {
+ context = mIMM.mServedView.getContext();
+ } else {
+ context = null;
+ }
+ if (context != null) {
+ TypedArray ta = context.getTheme()
+ .obtainStyledAttributes(new int[] {
+ com.android.internal.R.attr.candidatesTextStyleSpans
+ });
+ CharSequence style = ta.getText(0);
+ ta.recycle();
+ if (style != null && style instanceof Spanned) {
+ mDefaultComposingSpans = ((Spanned)style).getSpans(
+ 0, style.length(), Object.class);
+ }
+ }
+ }
+ }
+
private void replaceText(CharSequence text, int newCursorPosition,
boolean composing) {
final Editable content = getEditable();
@@ -520,32 +608,11 @@ public class BaseInputConnection implements InputConnection {
if (!(text instanceof Spannable)) {
sp = new SpannableStringBuilder(text);
text = sp;
- if (mDefaultComposingSpans == null) {
- Context context;
- if (mTargetView != null) {
- context = mTargetView.getContext();
- } else if (mIMM.mServedView != null) {
- context = mIMM.mServedView.getContext();
- } else {
- context = null;
- }
- if (context != null) {
- TypedArray ta = context.getTheme()
- .obtainStyledAttributes(new int[] {
- com.android.internal.R.attr.candidatesTextStyleSpans
- });
- CharSequence style = ta.getText(0);
- ta.recycle();
- if (style != null && style instanceof Spanned) {
- mDefaultComposingSpans = ((Spanned)style).getSpans(
- 0, style.length(), Object.class);
- }
- }
- }
+ ensureDefaultComposingSpans();
if (mDefaultComposingSpans != null) {
for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
}
}
} else {
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 8b6831e65539..3b8a3642818c 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -80,6 +80,21 @@ public interface InputConnection {
public CharSequence getTextAfterCursor(int n, int flags);
/**
+ * Gets the selected text, if any.
+ *
+ * <p>This method may fail if either the input connection has become
+ * invalid (such as its process crashing) or the client is taking too
+ * long to respond with the text (it is given a couple of seconds to return).
+ * In either case, a null is returned.
+ *
+ * @param flags Supplies additional options controlling how the text is
+ * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}.
+ * @return Returns the text that is currently selected, if any, or null if
+ * no text is selected.
+ */
+ public CharSequence getSelectedText(int flags);
+
+ /**
* Retrieve the current capitalization mode in effect at the current
* cursor position in the text. See
* {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode} for
@@ -162,6 +177,18 @@ public interface InputConnection {
public boolean setComposingText(CharSequence text, int newCursorPosition);
/**
+ * Mark a certain region of text as composing text. Any composing text set
+ * previously will be removed automatically. The default style for composing
+ * text is used.
+ *
+ * @param start the position in the text at which the composing region begins
+ * @param end the position in the text at which the composing region ends
+ * @return Returns true on success, false if the input connection is no longer
+ * valid.
+ */
+ public boolean setComposingRegion(int start, int end);
+
+ /**
* Have the text editor finish whatever composing text is currently
* active. This simply leaves the text as-is, removing any special
* composing styling or other state that was around it. The cursor
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 210559aed904..b73f9bb5eb7c 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -50,6 +50,10 @@ public class InputConnectionWrapper implements InputConnection {
return mTarget.getTextAfterCursor(n, flags);
}
+ public CharSequence getSelectedText(int flags) {
+ return mTarget.getSelectedText(flags);
+ }
+
public int getCursorCapsMode(int reqModes) {
return mTarget.getCursorCapsMode(reqModes);
}
@@ -67,6 +71,10 @@ public class InputConnectionWrapper implements InputConnection {
return mTarget.setComposingText(text, newCursorPosition);
}
+ public boolean setComposingRegion(int start, int end) {
+ return mTarget.setComposingRegion(start, end);
+ }
+
public boolean finishComposingText() {
return mTarget.finishComposingText();
}
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index d5058b0e0f8e..d17199028658 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -284,7 +284,7 @@ public final class CacheManager {
// only called from WebCore Thread
// make sure to call startCacheTransaction/endCacheTransaction in pair
/**
- * @deprecated
+ * @deprecated Always returns false.
*/
@Deprecated
public static boolean startCacheTransaction() {
@@ -294,7 +294,7 @@ public final class CacheManager {
// only called from WebCore Thread
// make sure to call startCacheTransaction/endCacheTransaction in pair
/**
- * @deprecated
+ * @deprecated Always returns false.
*/
@Deprecated
public static boolean endCacheTransaction() {
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index e76669361d5e..1f8d53f2a841 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -20,6 +20,8 @@ import android.os.Handler;
import android.os.Message;
import android.util.Log;
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
import java.util.Set;
final class JWebCoreJavaBridge extends Handler {
@@ -44,7 +46,8 @@ final class JWebCoreJavaBridge extends Handler {
// keep track of the main WebView attached to the current window so that we
// can get the proper Context.
- private WebView mCurrentMainWebView;
+ private static WeakReference<WebView> sCurrentMainWebView =
+ new WeakReference<WebView>(null);
/* package */
static final int REFRESH_PLUGINS = 100;
@@ -62,20 +65,20 @@ final class JWebCoreJavaBridge extends Handler {
nativeFinalize();
}
- synchronized void setActiveWebView(WebView webview) {
- if (mCurrentMainWebView != null) {
+ static synchronized void setActiveWebView(WebView webview) {
+ if (sCurrentMainWebView.get() != null) {
// it is possible if there is a sub-WebView. Do nothing.
return;
}
- mCurrentMainWebView = webview;
+ sCurrentMainWebView = new WeakReference<WebView>(webview);
}
- synchronized void removeActiveWebView(WebView webview) {
- if (mCurrentMainWebView != webview) {
+ static synchronized void removeActiveWebView(WebView webview) {
+ if (sCurrentMainWebView.get() != webview) {
// it is possible if there is a sub-WebView. Do nothing.
return;
}
- mCurrentMainWebView = null;
+ sCurrentMainWebView.clear();
}
/**
@@ -256,11 +259,12 @@ final class JWebCoreJavaBridge extends Handler {
synchronized private String getSignedPublicKey(int index, String challenge,
String url) {
- if (mCurrentMainWebView != null) {
+ WebView current = sCurrentMainWebView.get();
+ if (current != null) {
// generateKeyPair expects organizations which we don't have. Ignore
// url.
return CertTool.getSignedPublicKey(
- mCurrentMainWebView.getContext(), index, challenge);
+ current.getContext(), index, challenge);
} else {
Log.e(LOGTAG, "There is no active WebView for getSignedPublicKey");
return "";
diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java
index 034c88ae0731..ca9ad53c52a5 100644
--- a/core/java/android/webkit/MimeTypeMap.java
+++ b/core/java/android/webkit/MimeTypeMap.java
@@ -67,7 +67,7 @@ public class MimeTypeMap {
// if the filename contains special characters, we don't
// consider it valid for our matching purposes:
if (filename.length() > 0 &&
- Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)]+", filename)) {
+ Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) {
int dotPos = filename.lastIndexOf('.');
if (0 <= dotPos) {
return filename.substring(dotPos + 1);
diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java
index cdcb662e057c..e5eeb8cf59c5 100644
--- a/core/java/android/webkit/PluginManager.java
+++ b/core/java/android/webkit/PluginManager.java
@@ -21,9 +21,9 @@ import java.util.List;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -59,7 +59,9 @@ public class PluginManager {
*/
public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN";
- private static final String LOGTAG = "webkit";
+ private static final String LOGTAG = "PluginManager";
+
+ private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/";
private static final String PLUGIN_TYPE = "type";
private static final String TYPE_NATIVE = "native";
@@ -111,9 +113,8 @@ public class PluginManager {
ArrayList<String> directories = new ArrayList<String>();
PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(
- PLUGIN_ACTION), PackageManager.GET_SERVICES
- | PackageManager.GET_META_DATA);
+ List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
synchronized(mPackageInfoCache) {
@@ -143,10 +144,19 @@ public class PluginManager {
continue;
}
- // check if their is a conflict in the lib directory names
+ /*
+ * find the location of the plugin's shared library. The default
+ * is to assume the app is either a user installed app or an
+ * updated system app. In both of these cases the library is
+ * stored in the app's data directory.
+ */
String directory = pkgInfo.applicationInfo.dataDir + "/lib";
- if (directories.contains(directory)) {
- continue;
+ final int appFlags = pkgInfo.applicationInfo.flags;
+ final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM |
+ ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+ // preloaded system app with no user updates
+ if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) {
+ directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName;
}
// check if the plugin has the required permissions
@@ -236,7 +246,7 @@ public class PluginManager {
// must be synchronized to ensure the consistency of the cache
synchronized(mPackageInfoCache) {
for (PackageInfo pkgInfo : mPackageInfoCache) {
- if (pluginLib.startsWith(pkgInfo.applicationInfo.dataDir)) {
+ if (pluginLib.contains(pkgInfo.packageName)) {
return pkgInfo.packageName;
}
}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 1d5aac7fb3c0..9f642c09a274 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -320,4 +320,22 @@ public class WebChromeClient {
public void openFileChooser(ValueCallback<Uri> uploadFile) {
uploadFile.onReceiveValue(null);
}
+
+ /**
+ * Tell the client that the selection has been initiated.
+ * @hide
+ */
+ public void onSelectionStart(WebView view) {
+ // By default we cancel the selection again, thus disabling
+ // text selection unless the chrome client supports it.
+ view.notifySelectDialogDismissed();
+ }
+
+ /**
+ * Tell the client that the selection has been copied or canceled.
+ * @hide
+ */
+ public void onSelectionDone(WebView view) {
+ }
+
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index b767f11753dc..da0c61b2dc51 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -207,6 +207,7 @@ public class WebSettings {
private boolean mBuiltInZoomControls = false;
private boolean mAllowFileAccess = true;
private boolean mLoadWithOverviewMode = false;
+ private boolean mUseWebViewBackgroundOverscrollBackground = true;
// private WebSettings, not accessible by the host activity
static private int mDoubleTapToastCount = 3;
@@ -485,6 +486,23 @@ public class WebSettings {
}
/**
+ * Set whether the WebView uses its background for over scroll background.
+ * If true, it will use the WebView's background. If false, it will use an
+ * internal pattern. Default is true.
+ */
+ public void setUseWebViewBackgroundForOverscrollBackground(boolean view) {
+ mUseWebViewBackgroundOverscrollBackground = view;
+ }
+
+ /**
+ * Returns true if this WebView uses WebView's background instead of
+ * internal pattern for over scroll background.
+ */
+ public boolean getUseWebViewBackgroundForOverscrollBackground() {
+ return mUseWebViewBackgroundOverscrollBackground;
+ }
+
+ /**
* Store whether the WebView is saving form data.
*/
public void setSaveFormData(boolean save) {
@@ -1030,8 +1048,13 @@ public class WebSettings {
}
/**
- * TODO: need to add @Deprecated
+ * Set a custom path to plugins used by the WebView. This method is
+ * obsolete since each plugin is now loaded from its own package.
+ * @param pluginsPath String path to the directory containing plugins.
+ * @deprecated This method is no longer used as plugins are loaded from
+ * their own APK via the system's package manager.
*/
+ @Deprecated
public synchronized void setPluginsPath(String pluginsPath) {
}
@@ -1201,8 +1224,13 @@ public class WebSettings {
}
/**
- * TODO: need to add @Deprecated
+ * Returns the directory that contains the plugin libraries. This method is
+ * obsolete since each plugin is now loaded from its own package.
+ * @return An empty string.
+ * @deprecated This method is no longer used as plugins are loaded from
+ * their own APK via the system's package manager.
*/
+ @Deprecated
public synchronized String getPluginsPath() {
return "";
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index bbd8b95c7bea..456e0d9dd6e2 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,13 +16,16 @@
package android.webkit;
+import com.android.internal.R;
+
import android.annotation.Widget;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -30,7 +33,6 @@ import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Interpolator;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
import android.graphics.Point;
@@ -74,22 +76,22 @@ import android.webkit.WebViewCore.TouchEventData;
import android.widget.AbsoluteLayout;
import android.widget.Adapter;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckedTextView;
+import android.widget.EdgeGlow;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListView;
-import android.widget.Scroller;
+import android.widget.OverScroller;
import android.widget.Toast;
import android.widget.ZoomButtonsController;
import android.widget.ZoomControls;
-import android.widget.AdapterView.OnItemClickListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
-import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
@@ -239,14 +241,14 @@ import junit.framework.Assert;
*
* <h3>Building web pages to support different screen densities</h3>
*
- * <p>A screen's density is based on it's screen resolution and physical size. A screen with low
- * density has fewer available pixels per inch, where a screen with high density
- * has more -- sometimes significantly more -- pixels per inch. The density of a
+ * <p>The screen density of a device is based on the screen resolution. A screen with low density
+ * has fewer available pixels per inch, where a screen with high density
+ * has more &mdash; sometimes significantly more &mdash; pixels per inch. The density of a
* screen is important because, other things being equal, a UI element (such as a button) whose
* height and width are defined in terms of screen pixels will appear larger on the lower density
- * screen and smaller on the higher density screen. For simplicity, Android collapses all
- * actual screen densities into three generalized densities:high, medium, and low. </p>
- *
+ * screen and smaller on the higher density screen.
+ * For simplicity, Android collapses all actual screen densities into three generalized densities:
+ * high, medium, and low.</p>
* <p>By default, WebView scales a web page so that it is drawn at a size that matches the default
* appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen
* (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels
@@ -459,8 +461,12 @@ public class WebView extends AbsoluteLayout
private static final int TOUCH_SHORTPRESS_MODE = 5;
private static final int TOUCH_DOUBLE_TAP_MODE = 6;
private static final int TOUCH_DONE_MODE = 7;
- private static final int TOUCH_SELECT_MODE = 8;
- private static final int TOUCH_PINCH_DRAG = 9;
+ private static final int TOUCH_PINCH_DRAG = 8;
+
+ /**
+ * True if we have a touch panel capable of detecting smooth pan/scale at the same time
+ */
+ private boolean mAllowPanAndScale;
// Whether to forward the touch events to WebCore
private boolean mForwardTouchEvents = false;
@@ -558,7 +564,10 @@ public class WebView extends AbsoluteLayout
// time for the longest scroll animation
private static final int MAX_DURATION = 750; // milliseconds
private static final int SLIDE_TITLE_DURATION = 500; // milliseconds
- private Scroller mScroller;
+ private OverScroller mScroller;
+ private boolean mInOverScrollMode = false;
+ private static Paint mOverScrollBackground;
+ private static Paint mOverScrollBorder;
private boolean mWrapContent;
private static final int MOTIONLESS_FALSE = 0;
@@ -755,6 +764,27 @@ public class WebView extends AbsoluteLayout
private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
+ /**
+ * Max distance to overscroll by in pixels.
+ * This how far content can be pulled beyond its normal bounds by the user.
+ */
+ private int mOverscrollDistance;
+
+ /**
+ * Max distance to overfling by in pixels.
+ * This is how far flinged content can move beyond the end of its normal bounds.
+ */
+ private int mOverflingDistance;
+
+ /*
+ * These manage the edge glow effect when flung or pulled beyond the edges.
+ * If one is not null, all are not null. Checking one for null is as good as checking each.
+ */
+ private EdgeGlow mEdgeGlowTop;
+ private EdgeGlow mEdgeGlowBottom;
+ private EdgeGlow mEdgeGlowLeft;
+ private EdgeGlow mEdgeGlowRight;
+
// Used to match key downs and key ups
private boolean mGotKeyDown;
@@ -944,16 +974,18 @@ public class WebView extends AbsoluteLayout
mViewManager = new ViewManager(this);
mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
mDatabase = WebViewDatabase.getInstance(context);
- mScroller = new Scroller(context);
+ mScroller = new OverScroller(context);
updateMultiTouchSupport(context);
}
void updateMultiTouchSupport(Context context) {
WebSettings settings = getSettings();
- mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
+ final PackageManager pm = context.getPackageManager();
+ mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
&& settings.supportZoom() && settings.getBuiltInZoomControls();
+ mAllowPanAndScale = pm.hasSystemFeature(
+ PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
if (mSupportMultiTouch && (mScaleDetector == null)) {
mScaleDetector = new ScaleGestureDetector(context,
new ScaleDetectorListener());
@@ -1006,6 +1038,29 @@ public class WebView extends AbsoluteLayout
mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
mMaximumFling = configuration.getScaledMaximumFlingVelocity();
+ mOverscrollDistance = configuration.getScaledOverscrollDistance();
+ mOverflingDistance = configuration.getScaledOverflingDistance();
+ }
+
+ @Override
+ public void setOverscrollMode(int mode) {
+ super.setOverscrollMode(mode);
+ if (mode != OVERSCROLL_NEVER) {
+ if (mEdgeGlowTop == null) {
+ final Resources res = getContext().getResources();
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+ final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+ mEdgeGlowTop = new EdgeGlow(edge, glow);
+ mEdgeGlowBottom = new EdgeGlow(edge, glow);
+ mEdgeGlowLeft = new EdgeGlow(edge, glow);
+ mEdgeGlowRight = new EdgeGlow(edge, glow);
+ }
+ } else {
+ mEdgeGlowTop = null;
+ mEdgeGlowBottom = null;
+ mEdgeGlowLeft = null;
+ mEdgeGlowRight = null;
+ }
}
/* package */void updateDefaultZoomDensity(int zoomDensity) {
@@ -1147,7 +1202,8 @@ public class WebView extends AbsoluteLayout
* Return the amount of the titlebarview (if any) that is visible
*/
private int getVisibleTitleHeight() {
- return Math.max(getTitleHeight() - mScrollY, 0);
+ // need to restrict mScrollY due to over scroll
+ return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
}
/*
@@ -1383,16 +1439,23 @@ public class WebView extends AbsoluteLayout
final File temp = new File(dest.getPath() + ".writing");
new Thread(new Runnable() {
public void run() {
+ FileOutputStream out = null;
try {
- FileOutputStream out = new FileOutputStream(temp);
+ out = new FileOutputStream(temp);
p.writeToStream(out);
- out.close();
// Writing the picture succeeded, rename the temporary file
// to the destination.
temp.renameTo(dest);
} catch (Exception e) {
// too late to do anything about it.
} finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (Exception e) {
+ // Can't do anything about that
+ }
+ }
temp.delete();
}
}
@@ -1445,20 +1508,23 @@ public class WebView extends AbsoluteLayout
final Bundle copy = new Bundle(b);
new Thread(new Runnable() {
public void run() {
- final Picture p = Picture.createFromStream(in);
- if (p != null) {
- // Post a runnable on the main thread to update the
- // history picture fields.
- mPrivateHandler.post(new Runnable() {
- public void run() {
- restoreHistoryPictureFields(p, copy);
- }
- });
- }
try {
- in.close();
- } catch (Exception e) {
- // Nothing we can do now.
+ final Picture p = Picture.createFromStream(in);
+ if (p != null) {
+ // Post a runnable on the main thread to update the
+ // history picture fields.
+ mPrivateHandler.post(new Runnable() {
+ public void run() {
+ restoreHistoryPictureFields(p, copy);
+ }
+ });
+ }
+ } finally {
+ try {
+ in.close();
+ } catch (Exception e) {
+ // Nothing we can do now.
+ }
}
}
}).start();
@@ -1777,7 +1843,7 @@ public class WebView extends AbsoluteLayout
}
nativeClearCursor(); // start next trackball movement from page edge
if (bottom) {
- return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0);
+ return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0);
}
// Page down.
int h = getHeight();
@@ -2007,13 +2073,15 @@ public class WebView extends AbsoluteLayout
// Expects x in view coordinates
private int pinLocX(int x) {
- return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
+ if (mInOverScrollMode) return x;
+ return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
}
// Expects y in view coordinates
private int pinLocY(int y) {
+ if (mInOverScrollMode) return y;
return pinLoc(y, getViewHeightWithTitle(),
- computeVerticalScrollRange() + getTitleHeight());
+ computeRealVerticalScrollRange() + getTitleHeight());
}
/**
@@ -2325,7 +2393,7 @@ public class WebView extends AbsoluteLayout
// Sets r to be our visible rectangle in content coordinates
private void calcOurContentVisibleRect(Rect r) {
calcOurVisibleRect(r);
- // pin the rect to the bounds of the content
+ // since we might overscroll, pin the rect to the bounds of the content
r.left = Math.max(viewToContentX(r.left), 0);
// viewToContentY will remove the total height of the title bar. Add
// the visible height back in to account for the fact that if the title
@@ -2406,8 +2474,7 @@ public class WebView extends AbsoluteLayout
return false;
}
- @Override
- protected int computeHorizontalScrollRange() {
+ private int computeRealHorizontalScrollRange() {
if (mDrawHistory) {
return mHistoryWidth;
} else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF
@@ -2421,7 +2488,27 @@ public class WebView extends AbsoluteLayout
}
@Override
- protected int computeVerticalScrollRange() {
+ protected int computeHorizontalScrollRange() {
+ int range = computeRealHorizontalScrollRange();
+
+ // Adjust reported range if overscrolled to compress the scroll bars
+ final int scrollX = mScrollX;
+ final int overscrollRight = computeMaxScrollX();
+ if (scrollX < 0) {
+ range -= scrollX;
+ } else if (scrollX > overscrollRight) {
+ range += scrollX - overscrollRight;
+ }
+
+ return range;
+ }
+
+ @Override
+ protected int computeHorizontalScrollOffset() {
+ return Math.max(mScrollX, 0);
+ }
+
+ private int computeRealVerticalScrollRange() {
if (mDrawHistory) {
return mHistoryHeight;
} else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
@@ -2435,6 +2522,22 @@ public class WebView extends AbsoluteLayout
}
@Override
+ protected int computeVerticalScrollRange() {
+ int range = computeRealVerticalScrollRange();
+
+ // Adjust reported range if overscrolled to compress the scroll bars
+ final int scrollY = mScrollY;
+ final int overscrollBottom = computeMaxScrollY();
+ if (scrollY < 0) {
+ range -= scrollY;
+ } else if (scrollY > overscrollBottom) {
+ range += scrollY - overscrollBottom;
+ }
+
+ return range;
+ }
+
+ @Override
protected int computeVerticalScrollOffset() {
return Math.max(mScrollY - getTitleHeight(), 0);
}
@@ -2449,10 +2552,31 @@ public class WebView extends AbsoluteLayout
protected void onDrawVerticalScrollBar(Canvas canvas,
Drawable scrollBar,
int l, int t, int r, int b) {
+ if (mScrollY < 0) {
+ t -= mScrollY;
+ }
scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b);
scrollBar.draw(canvas);
}
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY, boolean clampedX,
+ boolean clampedY) {
+ mInOverScrollMode = false;
+ int maxX = computeMaxScrollX();
+ if (maxX == 0) {
+ // do not over scroll x if the page just fits the screen
+ scrollX = pinLocX(scrollX);
+ } else if (scrollX < 0 || scrollX > maxX) {
+ mInOverScrollMode = true;
+ }
+ if (scrollY < 0 || scrollY > computeMaxScrollY()) {
+ mInOverScrollMode = true;
+ }
+
+ super.scrollTo(scrollX, scrollY);
+ }
+
/**
* Get the url for the current page. This is not always the same as the url
* passed to WebViewClient.onPageStarted because although the load for
@@ -2683,6 +2807,14 @@ public class WebView extends AbsoluteLayout
nativeSetFindIsUp(isUp);
}
+ /**
+ * @hide
+ */
+ public int findIndex() {
+ if (0 == mNativeClass) return -1;
+ return nativeFindIndex();
+ }
+
// Used to know whether the find dialog is open. Affects whether
// or not we draw the highlights for matches.
private boolean mFindIsUp;
@@ -2798,11 +2930,37 @@ public class WebView extends AbsoluteLayout
if (mScroller.computeScrollOffset()) {
int oldX = mScrollX;
int oldY = mScrollY;
- mScrollX = mScroller.getCurrX();
- mScrollY = mScroller.getCurrY();
- postInvalidate(); // So we draw again
- if (oldX != mScrollX || oldY != mScrollY) {
- onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+ int x = mScroller.getCurrX();
+ int y = mScroller.getCurrY();
+ invalidate(); // So we draw again
+
+ if (oldX != x || oldY != y) {
+ final int rangeX = computeMaxScrollX();
+ final int rangeY = computeMaxScrollY();
+ overscrollBy(x - oldX, y - oldY, oldX, oldY,
+ rangeX, rangeY,
+ mOverflingDistance, mOverflingDistance, false);
+
+ if (mEdgeGlowTop != null) {
+ if (rangeY > 0 || getOverscrollMode() == OVERSCROLL_ALWAYS) {
+ if (y < 0 && oldY >= 0) {
+ mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
+ } else if (y > rangeY && oldY <= rangeY) {
+ mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
+ }
+ }
+
+ if (rangeX > 0) {
+ if (x < 0 && oldX >= 0) {
+ mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
+ } else if (x > rangeX && oldX <= rangeX) {
+ mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
+ }
+ }
+ }
+ }
+ if (mScroller.isFinished()) {
+ mPrivateHandler.sendEmptyMessage(RESUME_WEBCORE_PRIORITY);
}
} else {
super.computeScroll();
@@ -3250,6 +3408,36 @@ public class WebView extends AbsoluteLayout
}
int saveCount = canvas.save();
+ if (mInOverScrollMode && !getSettings()
+ .getUseWebViewBackgroundForOverscrollBackground()) {
+ if (mOverScrollBackground == null) {
+ mOverScrollBackground = new Paint();
+ Bitmap bm = BitmapFactory.decodeResource(
+ mContext.getResources(),
+ com.android.internal.R.drawable.status_bar_background);
+ mOverScrollBackground.setShader(new BitmapShader(bm,
+ Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
+ mOverScrollBorder = new Paint();
+ mOverScrollBorder.setStyle(Paint.Style.STROKE);
+ mOverScrollBorder.setStrokeWidth(0);
+ mOverScrollBorder.setColor(0xffbbbbbb);
+ }
+
+ int top = 0;
+ int right = computeRealHorizontalScrollRange();
+ int bottom = top + computeRealVerticalScrollRange();
+ // first draw the background and anchor to the top of the view
+ canvas.save();
+ canvas.translate(mScrollX, mScrollY);
+ canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom
+ - mScrollY, Region.Op.DIFFERENCE);
+ canvas.drawPaint(mOverScrollBackground);
+ canvas.restore();
+ // then draw the border
+ canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
+ // next clip the region for the content
+ canvas.clipRect(0, top, right, bottom);
+ }
if (mTitleBar != null) {
canvas.translate(0, (int) mTitleBar.getHeight());
}
@@ -3275,6 +3463,7 @@ public class WebView extends AbsoluteLayout
mScrollY + height);
mTitleShadow.draw(canvas);
}
+
if (AUTO_REDRAW_HACK && mAutoRedraw) {
invalidate();
}
@@ -3282,6 +3471,66 @@ public class WebView extends AbsoluteLayout
}
@Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mEdgeGlowTop != null && drawEdgeGlows(canvas)) {
+ invalidate();
+ }
+ }
+
+ /**
+ * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null.
+ *
+ * @param canvas Canvas to draw into, transformed into view coordinates.
+ * @return true if glow effects are still animating and the view should invalidate again.
+ */
+ private boolean drawEdgeGlows(Canvas canvas) {
+ final int scrollX = mScrollX;
+ final int scrollY = mScrollY;
+ final int width = getWidth();
+ int height = getHeight();
+
+ boolean invalidateForGlow = false;
+ if (!mEdgeGlowTop.isFinished()) {
+ final int restoreCount = canvas.save();
+
+ canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY));
+ mEdgeGlowTop.setSize(width * 2, height);
+ invalidateForGlow |= mEdgeGlowTop.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mEdgeGlowBottom.isFinished()) {
+ final int restoreCount = canvas.save();
+
+ canvas.translate(-width / 2 + scrollX, Math.max(computeMaxScrollY(), scrollY) + height);
+ canvas.rotate(180, width, 0);
+ mEdgeGlowBottom.setSize(width * 2, height);
+ invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mEdgeGlowLeft.isFinished()) {
+ final int restoreCount = canvas.save();
+
+ canvas.rotate(270);
+ canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX));
+ mEdgeGlowLeft.setSize(height * 2, width);
+ invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mEdgeGlowRight.isFinished()) {
+ final int restoreCount = canvas.save();
+
+ canvas.rotate(90);
+ canvas.translate(-height / 2 + scrollY,
+ -(Math.max(computeMaxScrollX(), scrollX) + width));
+ mEdgeGlowRight.setSize(height * 2, width);
+ invalidateForGlow |= mEdgeGlowRight.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ }
+ return invalidateForGlow;
+ }
+
+ @Override
public void setLayoutParams(ViewGroup.LayoutParams params) {
if (params.height == LayoutParams.WRAP_CONTENT) {
mWrapContent = true;
@@ -3299,12 +3548,34 @@ public class WebView extends AbsoluteLayout
// Send the click so that the textfield is in focus
centerKeyPressOnTextField();
rebuildWebTextView();
+ } else {
+ clearTextEntry(true);
}
if (inEditingMode()) {
return mWebTextView.performLongClick();
- } else {
- return super.performLongClick();
}
+ /* if long click brings up a context menu, the super function
+ * returns true and we're done. Otherwise, nothing happened when
+ * the user clicked. */
+ if (super.performLongClick()) {
+ return true;
+ }
+ /* In the case where the application hasn't already handled the long
+ * click action, look for a word under the click. If one is found,
+ * animate the text selection into view.
+ * FIXME: no animation code yet */
+ if (mSelectingText) return false; // long click does nothing on selection
+ int x = viewToContentX((int) mLastTouchX + mScrollX);
+ int y = viewToContentY((int) mLastTouchY + mScrollY);
+ setUpSelect();
+ if (mNativeClass != 0 && nativeWordSelection(x, y)) {
+ nativeSetExtendSelection();
+ WebChromeClient client = getWebChromeClient();
+ if (client != null) client.onSelectionStart(this);
+ return true;
+ }
+ notifySelectDialogDismissed();
+ return false;
}
boolean inAnimateZoom() {
@@ -3455,19 +3726,12 @@ public class WebView extends AbsoluteLayout
// decide which adornments to draw
int extras = DRAW_EXTRAS_NONE;
if (mFindIsUp) {
- // When the FindDialog is up, only draw the matches if we are not in
- // the process of scrolling them into view.
- if (!animateScroll) {
extras = DRAW_EXTRAS_FIND;
- }
- } else if (mShiftIsPressed && !nativeFocusIsPlugin()) {
- if (!animateZoom && !mPreviewZoomOnly) {
- extras = DRAW_EXTRAS_SELECTION;
- nativeSetSelectionRegion(mTouchSelection || mExtendSelection);
- nativeSetSelectionPointer(!mTouchSelection, mInvActualScale,
- mSelectX, mSelectY - getTitleHeight(),
- mExtendSelection);
- }
+ } else if (mSelectingText) {
+ extras = DRAW_EXTRAS_SELECTION;
+ nativeSetSelectionPointer(mDrawSelectionPointer,
+ mInvActualScale,
+ mSelectX, mSelectY - getTitleHeight());
} else if (drawCursorRing) {
extras = DRAW_EXTRAS_CURSOR_RING;
}
@@ -3476,11 +3740,6 @@ public class WebView extends AbsoluteLayout
if (extras == DRAW_EXTRAS_CURSOR_RING) {
if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) {
mTouchMode = TOUCH_SHORTPRESS_MODE;
- HitTestResult hitTest = getHitTestResult();
- if (hitTest == null
- || hitTest.mType == HitTestResult.UNKNOWN_TYPE) {
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- }
}
}
if (mFocusSizeChanged) {
@@ -3788,6 +4047,19 @@ public class WebView extends AbsoluteLayout
private boolean mGotCenterDown = false;
@Override
+ public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+ // send complex characters to webkit for use by JS and plugins
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) {
+ // pass the key to DOM
+ mWebViewCore.sendMessage(EventHub.KEY_DOWN, event);
+ mWebViewCore.sendMessage(EventHub.KEY_UP, event);
+ // return true as DOM handles the key
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (DebugFlags.WEB_VIEW) {
Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis()
@@ -3819,8 +4091,8 @@ public class WebView extends AbsoluteLayout
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
if (nativeFocusIsPlugin()) {
mShiftIsPressed = true;
- } else if (!nativeCursorWantsKeyEvents() && !mShiftIsPressed) {
- setUpSelectXY();
+ } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) {
+ setUpSelect();
}
}
@@ -3831,7 +4103,7 @@ public class WebView extends AbsoluteLayout
letPluginHandleNavKey(keyCode, event.getEventTime(), true);
return true;
}
- if (mShiftIsPressed) {
+ if (mSelectingText) {
int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT
? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0;
int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ?
@@ -3851,7 +4123,7 @@ public class WebView extends AbsoluteLayout
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
switchOutDrawHistory();
if (event.getRepeatCount() == 0) {
- if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+ if (mSelectingText) {
return true; // discard press if copy in progress
}
mGotCenterDown = true;
@@ -3869,10 +4141,8 @@ public class WebView extends AbsoluteLayout
if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT
&& keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) {
// turn off copy select if a shift-key combo is pressed
- mExtendSelection = mShiftIsPressed = false;
- if (mTouchMode == TOUCH_SELECT_MODE) {
- mTouchMode = TOUCH_INIT_MODE;
- }
+ selectionDone();
+ mShiftIsPressed = false;
}
if (getSettings().getNavDump()) {
@@ -3962,7 +4232,8 @@ public class WebView extends AbsoluteLayout
|| keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) {
if (nativeFocusIsPlugin()) {
mShiftIsPressed = false;
- } else if (commitCopy()) {
+ } else if (copySelection()) {
+ selectionDone();
return true;
}
}
@@ -3983,11 +4254,13 @@ public class WebView extends AbsoluteLayout
mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
mGotCenterDown = false;
- if (mShiftIsPressed && !nativeFocusIsPlugin()) {
+ if (mSelectingText) {
if (mExtendSelection) {
- commitCopy();
+ copySelection();
+ selectionDone();
} else {
mExtendSelection = true;
+ nativeSetExtendSelection();
invalidate(); // draw the i-beam instead of the arrow
}
return true; // discard press if copy in progress
@@ -4032,9 +4305,18 @@ public class WebView extends AbsoluteLayout
return false;
}
- private void setUpSelectXY() {
+ /**
+ * @hide pending API council approval.
+ */
+ public void setUpSelect() {
+ if (0 == mNativeClass) return; // client isn't initialized
+ if (inFullScreenMode()) return;
+ if (mSelectingText) return;
mExtendSelection = false;
- mShiftIsPressed = true;
+ mSelectingText = mDrawSelectionPointer = true;
+ // don't let the picture change during text selection
+ WebViewCore.pauseUpdatePicture(mWebViewCore);
+ nativeResetSelection();
if (nativeHasCursorNode()) {
Rect rect = nativeCursorNodeBounds();
mSelectX = contentToViewX(rect.left);
@@ -4054,40 +4336,83 @@ public class WebView extends AbsoluteLayout
* Do not rely on this functionality; it will be deprecated in the future.
*/
public void emulateShiftHeld() {
+ setUpSelect();
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public void selectAll() {
if (0 == mNativeClass) return; // client isn't initialized
- setUpSelectXY();
+ if (inFullScreenMode()) return;
+ if (!mSelectingText) setUpSelect();
+ nativeSelectAll();
+ mDrawSelectionPointer = false;
+ mExtendSelection = true;
+ invalidate();
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public boolean selectDialogIsUp() {
+ return mSelectingText;
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public void notifySelectDialogDismissed() {
+ mSelectingText = false;
+ WebViewCore.resumeUpdatePicture(mWebViewCore);
+ }
+
+ /**
+ * @hide pending API council approval.
+ */
+ public void selectionDone() {
+ if (mSelectingText) {
+ WebChromeClient client = getWebChromeClient();
+ if (client != null) client.onSelectionDone(this);
+ invalidate(); // redraw without selection
+ notifySelectDialogDismissed();
+ }
}
- private boolean commitCopy() {
+ /**
+ * @hide pending API council approval.
+ */
+ public boolean copySelection() {
boolean copiedSomething = false;
- if (mExtendSelection) {
- String selection = nativeGetSelection();
- if (selection != "") {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "commitCopy \"" + selection + "\"");
- }
- Toast.makeText(mContext
- , com.android.internal.R.string.text_copied
- , Toast.LENGTH_SHORT).show();
- copiedSomething = true;
- try {
- IClipboard clip = IClipboard.Stub.asInterface(
- ServiceManager.getService("clipboard"));
- clip.setClipboardText(selection);
- } catch (android.os.RemoteException e) {
- Log.e(LOGTAG, "Clipboard failed", e);
- }
+ String selection = getSelection();
+ if (selection != "") {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "copySelection \"" + selection + "\"");
+ }
+ Toast.makeText(mContext
+ , com.android.internal.R.string.text_copied
+ , Toast.LENGTH_SHORT).show();
+ copiedSomething = true;
+ try {
+ IClipboard clip = IClipboard.Stub.asInterface(
+ ServiceManager.getService("clipboard"));
+ clip.setClipboardText(selection);
+ } catch (android.os.RemoteException e) {
+ Log.e(LOGTAG, "Clipboard failed", e);
}
- mExtendSelection = false;
}
- mShiftIsPressed = false;
invalidate(); // remove selection region and pointer
- if (mTouchMode == TOUCH_SELECT_MODE) {
- mTouchMode = TOUCH_INIT_MODE;
- }
return copiedSomething;
}
+ /**
+ * @hide pending API council approval.
+ */
+ public String getSelection() {
+ if (mNativeClass == 0) return "";
+ return nativeGetSelection();
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -4177,9 +4502,9 @@ public class WebView extends AbsoluteLayout
public void onWindowFocusChanged(boolean hasWindowFocus) {
setActive(hasWindowFocus);
if (hasWindowFocus) {
- BrowserFrame.sJavaBridge.setActiveWebView(this);
+ JWebCoreJavaBridge.setActiveWebView(this);
} else {
- BrowserFrame.sJavaBridge.removeActiveWebView(this);
+ JWebCoreJavaBridge.removeActiveWebView(this);
}
super.onWindowFocusChanged(hasWindowFocus);
}
@@ -4325,12 +4650,14 @@ public class WebView extends AbsoluteLayout
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
- sendOurVisibleRect();
- // update WebKit if visible title bar height changed. The logic is same
- // as getVisibleTitleHeight.
- int titleHeight = getTitleHeight();
- if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
- sendViewSizeZoom();
+ if (!mInOverScrollMode) {
+ sendOurVisibleRect();
+ // update WebKit if visible title bar height changed. The logic is same
+ // as getVisibleTitleHeight.
+ int titleHeight = getTitleHeight();
+ if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
+ sendViewSizeZoom();
+ }
}
}
@@ -4400,7 +4727,7 @@ public class WebView extends AbsoluteLayout
public DragTrackerHandler(float x, float y, DragTracker proxy) {
mProxy = proxy;
- int docBottom = computeVerticalScrollRange() + getTitleHeight();
+ int docBottom = computeRealVerticalScrollRange() + getTitleHeight();
int viewTop = getScrollY();
int viewBottom = viewTop + getHeight();
@@ -4413,7 +4740,7 @@ public class WebView extends AbsoluteLayout
" up/down= " + mMinDY + " " + mMaxDY);
}
- int docRight = computeHorizontalScrollRange();
+ int docRight = computeRealHorizontalScrollRange();
int viewLeft = getScrollX();
int viewRight = viewLeft + getWidth();
mStartX = x;
@@ -4662,7 +4989,7 @@ public class WebView extends AbsoluteLayout
private boolean shouldForwardTouchEvent() {
return mFullScreenHolder != null || (mForwardTouchEvents
- && mTouchMode != TOUCH_SELECT_MODE
+ && !mSelectingText
&& mPreventDefault != PREVENT_DEFAULT_IGNORE);
}
@@ -4688,11 +5015,13 @@ public class WebView extends AbsoluteLayout
// FIXME: we may consider to give WebKit an option to handle multi-touch
// events later.
if (mSupportMultiTouch && ev.getPointerCount() > 1) {
- if (mMinZoomScale < mMaxZoomScale) {
+ if (mAllowPanAndScale || mMinZoomScale < mMaxZoomScale) {
mScaleDetector.onTouchEvent(ev);
if (mScaleDetector.isInProgress()) {
mLastTouchTime = eventTime;
- return true;
+ if (!mAllowPanAndScale) {
+ return true;
+ }
}
x = mScaleDetector.getFocusX();
y = mScaleDetector.getFocusY();
@@ -4750,16 +5079,6 @@ public class WebView extends AbsoluteLayout
mTouchMode = TOUCH_DRAG_START_MODE;
mConfirmMove = true;
mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
- } else if (!inFullScreenMode() && mShiftIsPressed) {
- mSelectX = mScrollX + (int) x;
- mSelectY = mScrollY + (int) y;
- mTouchMode = TOUCH_SELECT_MODE;
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY);
- }
- nativeMoveSelection(contentX, contentY, false);
- mTouchSelection = mExtendSelection = true;
- invalidate(); // draw the i-beam instead of the arrow
} else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
@@ -4784,6 +5103,14 @@ public class WebView extends AbsoluteLayout
EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
(eventTime - mLastTouchUpTime), eventTime);
}
+ if (mSelectingText) {
+ mDrawSelectionPointer = false;
+ mSelectionStarted = nativeStartSelection(contentX, contentY);
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "select=" + contentX + "," + contentY);
+ }
+ invalidate();
+ }
}
// Trigger the link
if (mTouchMode == TOUCH_INIT_MODE
@@ -4875,17 +5202,17 @@ public class WebView extends AbsoluteLayout
+ " mTouchMode = " + mTouchMode);
}
mVelocityTracker.addMovement(ev);
- if (mTouchMode != TOUCH_DRAG_MODE) {
- if (mTouchMode == TOUCH_SELECT_MODE) {
- mSelectX = mScrollX + (int) x;
- mSelectY = mScrollY + (int) y;
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY);
- }
- nativeMoveSelection(contentX, contentY, true);
- invalidate();
- break;
+ if (mSelectingText && mSelectionStarted) {
+ if (DebugFlags.WEB_VIEW) {
+ Log.v(LOGTAG, "extend=" + contentX + "," + contentY);
}
+ nativeExtendSelection(contentX, contentY);
+ invalidate();
+ break;
+ }
+
+ if (mTouchMode != TOUCH_DRAG_MODE) {
+
if (!mConfirmMove) {
break;
}
@@ -4896,15 +5223,21 @@ public class WebView extends AbsoluteLayout
mLastTouchTime = eventTime;
break;
}
- // if it starts nearly horizontal or vertical, enforce it
- int ax = Math.abs(deltaX);
- int ay = Math.abs(deltaY);
- if (ax > MAX_SLOPE_FOR_DIAG * ay) {
- mSnapScrollMode = SNAP_X;
- mSnapPositive = deltaX > 0;
- } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
- mSnapScrollMode = SNAP_Y;
- mSnapPositive = deltaY > 0;
+
+ // Only lock dragging to one axis if we don't have a scale in progress.
+ // Scaling implies free-roaming movement. Note we'll only ever get here
+ // if mAllowPanAndScale is true.
+ if (mScaleDetector != null && !mScaleDetector.isInProgress()) {
+ // if it starts nearly horizontal or vertical, enforce it
+ int ax = Math.abs(deltaX);
+ int ay = Math.abs(deltaY);
+ if (ax > MAX_SLOPE_FOR_DIAG * ay) {
+ mSnapScrollMode = SNAP_X;
+ mSnapPositive = deltaX > 0;
+ } else if (ay > MAX_SLOPE_FOR_DIAG * ax) {
+ mSnapScrollMode = SNAP_Y;
+ mSnapPositive = deltaY > 0;
+ }
}
mTouchMode = TOUCH_DRAG_MODE;
@@ -4923,18 +5256,6 @@ public class WebView extends AbsoluteLayout
}
// do pan
- int newScrollX = pinLocX(mScrollX + deltaX);
- int newDeltaX = newScrollX - mScrollX;
- if (deltaX != newDeltaX) {
- deltaX = newDeltaX;
- fDeltaX = (float) newDeltaX;
- }
- int newScrollY = pinLocY(mScrollY + deltaY);
- int newDeltaY = newScrollY - mScrollY;
- if (deltaY != newDeltaY) {
- deltaY = newDeltaY;
- fDeltaY = (float) newDeltaY;
- }
boolean done = false;
boolean keepScrollBarsVisible = false;
if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
@@ -5052,10 +5373,6 @@ public class WebView extends AbsoluteLayout
mTouchMode = TOUCH_DONE_MODE;
}
break;
- case TOUCH_SELECT_MODE:
- commitCopy();
- mTouchSelection = false;
- break;
case TOUCH_INIT_MODE: // tap
case TOUCH_SHORTPRESS_START_MODE:
case TOUCH_SHORTPRESS_MODE:
@@ -5086,6 +5403,13 @@ public class WebView extends AbsoluteLayout
break;
}
} else {
+ if (mSelectingText) {
+ if (nativeHitSelection(contentX, contentY)) {
+ copySelection();
+ }
+ selectionDone();
+ break;
+ }
if (mTouchMode == TOUCH_INIT_MODE) {
mPrivateHandler.sendEmptyMessageDelayed(
RELEASE_SINGLE_TAP, ViewConfiguration
@@ -5115,6 +5439,12 @@ public class WebView extends AbsoluteLayout
mHeldMotionless = MOTIONLESS_IGNORE;
doFling();
break;
+ } else {
+ if (mScroller.springback(mScrollX, mScrollY, 0,
+ computeMaxScrollX(), 0,
+ computeMaxScrollY())) {
+ invalidate();
+ }
}
// redraw in high-quality, as we're done dragging
mHeldMotionless = MOTIONLESS_TRUE;
@@ -5134,6 +5464,8 @@ public class WebView extends AbsoluteLayout
}
case MotionEvent.ACTION_CANCEL: {
if (mTouchMode == TOUCH_DRAG_MODE) {
+ mScroller.springback(mScrollX, mScrollY, 0,
+ computeMaxScrollX(), 0, computeMaxScrollY());
invalidate();
}
cancelWebCoreTouchEvent(contentX, contentY, false);
@@ -5197,7 +5529,34 @@ public class WebView extends AbsoluteLayout
private void doDrag(int deltaX, int deltaY) {
if ((deltaX | deltaY) != 0) {
- scrollBy(deltaX, deltaY);
+ final int oldX = mScrollX;
+ final int oldY = mScrollY;
+ final int rangeX = computeMaxScrollX();
+ final int rangeY = computeMaxScrollY();
+ overscrollBy(deltaX, deltaY, oldX, oldY,
+ rangeX, rangeY,
+ mOverscrollDistance, mOverscrollDistance, true);
+
+ if (mEdgeGlowTop != null) {
+ // Don't show left/right glows if we fit the whole content.
+ if (rangeX > 0) {
+ final int pulledToX = oldX + deltaX;
+ if (pulledToX < 0) {
+ mEdgeGlowLeft.onPull((float) deltaX / getWidth());
+ } else if (pulledToX > rangeX) {
+ mEdgeGlowRight.onPull((float) deltaX / getWidth());
+ }
+ }
+
+ if (rangeY > 0 || getOverscrollMode() == OVERSCROLL_ALWAYS) {
+ final int pulledToY = oldY + deltaY;
+ if (pulledToY < 0) {
+ mEdgeGlowTop.onPull((float) deltaY / getHeight());
+ } else if (pulledToY > rangeY) {
+ mEdgeGlowBottom.onPull((float) deltaY / getHeight());
+ }
+ }
+ }
}
if (!getSettings().getBuiltInZoomControls()) {
boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
@@ -5224,6 +5583,14 @@ public class WebView extends AbsoluteLayout
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+
+ // Release any pulled glows
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
+ mEdgeGlowLeft.onRelease();
+ mEdgeGlowRight.onRelease();
+ }
}
private void cancelTouch() {
@@ -5237,6 +5604,15 @@ public class WebView extends AbsoluteLayout
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+
+ // Release any pulled glows
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
+ mEdgeGlowLeft.onRelease();
+ mEdgeGlowRight.onRelease();
+ }
+
if (mTouchMode == TOUCH_DRAG_MODE) {
WebViewCore.resumePriority();
WebViewCore.resumeUpdatePicture(mWebViewCore);
@@ -5256,8 +5632,10 @@ public class WebView extends AbsoluteLayout
private float mTrackballRemainsY = 0.0f;
private int mTrackballXMove = 0;
private int mTrackballYMove = 0;
+ private boolean mSelectingText = false;
+ private boolean mSelectionStarted = false;
private boolean mExtendSelection = false;
- private boolean mTouchSelection = false;
+ private boolean mDrawSelectionPointer = false;
private static final int TRACKBALL_KEY_TIMEOUT = 1000;
private static final int TRACKBALL_TIMEOUT = 200;
private static final int TRACKBALL_WAIT = 100;
@@ -5296,10 +5674,8 @@ public class WebView extends AbsoluteLayout
if (ev.getY() < 0) pageUp(true);
return true;
}
- boolean shiftPressed = mShiftIsPressed && (mNativeClass == 0
- || !nativeFocusIsPlugin());
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (shiftPressed) {
+ if (mSelectingText) {
return true; // discard press if copy in progress
}
mTrackballDown = true;
@@ -5324,11 +5700,13 @@ public class WebView extends AbsoluteLayout
mPrivateHandler.removeMessages(LONG_PRESS_CENTER);
mTrackballDown = false;
mTrackballUpTime = time;
- if (shiftPressed) {
+ if (mSelectingText) {
if (mExtendSelection) {
- commitCopy();
+ copySelection();
+ selectionDone();
} else {
mExtendSelection = true;
+ nativeSetExtendSelection();
invalidate(); // draw the i-beam instead of the arrow
}
return true; // discard press if copy in progress
@@ -5395,8 +5773,7 @@ public class WebView extends AbsoluteLayout
+ " yRate=" + yRate
);
}
- nativeMoveSelection(viewToContentX(mSelectX),
- viewToContentY(mSelectY), mExtendSelection);
+ nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY));
int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET
: mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET
: 0;
@@ -5462,7 +5839,16 @@ public class WebView extends AbsoluteLayout
float yRate = mTrackballRemainsY * 1000 / elapsed;
int viewWidth = getViewWidth();
int viewHeight = getViewHeight();
- if (mShiftIsPressed && (mNativeClass == 0 || !nativeFocusIsPlugin())) {
+ if (mSelectingText) {
+ if (!mDrawSelectionPointer) {
+ // The last selection was made by touch, disabling drawing the
+ // selection pointer. Allow the trackball to adjust the
+ // position of the touch control.
+ mSelectX = contentToViewX(nativeSelectionX());
+ mSelectY = contentToViewY(nativeSelectionY());
+ mDrawSelectionPointer = mExtendSelection = true;
+ nativeSetExtendSelection();
+ }
moveSelection(scaleTrackballX(xRate, viewWidth),
scaleTrackballY(yRate, viewHeight));
mTrackballRemainsX = mTrackballRemainsY = 0;
@@ -5535,17 +5921,17 @@ public class WebView extends AbsoluteLayout
}
private int computeMaxScrollX() {
- return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+ return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
}
private int computeMaxScrollY() {
- return Math.max(computeVerticalScrollRange() + getTitleHeight()
+ return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
- getViewHeightWithTitle(), 0);
}
public void flingScroll(int vx, int vy) {
mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
- computeMaxScrollY());
+ computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
invalidate();
}
@@ -5575,6 +5961,10 @@ public class WebView extends AbsoluteLayout
if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
WebViewCore.resumePriority();
WebViewCore.resumeUpdatePicture(mWebViewCore);
+ if (mScroller.springback(mScrollX, mScrollY, 0, computeMaxScrollX(),
+ 0, computeMaxScrollY())) {
+ invalidate();
+ }
return;
}
float currentVelocity = mScroller.getCurrVelocity();
@@ -5598,17 +5988,37 @@ public class WebView extends AbsoluteLayout
+ " maxX=" + maxX + " maxY=" + maxY
+ " mScrollX=" + mScrollX + " mScrollY=" + mScrollY);
}
+
+ // Allow sloppy flings without overscrolling at the edges.
+ if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
+ vx = 0;
+ }
+ if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
+ vy = 0;
+ }
+
+ if (mOverscrollDistance < mOverflingDistance) {
+ if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) {
+ vx = 0;
+ }
+ if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) {
+ vy = 0;
+ }
+ }
+
mLastVelX = vx;
mLastVelY = vy;
mLastVelocity = (float) Math.hypot(vx, vy);
- mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
- // TODO: duration is calculated based on velocity, if the range is
- // small, the animation will stop before duration is up. We may
- // want to calculate how long the animation is going to run to precisely
- // resume the webcore update.
+ // no horizontal overscroll if the content just fits
+ mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY,
+ maxX == 0 ? 0 : mOverflingDistance, mOverflingDistance);
+ // Duration is calculated based on velocity. With range boundaries and overscroll
+ // we may not know how long the final animation will take. (Hence the deprecation
+ // warning on the call below.) It's not a big deal for scroll bars but if webcore
+ // resumes during this effect we will take a performance hit. See computeScroll;
+ // we resume webcore there when the animation is finished.
final int time = mScroller.getDuration();
- mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time);
awakenScrollBars(time);
invalidate();
}
@@ -6656,6 +7066,10 @@ public class WebView extends AbsoluteLayout
case MotionEvent.ACTION_CANCEL:
if (mDeferTouchMode == TOUCH_DRAG_MODE) {
// no fling in defer process
+ mScroller.springback(mScrollX, mScrollY, 0,
+ computeMaxScrollX(), 0,
+ computeMaxScrollY());
+ invalidate();
WebViewCore.resumePriority();
WebViewCore.resumeUpdatePicture(mWebViewCore);
}
@@ -7382,6 +7796,7 @@ public class WebView extends AbsoluteLayout
private native void nativeDebugDump();
private native void nativeDestroy();
private native boolean nativeEvaluateLayersAnimations();
+ private native void nativeExtendSelection(int x, int y);
private native void nativeDrawExtras(Canvas canvas, int extra);
private native void nativeDumpDisplayTree(String urlOrNull);
private native int nativeFindAll(String findLower, String findUpper);
@@ -7410,6 +7825,7 @@ public class WebView extends AbsoluteLayout
private native boolean nativeHasCursorNode();
private native boolean nativeHasFocusNode();
private native void nativeHideCursor();
+ private native boolean nativeHitSelection(int x, int y);
private native String nativeImageURI(int x, int y);
private native void nativeInstrumentReport();
/* package */ native boolean nativeMoveCursorToNextTextInput();
@@ -7419,21 +7835,27 @@ public class WebView extends AbsoluteLayout
private native boolean nativeMoveCursor(int keyCode, int count,
boolean noScroll);
private native int nativeMoveGeneration();
- private native void nativeMoveSelection(int x, int y,
- boolean extendSelection);
+ private native void nativeMoveSelection(int x, int y);
private native boolean nativePointInNavCache(int x, int y, int slop);
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
private native void nativeRecordButtons(boolean focused,
boolean pressed, boolean invalidate);
+ private native void nativeResetSelection();
+ private native void nativeSelectAll();
private native void nativeSelectBestAt(Rect rect);
+ private native int nativeSelectionX();
+ private native int nativeSelectionY();
+ private native int nativeFindIndex();
+ private native void nativeSetExtendSelection();
private native void nativeSetFindIsEmpty();
private native void nativeSetFindIsUp(boolean isUp);
private native void nativeSetFollowedLink(boolean followed);
private native void nativeSetHeightCanMeasure(boolean measure);
private native void nativeSetRootLayer(int layer);
private native void nativeSetSelectionPointer(boolean set,
- float scale, int x, int y, boolean extendSelection);
+ float scale, int x, int y);
+ private native boolean nativeStartSelection(int x, int y);
private native void nativeSetSelectionRegion(boolean set);
private native Rect nativeSubtractLayers(Rect content);
private native int nativeTextGeneration();
@@ -7441,6 +7863,7 @@ public class WebView extends AbsoluteLayout
// we always want to pass in our generation number.
private native void nativeUpdateCachedTextfield(String updatedText,
int generation);
+ private native boolean nativeWordSelection(int x, int y);
// return NO_LEFTEDGE means failure.
private static final int NO_LEFTEDGE = -1;
private native int nativeGetBlockLeftEdge(int x, int y, float scale);
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 4118119e074e..bf4d95bbf4dc 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -1568,9 +1568,16 @@ final class WebViewCore {
+ evt);
}
int keyCode = evt.getKeyCode();
- if (!nativeKey(keyCode, evt.getUnicodeChar(),
- evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(),
- evt.isSymPressed(),
+ int unicodeChar = evt.getUnicodeChar();
+
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN && evt.getCharacters() != null
+ && evt.getCharacters().length() > 0) {
+ // we should only receive individual complex characters
+ unicodeChar = evt.getCharacters().codePointAt(0);
+ }
+
+ if (!nativeKey(keyCode, unicodeChar, evt.getRepeatCount(), evt.isShiftPressed(),
+ evt.isAltPressed(), evt.isSymPressed(),
isDown) && keyCode != KeyEvent.KEYCODE_ENTER) {
if (keyCode >= KeyEvent.KEYCODE_DPAD_UP
&& keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) {
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index b18419dc1768..7acd9baba8b4 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -727,6 +727,9 @@ public class WebViewDatabase {
}
long getCacheTotalSize() {
+ if (mCacheDatabase == null) {
+ return 0;
+ }
long size = 0;
Cursor cursor = null;
final String query = "SELECT SUM(contentlength) as sum FROM cache";
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index fcfecb30ca70..8f5c35ea7425 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,6 +19,7 @@ package android.widget;
import com.android.internal.R;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
@@ -33,6 +34,7 @@ import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -44,7 +46,7 @@ import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
-import android.view.ContextMenu.ContextMenuInfo;
+import android.view.animation.AnimationUtils;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
@@ -128,6 +130,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
static final int TOUCH_MODE_FLING = 4;
/**
+ * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
+ */
+ static final int TOUCH_MODE_OVERSCROLL = 5;
+
+ /**
+ * Indicates the view is being flung outside of normal content bounds
+ * and will spring back.
+ */
+ static final int TOUCH_MODE_OVERFLING = 6;
+
+ /**
* Regular layout - usually an unsolicited layout from the view system
*/
static final int LAYOUT_NORMAL = 0;
@@ -369,6 +382,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private ContextMenuInfo mContextMenuInfo = null;
/**
+ * Maximum distance to record overscroll
+ */
+ int mOverscrollMax;
+
+ /**
+ * Content height divided by this is the overscroll limit.
+ */
+ static final int OVERSCROLL_LIMIT_DIVISOR = 3;
+
+ /**
* Used to request a layout when we changed touch mode
*/
private static final int TOUCH_MODE_UNKNOWN = -1;
@@ -461,6 +484,43 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private static final int INVALID_POINTER = -1;
/**
+ * Maximum distance to overscroll by during edge effects
+ */
+ int mOverscrollDistance;
+
+ /**
+ * Maximum distance to overfling during edge effects
+ */
+ int mOverflingDistance;
+
+ // These two EdgeGlows are always set and used together.
+ // Checking one for null is as good as checking both.
+
+ /**
+ * Tracks the state of the top edge glow.
+ */
+ private EdgeGlow mEdgeGlowTop;
+
+ /**
+ * Tracks the state of the bottom edge glow.
+ */
+ private EdgeGlow mEdgeGlowBottom;
+
+ /**
+ * An estimate of how many pixels are between the top of the list and
+ * the top of the first position in the adapter, based on the last time
+ * we saw it. Used to hint where to draw edge glows.
+ */
+ private int mFirstPositionDistanceGuess;
+
+ /**
+ * An estimate of how many pixels are between the bottom of the list and
+ * the bottom of the last position in the adapter, based on the last time
+ * we saw it. Used to hint where to draw edge glows.
+ */
+ private int mLastPositionDistanceGuess;
+
+ /**
* Interface definition for a callback to be invoked when the list or grid
* has been scrolled.
*/
@@ -575,9 +635,41 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mOverscrollDistance = configuration.getScaledOverscrollDistance();
+ mOverflingDistance = configuration.getScaledOverflingDistance();
+
mDensityScale = getContext().getResources().getDisplayMetrics().density;
}
+ @Override
+ public void setOverscrollMode(int mode) {
+ if (mode != OVERSCROLL_NEVER) {
+ if (mEdgeGlowTop == null) {
+ final Resources res = getContext().getResources();
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+ final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+ mEdgeGlowTop = new EdgeGlow(edge, glow);
+ mEdgeGlowBottom = new EdgeGlow(edge, glow);
+ }
+ } else {
+ mEdgeGlowTop = null;
+ mEdgeGlowBottom = null;
+ }
+ super.setOverscrollMode(mode);
+ }
+
+ /**
+ * @return true if all list content currently fits within the view boundaries
+ */
+ private boolean contentFits() {
+ final int childCount = getChildCount();
+ if (childCount != mItemCount) {
+ return false;
+ }
+
+ return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= mBottom;
+ }
+
/**
* Enables fast scrolling by letting the user quickly scroll through lists by
* dragging the fast scroll thumb. The adapter attached to the list may want
@@ -1074,6 +1166,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int result;
if (mSmoothScrollbarEnabled) {
result = Math.max(mItemCount * 100, 0);
+ if (mScrollY != 0) {
+ // Compensate for overscroll
+ result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
+ }
} else {
result = mItemCount;
}
@@ -1146,6 +1242,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
layoutChildren();
mInLayout = false;
+
+ mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
}
/**
@@ -1612,6 +1710,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mFlingRunnable.endFling();
if (mScrollY != 0) {
mScrollY = 0;
+ finishGlows();
invalidate();
}
}
@@ -1921,9 +2020,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Check if we have moved far enough that it looks more like a
// scroll than a tap
final int distance = Math.abs(deltaY);
- if (distance > mTouchSlop) {
+ final boolean overscroll = mScrollY != 0;
+ if (overscroll || distance > mTouchSlop) {
createScrollingCache();
- mTouchMode = TOUCH_MODE_SCROLL;
+ mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
mMotionCorrection = deltaY;
final Handler handler = getHandler();
// Handler should not be null unless the AbsListView is not attached to a
@@ -1959,6 +2059,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// touch mode). Force an initial layout to get rid of the selection.
layoutChildren();
}
+ } else {
+ int touchMode = mTouchMode;
+ if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
+ if (mFlingRunnable != null) {
+ mFlingRunnable.endFling();
+ }
+
+ if (mScrollY != 0) {
+ mScrollY = 0;
+ finishGlows();
+ invalidate();
+ }
+ }
}
}
@@ -1989,49 +2102,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
- mActivePointerId = ev.getPointerId(0);
- final int x = (int) ev.getX();
- final int y = (int) ev.getY();
- int motionPosition = pointToPosition(x, y);
- if (!mDataChanged) {
- if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
- && (getAdapter().isEnabled(motionPosition))) {
- // User clicked on an actual view (and was not stopping a fling). It might be a
- // click or a scroll. Assume it is a click until proven otherwise
- mTouchMode = TOUCH_MODE_DOWN;
- // FIXME Debounce
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- } else {
- if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
- // If we couldn't find a view to click on, but the down event was touching
- // the edge, we will bail out and try again. This allows the edge correcting
- // code in ViewRoot to try to find a nearby view to select
- return false;
- }
+ switch (mTouchMode) {
+ case TOUCH_MODE_OVERFLING: {
+ mFlingRunnable.endFling();
+ mTouchMode = TOUCH_MODE_OVERSCROLL;
+ mMotionY = mLastY = (int) ev.getY();
+ mMotionCorrection = 0;
+ mActivePointerId = ev.getPointerId(0);
+ break;
+ }
- if (mTouchMode == TOUCH_MODE_FLING) {
- // Stopped a fling. It is a scroll.
- createScrollingCache();
- mTouchMode = TOUCH_MODE_SCROLL;
- mMotionCorrection = 0;
- motionPosition = findMotionRow(y);
- reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ default: {
+ mActivePointerId = ev.getPointerId(0);
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ int motionPosition = pointToPosition(x, y);
+ if (!mDataChanged) {
+ if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
+ && (getAdapter().isEnabled(motionPosition))) {
+ // User clicked on an actual view (and was not stopping a fling). It might be a
+ // click or a scroll. Assume it is a click until proven otherwise
+ mTouchMode = TOUCH_MODE_DOWN;
+ // FIXME Debounce
+ if (mPendingCheckForTap == null) {
+ mPendingCheckForTap = new CheckForTap();
+ }
+ postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+ } else {
+ if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
+ // If we couldn't find a view to click on, but the down event was touching
+ // the edge, we will bail out and try again. This allows the edge correcting
+ // code in ViewRoot to try to find a nearby view to select
+ return false;
+ }
+
+ if (mTouchMode == TOUCH_MODE_FLING) {
+ // Stopped a fling. It is a scroll.
+ createScrollingCache();
+ mTouchMode = TOUCH_MODE_SCROLL;
+ mMotionCorrection = 0;
+ motionPosition = findMotionRow(y);
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
+ }
}
}
- }
- if (motionPosition >= 0) {
- // Remember where the motion event started
- v = getChildAt(motionPosition - mFirstPosition);
- mMotionViewOriginalTop = v.getTop();
+ if (motionPosition >= 0) {
+ // Remember where the motion event started
+ v = getChildAt(motionPosition - mFirstPosition);
+ mMotionViewOriginalTop = v.getTop();
+ }
+ mMotionX = x;
+ mMotionY = y;
+ mMotionPosition = motionPosition;
+ mLastY = Integer.MIN_VALUE;
+ break;
+ }
}
- mMotionX = x;
- mMotionY = y;
- mMotionPosition = motionPosition;
- mLastY = Integer.MIN_VALUE;
break;
}
@@ -2056,9 +2183,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
if (y != mLastY) {
+ // We may be here after stopping a fling and continuing to scroll.
+ // If so, we haven't disallowed intercepting touch events yet.
+ // Make sure that we do so in case we're in a parent that can intercept.
+ if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
+ Math.abs(deltaY) > mTouchSlop) {
+ requestDisallowInterceptTouchEvent(true);
+ }
+
+ final int rawDeltaY = deltaY;
deltaY -= mMotionCorrection;
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+ final int motionIndex;
+ if (mMotionPosition >= 0) {
+ motionIndex = mMotionPosition - mFirstPosition;
+ } else {
+ // If we don't have a motion position that we can reliably track,
+ // pick something in the middle to make a best guess at things below.
+ motionIndex = getChildCount() / 2;
+ }
+
+ int motionViewPrevTop = 0;
+ View motionView = this.getChildAt(motionIndex);
+ if (motionView != null) {
+ motionViewPrevTop = motionView.getTop();
+ }
+
// No need to do all this work if we're not going to move anyway
boolean atEdge = false;
if (incrementalDeltaY != 0) {
@@ -2066,23 +2217,99 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
// Check to see if we have bumped into the scroll limit
- if (atEdge && getChildCount() > 0) {
- // Treat this like we're starting a new scroll from the current
- // position. This will let the user start scrolling back into
- // content immediately rather than needing to scroll back to the
- // point where they hit the limit first.
- int motionPosition = findMotionRow(y);
- if (motionPosition >= 0) {
- final View motionView = getChildAt(motionPosition - mFirstPosition);
- mMotionViewOriginalTop = motionView.getTop();
+ motionView = this.getChildAt(motionIndex);
+ if (motionView != null) {
+ // Check if the top of the motion view is where it is
+ // supposed to be
+ final int motionViewRealTop = motionView.getTop();
+ if (atEdge) {
+ // Apply overscroll
+
+ int overscroll = -incrementalDeltaY -
+ (motionViewRealTop - motionViewPrevTop);
+ overscrollBy(0, overscroll, 0, mScrollY, 0, 0,
+ 0, mOverscrollDistance, true);
+ if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
+ // Don't allow overfling if we're at the edge.
+ mVelocityTracker.clear();
+ }
+
+ final int overscrollMode = getOverscrollMode();
+ if (overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS &&
+ !contentFits())) {
+ mTouchMode = TOUCH_MODE_OVERSCROLL;
+ if (rawDeltaY > 0) {
+ mEdgeGlowTop.onPull((float) overscroll / getHeight());
+ } else if (rawDeltaY < 0) {
+ mEdgeGlowBottom.onPull((float) overscroll / getHeight());
+ }
+ }
}
mMotionY = y;
- mMotionPosition = motionPosition;
invalidate();
}
mLastY = y;
}
break;
+
+ case TOUCH_MODE_OVERSCROLL:
+ if (y != mLastY) {
+ final int rawDeltaY = deltaY;
+ deltaY -= mMotionCorrection;
+ int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+
+ final int oldScroll = mScrollY;
+ final int newScroll = oldScroll - incrementalDeltaY;
+
+ if ((oldScroll >= 0 && newScroll <= 0) ||
+ (oldScroll <= 0 && newScroll >= 0)) {
+ // Coming back to 'real' list scrolling
+ incrementalDeltaY = -newScroll;
+ mScrollY = 0;
+
+ // No need to do all this work if we're not going to move anyway
+ if (incrementalDeltaY != 0) {
+ trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
+ }
+
+ // Check to see if we are back in
+ View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+ if (motionView != null) {
+ mTouchMode = TOUCH_MODE_SCROLL;
+
+ // We did not scroll the full amount. Treat this essentially like the
+ // start of a new touch scroll
+ final int motionPosition = findClosestMotionRow(y);
+
+ mMotionCorrection = 0;
+ motionView = getChildAt(motionPosition - mFirstPosition);
+ mMotionViewOriginalTop = motionView.getTop();
+ mMotionY = y;
+ mMotionPosition = motionPosition;
+ }
+ } else {
+ overscrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0,
+ 0, mOverscrollDistance, true);
+ final int overscrollMode = getOverscrollMode();
+ if (overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS &&
+ !contentFits())) {
+ if (rawDeltaY > 0) {
+ mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight());
+ } else if (rawDeltaY < 0) {
+ mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight());
+ }
+ invalidate();
+ }
+ if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
+ // Don't allow overfling if we're at the edge.
+ mVelocityTracker.clear();
+ }
+ }
+ mLastY = y;
+ }
+ break;
}
break;
@@ -2154,18 +2381,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case TOUCH_MODE_SCROLL:
final int childCount = getChildCount();
if (childCount > 0) {
- if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top &&
+ final int firstChildTop = getChildAt(0).getTop();
+ final int lastChildBottom = getChildAt(childCount - 1).getBottom();
+ final int contentTop = mListPadding.top;
+ final int contentBottom = getHeight() - mListPadding.bottom;
+ if (mFirstPosition == 0 && firstChildTop >= contentTop &&
mFirstPosition + childCount < mItemCount &&
- getChildAt(childCount - 1).getBottom() <=
- getHeight() - mListPadding.bottom) {
+ lastChildBottom <= getHeight() - contentBottom) {
mTouchMode = TOUCH_MODE_REST;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
} else {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
-
- if (Math.abs(initialVelocity) > mMinimumVelocity) {
+
+ // Fling if we have enough velocity and we aren't at a boundary.
+ // Since we can potentially overfling more than we can overscroll, don't
+ // allow the weird behavior where you can scroll to a boundary then
+ // fling further.
+ if (Math.abs(initialVelocity) > mMinimumVelocity &&
+ !((mFirstPosition == 0 &&
+ firstChildTop == contentTop - mOverscrollDistance) ||
+ (mFirstPosition + childCount == mItemCount &&
+ lastChildBottom == contentBottom + mOverscrollDistance))) {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
@@ -2182,10 +2420,32 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
}
break;
+
+ case TOUCH_MODE_OVERSCROLL:
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ }
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+
+ reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+ if (Math.abs(initialVelocity) > mMinimumVelocity) {
+ mFlingRunnable.startOverfling(-initialVelocity);
+ } else {
+ mFlingRunnable.startSpringback();
+ }
+
+ break;
}
setPressed(false);
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
+ }
+
// Need to redraw since we probably aren't drawing the selector anymore
invalidate();
@@ -2211,24 +2471,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
case MotionEvent.ACTION_CANCEL: {
- mTouchMode = TOUCH_MODE_REST;
- setPressed(false);
- View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
- if (motionView != null) {
- motionView.setPressed(false);
- }
- clearScrollingCache();
+ switch (mTouchMode) {
+ case TOUCH_MODE_OVERSCROLL:
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ }
+ mFlingRunnable.startSpringback();
+ break;
- final Handler handler = getHandler();
- if (handler != null) {
- handler.removeCallbacks(mPendingCheckForLongPress);
- }
+ case TOUCH_MODE_OVERFLING:
+ // Do nothing - let it play out.
+ break;
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
+ default:
+ mTouchMode = TOUCH_MODE_REST;
+ setPressed(false);
+ View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+ if (motionView != null) {
+ motionView.setPressed(false);
+ }
+ clearScrollingCache();
+
+ final Handler handler = getHandler();
+ if (handler != null) {
+ handler.removeCallbacks(mPendingCheckForLongPress);
+ }
+
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
}
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
+ }
mActivePointerId = INVALID_POINTER;
break;
}
@@ -2253,10 +2531,61 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
@Override
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ mScrollY = scrollY;
+
+ if (clampedY) {
+ // Velocity is broken by hitting the limit; don't start a fling off of this.
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ }
+ }
+ awakenScrollBars();
+ }
+
+ @Override
public void draw(Canvas canvas) {
super.draw(canvas);
+ if (mEdgeGlowTop != null) {
+ final int scrollY = mScrollY;
+ if (!mEdgeGlowTop.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+
+ canvas.translate(-width / 2, Math.min(0, scrollY + mFirstPositionDistanceGuess));
+ mEdgeGlowTop.setSize(width * 2, getHeight());
+ if (mEdgeGlowTop.draw(canvas)) {
+ invalidate();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mEdgeGlowBottom.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight();
+
+ canvas.translate(-width / 2,
+ Math.max(height, scrollY + mLastPositionDistanceGuess));
+ canvas.rotate(180, width, 0);
+ mEdgeGlowBottom.setSize(width * 2, height);
+ if (mEdgeGlowBottom.draw(canvas)) {
+ invalidate();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ }
if (mFastScroller != null) {
- mFastScroller.draw(canvas);
+ final int scrollY = mScrollY;
+ if (scrollY != 0) {
+ // Pin to the top/bottom during overscroll
+ int restoreCount = canvas.save();
+ canvas.translate(0, (float) scrollY);
+ mFastScroller.draw(canvas);
+ canvas.restoreToCount(restoreCount);
+ } else {
+ mFastScroller.draw(canvas);
+ }
}
}
@@ -2275,6 +2604,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
int touchMode = mTouchMode;
+ if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
+ mMotionCorrection = 0;
+ return true;
+ }
final int x = (int) ev.getX();
final int y = (int) ev.getY();
@@ -2339,6 +2672,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mMotionX = (int) ev.getX(newPointerIndex);
mMotionY = (int) ev.getY(newPointerIndex);
+ mMotionCorrection = 0;
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
@@ -2394,7 +2728,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Tracks the decay of a fling scroll
*/
- private final Scroller mScroller;
+ private final OverScroller mScroller;
/**
* Y value reported by mScroller on the previous fling
@@ -2402,7 +2736,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private int mLastFlingY;
FlingRunnable() {
- mScroller = new Scroller(getContext());
+ mScroller = new OverScroller(getContext());
}
void start(int initialVelocity) {
@@ -2421,6 +2755,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ void startSpringback() {
+ if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
+ mTouchMode = TOUCH_MODE_OVERFLING;
+ invalidate();
+ post(this);
+ } else {
+ mTouchMode = TOUCH_MODE_REST;
+ }
+ }
+
+ void startOverfling(int initialVelocity) {
+ final int min = mScrollY > 0 ? Integer.MIN_VALUE : 0;
+ final int max = mScrollY > 0 ? 0 : Integer.MAX_VALUE;
+ mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, min, max, 0, getHeight());
+ mTouchMode = TOUCH_MODE_OVERFLING;
+ invalidate();
+ post(this);
+ }
+
+ void edgeReached(int delta) {
+ mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
+ final int overscrollMode = getOverscrollMode();
+ if (overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
+ mTouchMode = TOUCH_MODE_OVERFLING;
+ final int vel = (int) mScroller.getCurrVelocity();
+ if (delta > 0) {
+ mEdgeGlowTop.onAbsorb(vel);
+ } else {
+ mEdgeGlowBottom.onAbsorb(vel);
+ }
+ }
+ invalidate();
+ post(this);
+ }
+
void startScroll(int distance, int duration) {
int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
mLastFlingY = initialY;
@@ -2453,7 +2823,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return;
}
- final Scroller scroller = mScroller;
+ final OverScroller scroller = mScroller;
boolean more = scroller.computeScrollOffset();
final int y = scroller.getCurrY();
@@ -2482,7 +2852,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
}
+ // Check to see if we have bumped into the scroll limit
+ View motionView = getChildAt(mMotionPosition - mFirstPosition);
+ int oldTop = 0;
+ if (motionView != null) {
+ oldTop = motionView.getTop();
+ }
+
final boolean atEnd = trackMotionScroll(delta, delta);
+ if (atEnd) {
+ if (motionView != null) {
+ // Tweak the scroll for how far we overshot
+ int overshoot = -(delta - (motionView.getTop() - oldTop));
+ overscrollBy(0, overshoot, 0, mScrollY, 0, 0,
+ 0, mOverflingDistance, false);
+ }
+ edgeReached(delta);
+ break;
+ }
if (more && !atEnd) {
invalidate();
@@ -2500,6 +2887,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
break;
}
+
+ case TOUCH_MODE_OVERFLING: {
+ final OverScroller scroller = mScroller;
+ if (scroller.computeScrollOffset()) {
+ final int scrollY = mScrollY;
+ final int deltaY = scroller.getCurrY() - scrollY;
+ if (overscrollBy(0, deltaY, 0, scrollY, 0, 0,
+ 0, mOverflingDistance, false)) {
+ startSpringback();
+ } else {
+ invalidate();
+ post(this);
+ }
+ } else {
+ endFling();
+ }
+ break;
+ }
}
}
@@ -2856,16 +3261,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final int firstPosition = mFirstPosition;
- if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
+ // Update our guesses for where the first and last views are
+ if (firstPosition == 0) {
+ mFirstPositionDistanceGuess = firstTop - mListPadding.top;
+ } else {
+ mFirstPositionDistanceGuess += incrementalDeltaY;
+ }
+ if (firstPosition + childCount == mItemCount) {
+ mLastPositionDistanceGuess = lastBottom + mListPadding.bottom;
+ } else {
+ mLastPositionDistanceGuess += incrementalDeltaY;
+ }
+
+ if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) {
// Don't need to move views down if the top of the first position
// is already visible
- return true;
+ return incrementalDeltaY != 0;
}
- if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
+ if (firstPosition + childCount == mItemCount && lastBottom <= end &&
+ incrementalDeltaY <= 0) {
// Don't need to move views up if the bottom of the last position
// is already visible
- return true;
+ return incrementalDeltaY != 0;
}
final boolean down = incrementalDeltaY < 0;
@@ -3799,6 +4217,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return result;
}
+ private void finishGlows() {
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.finish();
+ mEdgeGlowBottom.finish();
+ }
+ }
+
/**
* Sets the recycler listener to be notified whenever a View is set aside in
* the recycler for later reuse. This listener can be used to free resources
@@ -3822,7 +4247,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* View type for this view, as returned by
* {@link android.widget.Adapter#getItemViewType(int) }
*/
- @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.ExportedProperty(category = "list", mapping = {
@ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
@ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
})
@@ -3834,7 +4259,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* been added to the list view and whether they should be treated as
* recycled views or not.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "list")
boolean recycledHeaderFooter;
/**
@@ -3845,7 +4270,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* view to be attached to the window rather than just attached to the
* parent.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "list")
boolean forceAdd;
public LayoutParams(Context c, AttributeSet attrs) {
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index fe6d91ac9d63..8fcc2e87a966 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -56,7 +56,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
/**
* The position of the first child displayed
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "scrolling")
int mFirstPosition = 0;
/**
@@ -141,7 +141,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
* The position within the adapter's data set of the item to select
* during the next layout.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "list")
int mNextSelectedPosition = INVALID_POSITION;
/**
@@ -152,7 +152,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
/**
* The position within the adapter's data set of the currently selected item.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "list")
int mSelectedPosition = INVALID_POSITION;
/**
@@ -168,7 +168,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
/**
* The number of items in the current adapter.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "list")
int mItemCount;
/**
@@ -808,7 +808,6 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
- checkSelectionChanged();
checkFocus();
requestLayout();
@@ -819,13 +818,21 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
- private class SelectionNotifier extends Handler implements Runnable {
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeCallbacks(mSelectionNotifier);
+ }
+
+ private class SelectionNotifier implements Runnable {
public void run() {
if (mDataChanged) {
// Data has changed between when this SelectionNotifier
// was posted and now. We need to wait until the AdapterView
// has been synched to the new data.
- post(this);
+ if (getAdapter() != null) {
+ post(this);
+ }
} else {
fireOnSelected();
}
@@ -842,7 +849,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
if (mSelectionNotifier == null) {
mSelectionNotifier = new SelectionNotifier();
}
- mSelectionNotifier.post(mSelectionNotifier);
+ post(mSelectionNotifier);
} else {
fireOnSelected();
}
diff --git a/core/java/android/widget/CursorTreeAdapter.java b/core/java/android/widget/CursorTreeAdapter.java
index 7b9b7bdafc0f..3fadf4c54075 100644
--- a/core/java/android/widget/CursorTreeAdapter.java
+++ b/core/java/android/widget/CursorTreeAdapter.java
@@ -134,14 +134,16 @@ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implem
/**
* Sets the group Cursor.
*
- * @param cursor The Cursor to set for the group.
+ * @param cursor The Cursor to set for the group. If there is an existing cursor
+ * it will be closed.
*/
public void setGroupCursor(Cursor cursor) {
mGroupCursorHelper.changeCursor(cursor, false);
}
/**
- * Sets the children Cursor for a particular group.
+ * Sets the children Cursor for a particular group. If there is an existing cursor
+ * it will be closed.
* <p>
* This is useful when asynchronously querying to prevent blocking the UI.
*
@@ -476,7 +478,7 @@ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implem
mCursor.unregisterContentObserver(mContentObserver);
mCursor.unregisterDataSetObserver(mDataSetObserver);
- mCursor.deactivate();
+ mCursor.close();
mCursor = null;
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index a4ee68af791d..8aed454a4715 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -331,6 +331,7 @@ public class DatePicker extends FrameLayout {
mYear = ss.getYear();
mMonth = ss.getMonth();
mDay = ss.getDay();
+ updateSpinners();
}
/**
diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java
new file mode 100644
index 000000000000..93222e1c00fc
--- /dev/null
+++ b/core/java/android/widget/EdgeGlow.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * This class performs the glow effect used at the edges of scrollable widgets.
+ * @hide
+ */
+public class EdgeGlow {
+ private static final String TAG = "EdgeGlow";
+
+ // Time it will take the effect to fully recede in ms
+ private static final int RECEDE_TIME = 1000;
+
+ // Time it will take before a pulled glow begins receding
+ private static final int PULL_TIME = 250;
+
+ // Time it will take for a pulled glow to decay to partial strength before release
+ private static final int PULL_DECAY_TIME = 1000;
+
+ private static final float HELD_EDGE_ALPHA = 0.7f;
+ private static final float HELD_EDGE_SCALE_Y = 0.5f;
+ private static final float HELD_GLOW_ALPHA = 0.5f;
+ private static final float HELD_GLOW_SCALE_Y = 0.5f;
+
+ private static final float MAX_GLOW_HEIGHT = 0.33f;
+
+ private static final float PULL_GLOW_BEGIN = 0.5f;
+ private static final float PULL_EDGE_BEGIN = 0.6f;
+
+ // Minimum velocity that will be absorbed
+ private static final int MIN_VELOCITY = 750;
+
+ private static final float EPSILON = 0.001f;
+
+ private Drawable mEdge;
+ private Drawable mGlow;
+ private int mWidth;
+ private int mHeight;
+
+ private float mEdgeAlpha;
+ private float mEdgeScaleY;
+ private float mGlowAlpha;
+ private float mGlowScaleY;
+
+ private float mEdgeAlphaStart;
+ private float mEdgeAlphaFinish;
+ private float mEdgeScaleYStart;
+ private float mEdgeScaleYFinish;
+ private float mGlowAlphaStart;
+ private float mGlowAlphaFinish;
+ private float mGlowScaleYStart;
+ private float mGlowScaleYFinish;
+
+ private long mStartTime;
+ private int mDuration;
+
+ private Interpolator mInterpolator;
+
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_PULL = 1;
+ private static final int STATE_ABSORB = 2;
+ private static final int STATE_RECEDE = 3;
+ private static final int STATE_PULL_DECAY = 4;
+
+ private int mState = STATE_IDLE;
+
+ private float mPullDistance;
+
+ public EdgeGlow(Drawable edge, Drawable glow) {
+ mEdge = edge;
+ mGlow = glow;
+
+ mInterpolator = new DecelerateInterpolator();
+ }
+
+ public void setSize(int width, int height) {
+ mWidth = width;
+ mHeight = height;
+ }
+
+ public boolean isFinished() {
+ return mState == STATE_IDLE;
+ }
+
+ public void finish() {
+ mState = STATE_IDLE;
+ }
+
+ /**
+ * Call when the object is pulled by the user.
+ * @param deltaDistance Change in distance since the last call
+ */
+ public void onPull(float deltaDistance) {
+ final long now = AnimationUtils.currentAnimationTimeMillis();
+ if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
+ return;
+ }
+ if (mState != STATE_PULL) {
+ mGlowScaleY = PULL_GLOW_BEGIN;
+ }
+ mState = STATE_PULL;
+
+ mStartTime = now;
+ mDuration = PULL_TIME;
+
+ mPullDistance += deltaDistance;
+ float distance = Math.abs(mPullDistance);
+
+ mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, 1.f));
+ mEdgeScaleY = mEdgeScaleYStart = Math.max(HELD_EDGE_SCALE_Y, Math.min(distance, 2.f));
+
+ mGlowAlpha = mGlowAlphaStart = Math.max(0.5f,
+ Math.min(mGlowAlpha + Math.abs(deltaDistance), 1.f));
+
+ float glowChange = Math.abs(deltaDistance);
+ if (deltaDistance > 0 && mPullDistance < 0) {
+ glowChange = -glowChange;
+ }
+ if (mPullDistance == 0) {
+ mGlowScaleY = 0;
+ }
+ mGlowScaleY = mGlowScaleYStart = Math.max(0, mGlowScaleY + glowChange * 2);
+
+ mEdgeAlphaFinish = mEdgeAlpha;
+ mEdgeScaleYFinish = mEdgeScaleY;
+ mGlowAlphaFinish = mGlowAlpha;
+ mGlowScaleYFinish = mGlowScaleY;
+ }
+
+ /**
+ * Call when the object is released after being pulled.
+ */
+ public void onRelease() {
+ mPullDistance = 0;
+
+ if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
+ return;
+ }
+
+ mState = STATE_RECEDE;
+ mEdgeAlphaStart = mEdgeAlpha;
+ mEdgeScaleYStart = mEdgeScaleY;
+ mGlowAlphaStart = mGlowAlpha;
+ mGlowScaleYStart = mGlowScaleY;
+
+ mEdgeAlphaFinish = 0.f;
+ mEdgeScaleYFinish = 0.1f;
+ mGlowAlphaFinish = 0.f;
+ mGlowScaleYFinish = 0.1f;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = RECEDE_TIME;
+ }
+
+ /**
+ * Call when the effect absorbs an impact at the given velocity.
+ * @param velocity Velocity at impact in pixels per second.
+ */
+ public void onAbsorb(int velocity) {
+ mState = STATE_ABSORB;
+ velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = (int) (velocity * 0.03f);
+
+ mEdgeAlphaStart = 0.5f;
+ mEdgeScaleYStart = 0.2f;
+ mGlowAlphaStart = 0.5f;
+ mGlowScaleYStart = 0.f;
+
+ mEdgeAlphaFinish = Math.max(0, Math.min(velocity * 0.01f, 1));
+ mEdgeScaleYFinish = 1.f;
+ mGlowAlphaFinish = 1.f;
+ mGlowScaleYFinish = Math.min(velocity * 0.001f, 1);
+ }
+
+ /**
+ * Draw into the provided canvas.
+ * Assumes that the canvas has been rotated accordingly and the size has been set.
+ * The effect will be drawn the full width of X=0 to X=width, emitting from Y=0 and extending
+ * to some factor < 1.f of height.
+ *
+ * @param canvas Canvas to draw into
+ * @return true if drawing should continue beyond this frame to continue the animation
+ */
+ public boolean draw(Canvas canvas) {
+ update();
+
+ final int edgeHeight = mEdge.getIntrinsicHeight();
+ final int glowHeight = mGlow.getIntrinsicHeight();
+
+ final float distScale = (float) mHeight / mWidth;
+
+ mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
+ mGlow.setBounds(0, 0, mWidth, (int) Math.min(glowHeight * mGlowScaleY * distScale * 0.6f,
+ mHeight * MAX_GLOW_HEIGHT));
+ mGlow.draw(canvas);
+
+ mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
+ mEdge.setBounds(0,
+ 0,
+ mWidth,
+ (int) (edgeHeight * mEdgeScaleY));
+ mEdge.draw(canvas);
+
+ return mState != STATE_IDLE;
+ }
+
+ private void update() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final float t = Math.min((float) (time - mStartTime) / mDuration, 1.f);
+
+ final float interp = mInterpolator.getInterpolation(t);
+
+ mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
+ mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
+ mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
+ mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+
+ if (t >= 1.f - EPSILON) {
+ switch (mState) {
+ case STATE_ABSORB:
+ mState = STATE_RECEDE;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = RECEDE_TIME;
+
+ mEdgeAlphaStart = mEdgeAlpha;
+ mEdgeScaleYStart = mEdgeScaleY;
+ mGlowAlphaStart = mGlowAlpha;
+ mGlowScaleYStart = mGlowScaleY;
+
+ mEdgeAlphaFinish = 0.f;
+ mEdgeScaleYFinish = 0.1f;
+ mGlowAlphaFinish = 0.f;
+ mGlowScaleYFinish = mGlowScaleY;
+ break;
+ case STATE_PULL:
+ mState = STATE_PULL_DECAY;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = PULL_DECAY_TIME;
+
+ mEdgeAlphaStart = mEdgeAlpha;
+ mEdgeScaleYStart = mEdgeScaleY;
+ mGlowAlphaStart = mGlowAlpha;
+ mGlowScaleYStart = mGlowScaleY;
+
+ mEdgeAlphaFinish = Math.min(mEdgeAlphaStart, HELD_EDGE_ALPHA);
+ mEdgeScaleYFinish = Math.min(mEdgeScaleYStart, HELD_EDGE_SCALE_Y);
+ mGlowAlphaFinish = Math.min(mGlowAlphaStart, HELD_GLOW_ALPHA);
+ mGlowScaleYFinish = Math.min(mGlowScaleY, HELD_GLOW_SCALE_Y);
+ break;
+ case STATE_PULL_DECAY:
+ // Do nothing; wait for release
+ break;
+ case STATE_RECEDE:
+ mState = STATE_IDLE;
+ break;
+ }
+ }
+ }
+}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index e27bb4fe2073..e4451800752b 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -46,27 +46,32 @@ import android.widget.RemoteViews.RemoteView;
*/
@RemoteView
public class FrameLayout extends ViewGroup {
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "measurement")
boolean mMeasureAllChildren = false;
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
private Drawable mForeground;
- @ViewDebug.ExportedProperty
+
+ @ViewDebug.ExportedProperty(category = "padding")
private int mForegroundPaddingLeft = 0;
- @ViewDebug.ExportedProperty
+
+ @ViewDebug.ExportedProperty(category = "padding")
private int mForegroundPaddingTop = 0;
- @ViewDebug.ExportedProperty
+
+ @ViewDebug.ExportedProperty(category = "padding")
private int mForegroundPaddingRight = 0;
- @ViewDebug.ExportedProperty
+
+ @ViewDebug.ExportedProperty(category = "padding")
private int mForegroundPaddingBottom = 0;
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
- @ViewDebug.ExportedProperty
+
+ @ViewDebug.ExportedProperty(category = "drawing")
private int mForegroundGravity = Gravity.FILL;
/** {@hide} */
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "drawing")
protected boolean mForegroundInPadding = true;
boolean mForegroundBoundsChanged = false;
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index aea804291c0c..ccd37d35263b 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -1090,7 +1090,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Gallery steals all key events
- return event.dispatch(this);
+ return event.dispatch(this, null, null);
}
/**
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 2f86d756b07a..a7300c288bf3 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1876,7 +1876,12 @@ public class GridView extends AbsListView {
// TODO: Account for vertical spacing too
final int numColumns = mNumColumns;
final int rowCount = (mItemCount + numColumns - 1) / numColumns;
- return Math.max(rowCount * 100, 0);
+ int result = Math.max(rowCount * 100, 0);
+ if (mScrollY != 0) {
+ // Compensate for overscroll
+ result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
+ }
+ return result;
}
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 32a91464f4be..0bb97dd359af 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,19 +16,24 @@
package android.widget;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
+import com.android.internal.R;
+
import android.util.AttributeSet;
-import android.view.FocusFinder;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.view.View;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import java.util.List;
@@ -63,7 +68,9 @@ public class HorizontalScrollView extends FrameLayout {
private long mLastScroll;
private final Rect mTempRect = new Rect();
- private Scroller mScroller;
+ private OverScroller mScroller;
+ private EdgeGlow mEdgeGlowLeft;
+ private EdgeGlow mEdgeGlowRight;
/**
* Flag to indicate that we are moving focus ourselves. This is so the
@@ -117,6 +124,9 @@ public class HorizontalScrollView extends FrameLayout {
private int mMinimumVelocity;
private int mMaximumVelocity;
+ private int mOverscrollDistance;
+ private int mOverflingDistance;
+
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
@@ -189,7 +199,7 @@ public class HorizontalScrollView extends FrameLayout {
private void initScrollView() {
- mScroller = new Scroller(getContext());
+ mScroller = new OverScroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
@@ -197,6 +207,8 @@ public class HorizontalScrollView extends FrameLayout {
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mOverscrollDistance = configuration.getScaledOverscrollDistance();
+ mOverflingDistance = configuration.getScaledOverflingDistance();
}
@Override
@@ -456,6 +468,9 @@ public class HorizontalScrollView extends FrameLayout {
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
+ if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
+ invalidate();
+ }
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
@@ -513,7 +528,26 @@ public class HorizontalScrollView extends FrameLayout {
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
- scrollBy(deltaX, 0);
+ final int oldX = mScrollX;
+ final int oldY = mScrollY;
+ final int range = getScrollRange();
+ if (overscrollBy(deltaX, 0, mScrollX, 0, range, 0,
+ mOverscrollDistance, 0, true)) {
+ // Break our velocity if we hit a scroll barrier.
+ mVelocityTracker.clear();
+ }
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+ final int overscrollMode = getOverscrollMode();
+ if (overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+ final int pulledToX = oldX + deltaX;
+ if (pulledToX < 0) {
+ mEdgeGlowLeft.onPull((float) deltaX / getWidth());
+ } else if (pulledToX > range) {
+ mEdgeGlowRight.onPull((float) deltaX / getWidth());
+ }
+ }
}
break;
case MotionEvent.ACTION_UP:
@@ -522,8 +556,15 @@ public class HorizontalScrollView extends FrameLayout {
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
- if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
- fling(-initialVelocity);
+ if (getChildCount() > 0) {
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ fling(-initialVelocity);
+ } else {
+ final int right = getScrollRange();
+ if (mScroller.springback(mScrollX, mScrollY, 0, right, 0, 0)) {
+ invalidate();
+ }
+ }
}
mActivePointerId = INVALID_POINTER;
@@ -533,16 +574,27 @@ public class HorizontalScrollView extends FrameLayout {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+ if (mEdgeGlowLeft != null) {
+ mEdgeGlowLeft.onRelease();
+ mEdgeGlowRight.onRelease();
+ }
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
+ if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
+ invalidate();
+ }
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+ if (mEdgeGlowLeft != null) {
+ mEdgeGlowLeft.onRelease();
+ mEdgeGlowRight.onRelease();
+ }
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -569,6 +621,22 @@ public class HorizontalScrollView extends FrameLayout {
}
}
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ // Treat animating scrolls differently; see #computeScroll() for why.
+ if (!mScroller.isFinished()) {
+ mScrollX = scrollX;
+ mScrollY = scrollY;
+ if (clampedX) {
+ mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
+ }
+ } else {
+ super.scrollTo(scrollX, scrollY);
+ }
+ awakenScrollBars();
+ }
+
private int getScrollRange() {
int scrollRange = 0;
if (getChildCount() > 0) {
@@ -951,7 +1019,16 @@ public class HorizontalScrollView extends FrameLayout {
return contentWidth;
}
- return getChildAt(0).getRight();
+ int scrollRange = getChildAt(0).getRight();
+ final int scrollX = mScrollX;
+ final int overscrollRight = Math.max(0, scrollRange - contentWidth);
+ if (scrollX < 0) {
+ scrollRange -= scrollX;
+ } else if (scrollX > overscrollRight) {
+ scrollRange += scrollX - overscrollRight;
+ }
+
+ return scrollRange;
}
@Override
@@ -1012,14 +1089,20 @@ public class HorizontalScrollView extends FrameLayout {
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
- if (getChildCount() > 0) {
- View child = getChildAt(0);
- x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
- y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
- if (x != oldX || y != oldY) {
- mScrollX = x;
- mScrollY = y;
- onScrollChanged(x, y, oldX, oldY);
+ if (oldX != x || oldY != y) {
+ overscrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0,
+ mOverflingDistance, 0, false);
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+ final int range = getScrollRange();
+ final int overscrollMode = getOverscrollMode();
+ if (overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+ if (x < 0 && oldX >= 0) {
+ mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
+ } else if (x > range && oldX <= range) {
+ mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
+ }
}
}
awakenScrollBars();
@@ -1256,7 +1339,7 @@ public class HorizontalScrollView extends FrameLayout {
int right = getChildAt(0).getWidth();
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
- Math.max(0, right - width), 0, 0);
+ Math.max(0, right - width), 0, 0, width/2, 0);
final boolean movingRight = velocityX > 0;
@@ -1294,6 +1377,56 @@ public class HorizontalScrollView extends FrameLayout {
}
}
+ @Override
+ public void setOverscrollMode(int mode) {
+ if (mode != OVERSCROLL_NEVER) {
+ if (mEdgeGlowLeft == null) {
+ final Resources res = getContext().getResources();
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+ final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+ mEdgeGlowLeft = new EdgeGlow(edge, glow);
+ mEdgeGlowRight = new EdgeGlow(edge, glow);
+ }
+ } else {
+ mEdgeGlowLeft = null;
+ mEdgeGlowRight = null;
+ }
+ super.setOverscrollMode(mode);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mEdgeGlowLeft != null) {
+ final int scrollX = mScrollX;
+ if (!mEdgeGlowLeft.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int height = getHeight();
+
+ canvas.rotate(270);
+ canvas.translate(-height * 1.5f, Math.min(0, scrollX));
+ mEdgeGlowLeft.setSize(getHeight() * 2, getWidth());
+ if (mEdgeGlowLeft.draw(canvas)) {
+ invalidate();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mEdgeGlowRight.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight();
+
+ canvas.rotate(90);
+ canvas.translate(-height / 2, -(Math.max(getScrollRange(), scrollX) + width));
+ mEdgeGlowRight.setSize(height * 2, width);
+ if (mEdgeGlowRight.draw(canvas)) {
+ invalidate();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ }
+ }
+
private int clamp(int n, int my, int child) {
if (my >= child || n < 0) {
return 0;
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index c77416b34ba1..46f7db484b45 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -260,9 +260,15 @@ public class ImageView extends View {
/**
* Sets a drawable as the content of this ImageView.
- *
+ *
+ * <p class="note">This does Bitmap reading and decoding on the UI
+ * thread, which can cause a latency hiccup. If that's a concern,
+ * consider using {@link #setImageDrawable} or
+ * {@link #setImageBitmap} and
+ * {@link android.graphics.BitmapFactory} instead.</p>
+ *
* @param resId the resource identifier of the the drawable
- *
+ *
* @attr ref android.R.styleable#ImageView_src
*/
@android.view.RemotableViewMethod
@@ -279,7 +285,13 @@ public class ImageView extends View {
/**
* Sets the content of this ImageView to the specified Uri.
- *
+ *
+ * <p class="note">This does Bitmap reading and decoding on the UI
+ * thread, which can cause a latency hiccup. If that's a concern,
+ * consider using {@link #setImageDrawable} or
+ * {@link #setImageBitmap} and
+ * {@link android.graphics.BitmapFactory} instead.</p>
+ *
* @param uri The Uri of an image
*/
@android.view.RemotableViewMethod
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 3d7940c7ef7c..faf082dcd9c6 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -53,7 +53,7 @@ public class LinearLayout extends ViewGroup {
* Whether the children of this layout are baseline aligned. Only applicable
* if {@link #mOrientation} is horizontal.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
private boolean mBaselineAligned = true;
/**
@@ -63,7 +63,7 @@ public class LinearLayout extends ViewGroup {
* Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
* with whether the children of this layout are baseline aligned.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
private int mBaselineAlignedChildIndex = -1;
/**
@@ -71,12 +71,13 @@ public class LinearLayout extends ViewGroup {
* We'll calculate the baseline of this layout as we measure vertically; for
* horizontal linear layouts, the offset of 0 is appropriate.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "measurement")
private int mBaselineChildTop = 0;
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "measurement")
private int mOrientation;
- @ViewDebug.ExportedProperty(mapping = {
+
+ @ViewDebug.ExportedProperty(category = "measurement", mapping = {
@ViewDebug.IntToString(from = -1, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
@@ -91,13 +92,14 @@ public class LinearLayout extends ViewGroup {
@ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
})
private int mGravity = Gravity.LEFT | Gravity.TOP;
- @ViewDebug.ExportedProperty
+
+ @ViewDebug.ExportedProperty(category = "measurement")
private int mTotalLength;
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
private float mWeightSum;
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
private boolean mUseLargestChild;
private int[] mMaxAscent;
@@ -1367,7 +1369,7 @@ public class LinearLayout extends ViewGroup {
* 0 if the view should not be stretched. Otherwise the extra pixels
* will be pro-rated among all views whose weight is greater than 0.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public float weight;
/**
@@ -1375,7 +1377,7 @@ public class LinearLayout extends ViewGroup {
*
* @see android.view.Gravity
*/
- @ViewDebug.ExportedProperty(mapping = {
+ @ViewDebug.ExportedProperty(category = "layout", mapping = {
@ViewDebug.IntToString(from = -1, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
@ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 3015406fd5f6..46cd45aa7663 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -121,6 +121,9 @@ public class ListView extends AbsListView {
Drawable mDivider;
int mDividerHeight;
+ Drawable mOverscrollHeader;
+ Drawable mOverscrollFooter;
+
private boolean mIsCacheColorOpaque;
private boolean mDividerIsOpaque;
private boolean mClipDivider;
@@ -175,6 +178,16 @@ public class ListView extends AbsListView {
setDivider(d);
}
+ final Drawable osHeader = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollHeader);
+ if (osHeader != null) {
+ setOverscrollHeader(osHeader);
+ }
+
+ final Drawable osFooter = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollFooter);
+ if (osFooter != null) {
+ setOverscrollFooter(osFooter);
+ }
+
// Use the height specified, zero being the default
final int dividerHeight = a.getDimensionPixelSize(
com.android.internal.R.styleable.ListView_dividerHeight, 0);
@@ -1143,7 +1156,7 @@ public class ListView extends AbsListView {
* UNSPECIFIED/AT_MOST modes, false otherwise.
* @hide
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "list")
protected boolean recycleOnMeasure() {
return true;
}
@@ -2945,14 +2958,52 @@ public class ListView extends AbsListView {
}
super.setCacheColorHint(color);
}
-
+
+ void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
+ final int height = drawable.getMinimumHeight();
+
+ canvas.save();
+ canvas.clipRect(bounds);
+
+ final int span = bounds.bottom - bounds.top;
+ if (span < height) {
+ bounds.top = bounds.bottom - height;
+ }
+
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+
+ canvas.restore();
+ }
+
+ void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
+ final int height = drawable.getMinimumHeight();
+
+ canvas.save();
+ canvas.clipRect(bounds);
+
+ final int span = bounds.bottom - bounds.top;
+ if (span < height) {
+ bounds.bottom = bounds.top + height;
+ }
+
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+
+ canvas.restore();
+ }
+
@Override
protected void dispatchDraw(Canvas canvas) {
// Draw the dividers
final int dividerHeight = mDividerHeight;
+ final Drawable overscrollHeader = mOverscrollHeader;
+ final Drawable overscrollFooter = mOverscrollFooter;
+ final boolean drawOverscrollHeader = overscrollHeader != null;
+ final boolean drawOverscrollFooter = overscrollFooter != null;
final boolean drawDividers = dividerHeight > 0 && mDivider != null;
- if (drawDividers) {
+ if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
// Only modify the top and bottom in the loop, we set the left and right here
final Rect bounds = mTempRect;
bounds.left = mPaddingLeft;
@@ -2983,14 +3034,28 @@ public class ListView extends AbsListView {
if (!mStackFromBottom) {
int bottom = 0;
+ // Draw top divider or header for overscroll
final int scrollY = mScrollY;
+ if (count > 0 && scrollY < 0) {
+ if (drawOverscrollHeader) {
+ bounds.bottom = 0;
+ bounds.top = scrollY;
+ drawOverscrollHeader(canvas, overscrollHeader, bounds);
+ } else if (drawDividers) {
+ bounds.bottom = 0;
+ bounds.top = -dividerHeight;
+ drawDivider(canvas, bounds, -1);
+ }
+ }
+
for (int i = 0; i < count; i++) {
if ((headerDividers || first + i >= headerCount) &&
(footerDividers || first + i < footerLimit)) {
View child = getChildAt(i);
bottom = child.getBottom();
// Don't draw dividers next to items that are not enabled
- if (drawDividers) {
+ if (drawDividers &&
+ (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
if ((areAllItemsSelectable ||
(adapter.isEnabled(first + i) && (i == count - 1 ||
adapter.isEnabled(first + i + 1))))) {
@@ -3005,13 +3070,28 @@ public class ListView extends AbsListView {
}
}
}
+
+ final int overFooterBottom = mBottom + mScrollY;
+ if (drawOverscrollFooter && first + count == itemCount &&
+ overFooterBottom > bottom) {
+ bounds.top = bottom;
+ bounds.bottom = overFooterBottom;
+ drawOverscrollFooter(canvas, overscrollFooter, bounds);
+ }
} else {
int top;
int listTop = mListPadding.top;
final int scrollY = mScrollY;
- for (int i = 0; i < count; i++) {
+ if (count > 0 && drawOverscrollHeader) {
+ bounds.top = scrollY;
+ bounds.bottom = getChildAt(0).getTop();
+ drawOverscrollHeader(canvas, overscrollHeader, bounds);
+ }
+
+ final int start = drawOverscrollHeader ? 1 : 0;
+ for (int i = start; i < count; i++) {
if ((headerDividers || first + i >= headerCount) &&
(footerDividers || first + i < footerLimit)) {
View child = getChildAt(i);
@@ -3037,10 +3117,17 @@ public class ListView extends AbsListView {
}
}
- if (count > 0 && scrollY > 0 && drawDividers) {
- bounds.top = listBottom;
- bounds.bottom = listBottom + dividerHeight;
- drawDivider(canvas, bounds, -1);
+ if (count > 0 && scrollY > 0) {
+ if (drawOverscrollFooter) {
+ final int absListBottom = mBottom;
+ bounds.top = absListBottom;
+ bounds.bottom = absListBottom + scrollY;
+ drawOverscrollFooter(canvas, overscrollFooter, bounds);
+ } else if (drawDividers) {
+ bounds.top = listBottom;
+ bounds.bottom = listBottom + dividerHeight;
+ drawDivider(canvas, bounds, -1);
+ }
}
}
}
@@ -3149,6 +3236,45 @@ public class ListView extends AbsListView {
invalidate();
}
+ /**
+ * Sets the drawable that will be drawn above all other list content.
+ * This area can become visible when the user overscrolls the list.
+ *
+ * @param header The drawable to use
+ */
+ public void setOverscrollHeader(Drawable header) {
+ mOverscrollHeader = header;
+ if (mScrollY < 0) {
+ invalidate();
+ }
+ }
+
+ /**
+ * @return The drawable that will be drawn above all other list content
+ */
+ public Drawable getOverscrollHeader() {
+ return mOverscrollHeader;
+ }
+
+ /**
+ * Sets the drawable that will be drawn below all other list content.
+ * This area can become visible when the user overscrolls the list,
+ * or when the list's content does not fully fill the container area.
+ *
+ * @param footer The drawable to use
+ */
+ public void setOverscrollFooter(Drawable footer) {
+ mOverscrollFooter = footer;
+ invalidate();
+ }
+
+ /**
+ * @return The drawable that will be drawn below all other list content
+ */
+ public Drawable getOverscrollFooter() {
+ return mOverscrollFooter;
+ }
+
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index c246c247a53e..39b1377c1e68 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -123,7 +123,7 @@ public class MediaController extends FrameLayout {
}
private void initFloatingWindow() {
- mWindowManager = (WindowManager)mContext.getSystemService("window");
+ mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mWindow = PolicyManager.makeNewWindow(mContext);
mWindow.setWindowManager(mWindowManager, null, null);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
new file mode 100644
index 000000000000..78973ad435de
--- /dev/null
+++ b/core/java/android/widget/OverScroller.java
@@ -0,0 +1,849 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.util.FloatMath;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+/**
+ * This class encapsulates scrolling with the ability to overshoot the bounds
+ * of a scrolling operation. This class is a drop-in replacement for
+ * {@link android.widget.Scroller} in most cases.
+ */
+public class OverScroller {
+ private int mMode;
+
+ private MagneticOverScroller mScrollerX;
+ private MagneticOverScroller mScrollerY;
+
+ private final Interpolator mInterpolator;
+
+ private static final int DEFAULT_DURATION = 250;
+ private static final int SCROLL_MODE = 0;
+ private static final int FLING_MODE = 1;
+
+ /**
+ * Creates an OverScroller with a viscous fluid scroll interpolator.
+ * @param context
+ */
+ public OverScroller(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Creates an OverScroller with default edge bounce coefficients.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ */
+ public OverScroller(Context context, Interpolator interpolator) {
+ this(context, interpolator, MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT,
+ MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT);
+ }
+
+ /**
+ * Creates an OverScroller.
+ * @param context The context of this application.
+ * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+ * be used.
+ * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+ * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+ * means no bounce.
+ * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
+ */
+ public OverScroller(Context context, Interpolator interpolator,
+ float bounceCoefficientX, float bounceCoefficientY) {
+ mInterpolator = interpolator;
+ mScrollerX = new MagneticOverScroller();
+ mScrollerY = new MagneticOverScroller();
+ MagneticOverScroller.initializeFromContext(context);
+
+ mScrollerX.setBounceCoefficient(bounceCoefficientX);
+ mScrollerY.setBounceCoefficient(bounceCoefficientY);
+ }
+
+ /**
+ *
+ * Returns whether the scroller has finished scrolling.
+ *
+ * @return True if the scroller has finished scrolling, false otherwise.
+ */
+ public final boolean isFinished() {
+ return mScrollerX.mFinished && mScrollerY.mFinished;
+ }
+
+ /**
+ * Force the finished field to a particular value. Contrary to
+ * {@link #abortAnimation()}, forcing the animation to finished
+ * does NOT cause the scroller to move to the final x and y
+ * position.
+ *
+ * @param finished The new finished value.
+ */
+ public final void forceFinished(boolean finished) {
+ mScrollerX.mFinished = mScrollerY.mFinished = finished;
+ }
+
+ /**
+ * Returns the current X offset in the scroll.
+ *
+ * @return The new X offset as an absolute distance from the origin.
+ */
+ public final int getCurrX() {
+ return mScrollerX.mCurrentPosition;
+ }
+
+ /**
+ * Returns the current Y offset in the scroll.
+ *
+ * @return The new Y offset as an absolute distance from the origin.
+ */
+ public final int getCurrY() {
+ return mScrollerY.mCurrentPosition;
+ }
+
+ /**
+ * @hide
+ * Returns the current velocity.
+ *
+ * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
+ */
+ public float getCurrVelocity() {
+ float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
+ squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
+ return FloatMath.sqrt(squaredNorm);
+ }
+
+ /**
+ * Returns the start X offset in the scroll.
+ *
+ * @return The start X offset as an absolute distance from the origin.
+ */
+ public final int getStartX() {
+ return mScrollerX.mStart;
+ }
+
+ /**
+ * Returns the start Y offset in the scroll.
+ *
+ * @return The start Y offset as an absolute distance from the origin.
+ */
+ public final int getStartY() {
+ return mScrollerY.mStart;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final X offset as an absolute distance from the origin.
+ */
+ public final int getFinalX() {
+ return mScrollerX.mFinal;
+ }
+
+ /**
+ * Returns where the scroll will end. Valid only for "fling" scrolls.
+ *
+ * @return The final Y offset as an absolute distance from the origin.
+ */
+ public final int getFinalY() {
+ return mScrollerY.mFinal;
+ }
+
+ /**
+ * Returns how long the scroll event will take, in milliseconds.
+ *
+ * @return The duration of the scroll in milliseconds.
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * This function will lie to the best of its ability.
+ */
+ public final int getDuration() {
+ return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
+ }
+
+ /**
+ * Extend the scroll animation. This allows a running animation to scroll
+ * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+ *
+ * @param extend Additional time to scroll in milliseconds.
+ * @see #setFinalX(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScrollers don't necessarily have a fixed duration.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ public void extendDuration(int extend) {
+ mScrollerX.extendDuration(extend);
+ mScrollerY.extendDuration(extend);
+ }
+
+ /**
+ * Sets the final position (X) for this scroller.
+ *
+ * @param newX The new X offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalY(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ public void setFinalX(int newX) {
+ mScrollerX.setFinalPosition(newX);
+ }
+
+ /**
+ * Sets the final position (Y) for this scroller.
+ *
+ * @param newY The new Y offset as an absolute distance from the origin.
+ * @see #extendDuration(int)
+ * @see #setFinalX(int)
+ *
+ * @hide Pending removal once nothing depends on it
+ * @deprecated OverScroller's final position may change during an animation.
+ * Instead of setting a new final position and extending
+ * the duration of an existing scroll, use startScroll
+ * to begin a new animation.
+ */
+ public void setFinalY(int newY) {
+ mScrollerY.setFinalPosition(newY);
+ }
+
+ /**
+ * Call this when you want to know the new location. If it returns true, the
+ * animation is not yet finished.
+ */
+ public boolean computeScrollOffset() {
+ if (isFinished()) {
+ return false;
+ }
+
+ switch (mMode) {
+ case SCROLL_MODE:
+ long time = AnimationUtils.currentAnimationTimeMillis();
+ // Any scroller can be used for time, since they were started
+ // together in scroll mode. We use X here.
+ final long elapsedTime = time - mScrollerX.mStartTime;
+
+ final int duration = mScrollerX.mDuration;
+ if (elapsedTime < duration) {
+ float q = (float) (elapsedTime) / duration;
+
+ if (mInterpolator == null)
+ q = Scroller.viscousFluid(q);
+ else
+ q = mInterpolator.getInterpolation(q);
+
+ mScrollerX.updateScroll(q);
+ mScrollerY.updateScroll(q);
+ } else {
+ abortAnimation();
+ }
+ break;
+
+ case FLING_MODE:
+ if (!mScrollerX.mFinished) {
+ if (!mScrollerX.update()) {
+ if (!mScrollerX.continueWhenFinished()) {
+ mScrollerX.finish();
+ }
+ }
+ }
+
+ if (!mScrollerY.mFinished) {
+ if (!mScrollerY.update()) {
+ if (!mScrollerY.continueWhenFinished()) {
+ mScrollerY.finish();
+ }
+ }
+ }
+
+ break;
+ }
+
+ return true;
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ * The scroll will use the default value of 250 milliseconds for the
+ * duration.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy) {
+ startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+ }
+
+ /**
+ * Start scrolling by providing a starting point and the distance to travel.
+ *
+ * @param startX Starting horizontal scroll offset in pixels. Positive
+ * numbers will scroll the content to the left.
+ * @param startY Starting vertical scroll offset in pixels. Positive numbers
+ * will scroll the content up.
+ * @param dx Horizontal distance to travel. Positive numbers will scroll the
+ * content to the left.
+ * @param dy Vertical distance to travel. Positive numbers will scroll the
+ * content up.
+ * @param duration Duration of the scroll in milliseconds.
+ */
+ public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+ mMode = SCROLL_MODE;
+ mScrollerX.startScroll(startX, dx, duration);
+ mScrollerY.startScroll(startY, dy, duration);
+ }
+
+ /**
+ * Call this when you want to 'spring back' into a valid coordinate range.
+ *
+ * @param startX Starting X coordinate
+ * @param startY Starting Y coordinate
+ * @param minX Minimum valid X value
+ * @param maxX Maximum valid X value
+ * @param minY Minimum valid Y value
+ * @param maxY Minimum valid Y value
+ * @return true if a springback was initiated, false if startX and startY were
+ * already within the valid range.
+ */
+ public boolean springback(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+ mMode = FLING_MODE;
+
+ // Make sure both methods are called.
+ final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
+ final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
+ return spingbackX || spingbackY;
+ }
+
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY) {
+ fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
+ }
+
+ /**
+ * Start scrolling based on a fling gesture. The distance traveled will
+ * depend on the initial velocity of the fling.
+ *
+ * @param startX Starting point of the scroll (X)
+ * @param startY Starting point of the scroll (Y)
+ * @param velocityX Initial velocity of the fling (X) measured in pixels per
+ * second.
+ * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+ * second
+ * @param minX Minimum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use minX as
+ * a springback boundary.
+ * @param maxX Maximum X value. The scroller will not scroll past this point
+ * unless overX > 0. If overfling is allowed, it will use maxX as
+ * a springback boundary.
+ * @param minY Minimum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use minY as
+ * a springback boundary.
+ * @param maxY Maximum Y value. The scroller will not scroll past this point
+ * unless overY > 0. If overfling is allowed, it will use maxY as
+ * a springback boundary.
+ * @param overX Overfling range. If > 0, horizontal overfling in either
+ * direction will be possible.
+ * @param overY Overfling range. If > 0, vertical overfling in either
+ * direction will be possible.
+ */
+ public void fling(int startX, int startY, int velocityX, int velocityY,
+ int minX, int maxX, int minY, int maxY, int overX, int overY) {
+ mMode = FLING_MODE;
+ mScrollerX.fling(startX, velocityX, minX, maxX, overX);
+ mScrollerY.fling(startY, velocityY, minY, maxY, overY);
+ }
+
+ /**
+ * Notify the scroller that we've reached a horizontal boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will transition the current motion and
+ * animate from startX to finalX as appropriate.
+ *
+ * @param startX Starting/current X position
+ * @param finalX Desired final X position
+ * @param overX Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalX. Absolute value - must be positive.
+ */
+ public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
+ mScrollerX.notifyEdgeReached(startX, finalX, overX);
+ }
+
+ /**
+ * Notify the scroller that we've reached a vertical boundary.
+ * Normally the information to handle this will already be known
+ * when the animation is started, such as in a call to one of the
+ * fling functions. However there are cases where this cannot be known
+ * in advance. This function will animate a parabolic motion from
+ * startY to finalY.
+ *
+ * @param startY Starting/current Y position
+ * @param finalY Desired final Y position
+ * @param overY Magnitude of overscroll allowed. This should be the maximum
+ * desired distance from finalY.
+ */
+ public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
+ mScrollerY.notifyEdgeReached(startY, finalY, overY);
+ }
+
+ /**
+ * Returns whether the current Scroller is currently returning to a valid position.
+ * Valid bounds were provided by the
+ * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
+ *
+ * One should check this value before calling
+ * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
+ * to restore a valid position will then be stopped. The caller has to take into account
+ * the fact that the started scroll will start from an overscrolled position.
+ *
+ * @return true when the current position is overscrolled and in the process of
+ * interpolating back to a valid value.
+ */
+ public boolean isOverscrolled() {
+ return ((!mScrollerX.mFinished &&
+ mScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
+ (!mScrollerY.mFinished &&
+ mScrollerY.mState != MagneticOverScroller.TO_EDGE));
+ }
+
+ /**
+ * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+ * aborting the animating causes the scroller to move to the final x and y
+ * positions.
+ *
+ * @see #forceFinished(boolean)
+ */
+ public void abortAnimation() {
+ mScrollerX.finish();
+ mScrollerY.finish();
+ }
+
+ /**
+ * Returns the time elapsed since the beginning of the scrolling.
+ *
+ * @return The elapsed time in milliseconds.
+ *
+ * @hide
+ */
+ public int timePassed() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
+ return (int) (time - startTime);
+ }
+
+ static class MagneticOverScroller {
+ // Initial position
+ int mStart;
+
+ // Current position
+ int mCurrentPosition;
+
+ // Final position
+ int mFinal;
+
+ // Initial velocity
+ int mVelocity;
+
+ // Current velocity
+ float mCurrVelocity;
+
+ // Constant current deceleration
+ float mDeceleration;
+
+ // Animation starting time, in system milliseconds
+ long mStartTime;
+
+ // Animation duration, in milliseconds
+ int mDuration;
+
+ // Whether the animation is currently in progress
+ boolean mFinished;
+
+ // Constant gravity value, used to scale deceleration
+ static float GRAVITY;
+
+ static void initializeFromContext(Context context) {
+ final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+ GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2)
+ * 39.37f // inch/meter
+ * ppi // pixels per inch
+ * ViewConfiguration.getScrollFriction();
+ }
+
+ private static final int TO_EDGE = 0;
+ private static final int TO_BOUNDARY = 1;
+ private static final int TO_BOUNCE = 2;
+
+ private int mState = TO_EDGE;
+
+ // The allowed overshot distance before boundary is reached.
+ private int mOver;
+
+ // Duration in milliseconds to go back from edge to edge. Springback is half of it.
+ private static final int OVERSCROLL_SPRINGBACK_DURATION = 200;
+
+ // Oscillation period
+ private static final float TIME_COEF =
+ 1000.0f * (float) Math.PI / OVERSCROLL_SPRINGBACK_DURATION;
+
+ // If the velocity is smaller than this value, no bounce is triggered
+ // when the edge limits are reached (would result in a zero pixels
+ // displacement anyway).
+ private static final float MINIMUM_VELOCITY_FOR_BOUNCE = Float.MAX_VALUE;//140.0f;
+
+ // Proportion of the velocity that is preserved when the edge is reached.
+ private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.16f;
+
+ private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
+
+ MagneticOverScroller() {
+ mFinished = true;
+ }
+
+ void updateScroll(float q) {
+ mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+ }
+
+ /*
+ * Get a signed deceleration that will reduce the velocity.
+ */
+ static float getDeceleration(int velocity) {
+ return velocity > 0 ? -GRAVITY : GRAVITY;
+ }
+
+ /*
+ * Returns the time (in milliseconds) it will take to go from start to end.
+ */
+ static int computeDuration(int start, int end, float initialVelocity, float deceleration) {
+ final int distance = start - end;
+ final float discriminant = initialVelocity * initialVelocity - 2.0f * deceleration
+ * distance;
+ if (discriminant >= 0.0f) {
+ float delta = (float) Math.sqrt(discriminant);
+ if (deceleration < 0.0f) {
+ delta = -delta;
+ }
+ return (int) (1000.0f * (-initialVelocity - delta) / deceleration);
+ }
+
+ // End position can not be reached
+ return 0;
+ }
+
+ void startScroll(int start, int distance, int duration) {
+ mFinished = false;
+
+ mStart = start;
+ mFinal = start + distance;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = duration;
+
+ // Unused
+ mDeceleration = 0.0f;
+ mVelocity = 0;
+ }
+
+ void fling(int start, int velocity, int min, int max) {
+ mFinished = false;
+
+ mStart = start;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+
+ mVelocity = velocity;
+
+ mDeceleration = getDeceleration(velocity);
+
+ // A start from an invalid position immediately brings back to a valid position
+ if (mStart < min) {
+ mDuration = 0;
+ mFinal = min;
+ return;
+ }
+
+ if (mStart > max) {
+ mDuration = 0;
+ mFinal = max;
+ return;
+ }
+
+ // Duration are expressed in milliseconds
+ mDuration = (int) (-1000.0f * velocity / mDeceleration);
+
+ mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
+
+ // Clamp to a valid final position
+ if (mFinal < min) {
+ mFinal = min;
+ mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
+ }
+
+ if (mFinal > max) {
+ mFinal = max;
+ mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
+ }
+ }
+
+ void finish() {
+ mCurrentPosition = mFinal;
+ // Not reset since WebView relies on this value for fast fling.
+ // mCurrVelocity = 0.0f;
+ mFinished = true;
+ }
+
+ void setFinalPosition(int position) {
+ mFinal = position;
+ mFinished = false;
+ }
+
+ void extendDuration(int extend) {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final int elapsedTime = (int) (time - mStartTime);
+ mDuration = elapsedTime + extend;
+ mFinished = false;
+ }
+
+ void setBounceCoefficient(float coefficient) {
+ mBounceCoefficient = coefficient;
+ }
+
+ boolean springback(int start, int min, int max) {
+ mFinished = true;
+
+ mStart = start;
+ mVelocity = 0;
+
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mDuration = 0;
+
+ if (start < min) {
+ startSpringback(start, min, false);
+ } else if (start > max) {
+ startSpringback(start, max, true);
+ }
+
+ return !mFinished;
+ }
+
+ private void startSpringback(int start, int end, boolean positive) {
+ mFinished = false;
+ mState = TO_BOUNCE;
+ mStart = mFinal = end;
+ mDuration = OVERSCROLL_SPRINGBACK_DURATION;
+ mStartTime -= OVERSCROLL_SPRINGBACK_DURATION / 2;
+ mVelocity = (int) (Math.abs(end - start) * TIME_COEF * (positive ? 1.0 : -1.0f));
+ }
+
+ void fling(int start, int velocity, int min, int max, int over) {
+ mState = TO_EDGE;
+ mOver = over;
+
+ mFinished = false;
+
+ mStart = start;
+ mStartTime = AnimationUtils.currentAnimationTimeMillis();
+
+ mVelocity = velocity;
+
+ mDeceleration = getDeceleration(velocity);
+
+ // Duration are expressed in milliseconds
+ mDuration = (int) (-1000.0f * velocity / mDeceleration);
+
+ mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
+
+ // Clamp to a valid final position
+ if (mFinal < min) {
+ mFinal = min;
+ mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
+ }
+
+ if (mFinal > max) {
+ mFinal = max;
+ mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
+ }
+
+ if (start > max) {
+ if (start >= max + over) {
+ springback(max + over, min, max);
+ } else {
+ if (velocity <= 0) {
+ springback(start, min, max);
+ } else {
+ long time = AnimationUtils.currentAnimationTimeMillis();
+ final double durationSinceEdge =
+ Math.atan((start-max) * TIME_COEF / velocity) / TIME_COEF;
+ mStartTime = (int) (time - 1000.0f * durationSinceEdge);
+
+ // Simulate a bounce that started from edge
+ mStart = max;
+
+ mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
+
+ onEdgeReached();
+ }
+ }
+ } else {
+ if (start < min) {
+ if (start <= min - over) {
+ springback(min - over, min, max);
+ } else {
+ if (velocity >= 0) {
+ springback(start, min, max);
+ } else {
+ long time = AnimationUtils.currentAnimationTimeMillis();
+ final double durationSinceEdge =
+ Math.atan((start-min) * TIME_COEF / velocity) / TIME_COEF;
+ mStartTime = (int) (time - 1000.0f * durationSinceEdge);
+
+ // Simulate a bounce that started from edge
+ mStart = min;
+
+ mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF));
+
+ onEdgeReached();
+ }
+
+ }
+ }
+ }
+ }
+
+ void notifyEdgeReached(int start, int end, int over) {
+ mDeceleration = getDeceleration(mVelocity);
+
+ // Local time, used to compute edge crossing time.
+ float timeCurrent = mCurrVelocity / mDeceleration;
+ final int distance = end - start;
+ float timeEdge = -(float) Math.sqrt((2.0f * distance / mDeceleration)
+ + (timeCurrent * timeCurrent));
+
+ mVelocity = (int) (mDeceleration * timeEdge);
+
+ // Simulate a symmetric bounce that started from edge
+ mStart = end;
+
+ mOver = over;
+
+ long time = AnimationUtils.currentAnimationTimeMillis();
+ mStartTime = (int) (time - 1000.0f * (timeCurrent - timeEdge));
+
+ onEdgeReached();
+ }
+
+ private void onEdgeReached() {
+ // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+ final float distance = mVelocity / TIME_COEF;
+
+ if (Math.abs(distance) < mOver) {
+ // Spring force will bring us back to final position
+ mState = TO_BOUNCE;
+ mFinal = mStart;
+ mDuration = OVERSCROLL_SPRINGBACK_DURATION;
+ } else {
+ // Velocity is too high, we will hit the boundary limit
+ mState = TO_BOUNDARY;
+ int over = mVelocity > 0 ? mOver : -mOver;
+ mFinal = mStart + over;
+ mDuration = (int) (1000.0f * Math.asin(over / distance) / TIME_COEF);
+ }
+ }
+
+ boolean continueWhenFinished() {
+ switch (mState) {
+ case TO_EDGE:
+ // Duration from start to null velocity
+ int duration = (int) (-1000.0f * mVelocity / mDeceleration);
+ if (mDuration < duration) {
+ // If the animation was clamped, we reached the edge
+ mStart = mFinal;
+ // Speed when edge was reached
+ mVelocity = (int) (mVelocity + mDeceleration * mDuration / 1000.0f);
+ mStartTime += mDuration;
+ onEdgeReached();
+ } else {
+ // Normal stop, no need to continue
+ return false;
+ }
+ break;
+ case TO_BOUNDARY:
+ mStartTime += mDuration;
+ startSpringback(mFinal, mFinal - (mVelocity > 0 ? mOver:-mOver), mVelocity > 0);
+ break;
+ case TO_BOUNCE:
+ //mVelocity = (int) (mVelocity * BOUNCE_COEFFICIENT);
+ mVelocity = (int) (mVelocity * mBounceCoefficient);
+ if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) {
+ return false;
+ }
+ mStartTime += mDuration;
+ break;
+ }
+
+ update();
+ return true;
+ }
+
+ /*
+ * Update the current position and velocity for current time. Returns
+ * true if update has been done and false if animation duration has been
+ * reached.
+ */
+ boolean update() {
+ final long time = AnimationUtils.currentAnimationTimeMillis();
+ final long duration = time - mStartTime;
+
+ if (duration > mDuration) {
+ return false;
+ }
+
+ double distance;
+ final float t = duration / 1000.0f;
+ if (mState == TO_EDGE) {
+ mCurrVelocity = mVelocity + mDeceleration * t;
+ distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+ } else {
+ final float d = t * TIME_COEF;
+ mCurrVelocity = mVelocity * (float)Math.cos(d);
+ distance = mVelocity / TIME_COEF * Math.sin(d);
+ }
+
+ mCurrentPosition = mStart + (int) distance;
+ return true;
+ }
+ }
+}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 202e65852149..ec7d9277d0f6 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -21,8 +21,9 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
-import android.graphics.Shader;
import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.drawable.Animatable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ClipDrawable;
@@ -30,11 +31,14 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.StateListDrawable;
-import android.graphics.drawable.Animatable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.Gravity;
+import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewDebug;
import android.view.animation.AlphaAnimation;
@@ -44,9 +48,6 @@ import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
import com.android.internal.R;
@@ -336,7 +337,7 @@ public class ProgressBar extends View {
*
* @return true if the progress bar is in indeterminate mode
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "progress")
public synchronized boolean isIndeterminate() {
return mIndeterminate;
}
@@ -609,7 +610,7 @@ public class ProgressBar extends View {
* @see #setMax(int)
* @see #getMax()
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "progress")
public synchronized int getProgress() {
return mIndeterminate ? 0 : mProgress;
}
@@ -626,7 +627,7 @@ public class ProgressBar extends View {
* @see #setMax(int)
* @see #getMax()
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "progress")
public synchronized int getSecondaryProgress() {
return mIndeterminate ? 0 : mSecondaryProgress;
}
@@ -640,7 +641,7 @@ public class ProgressBar extends View {
* @see #getProgress()
* @see #getSecondaryProgress()
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "progress")
public synchronized int getMax() {
return mMax;
}
@@ -762,6 +763,7 @@ public class ProgressBar extends View {
}
@Override
+ @RemotableViewMethod
public void setVisibility(int v) {
if (getVisibility() != v) {
super.setVisibility(v);
@@ -947,4 +949,20 @@ public class ProgressBar extends View {
setProgress(ss.progress);
setSecondaryProgress(ss.secondaryProgress);
}
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mIndeterminate) {
+ startAnimation();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mIndeterminate) {
+ stopAnimation();
+ }
+ }
}
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 07c3e4b67340..4bbb54048801 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -236,6 +236,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
trigger = true;
createUri = Uri.fromParts("tel", (String)cookie, null);
+ //$FALL-THROUGH$
case TOKEN_PHONE_LOOKUP: {
if (cursor != null && cursor.moveToFirst()) {
long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX);
@@ -249,12 +250,14 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
trigger = true;
createUri = Uri.fromParts("mailto", (String)cookie, null);
+ //$FALL-THROUGH$
case TOKEN_EMAIL_LOOKUP: {
if (cursor != null && cursor.moveToFirst()) {
long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX);
String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX);
lookupUri = Contacts.getLookupUri(contactId, lookupKey);
}
+ break;
}
case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: {
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 1630e7f102bd..a47359f8a6a4 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -1014,7 +1014,7 @@ public class RelativeLayout extends ViewGroup {
* @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
*/
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
- @ViewDebug.ExportedProperty(resolveId = true, indexMapping = {
+ @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
@ViewDebug.IntToString(from = ABOVE, to = "above"),
@ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"),
@ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"),
@@ -1043,7 +1043,7 @@ public class RelativeLayout extends ViewGroup {
* When true, uses the parent as the anchor if the anchor doesn't exist or if
* the anchor's visibility is GONE.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public boolean alignWithParent;
public LayoutParams(Context c, AttributeSet attrs) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 3003580089da..7a70c808293b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -100,6 +100,7 @@ public class RemoteViews implements Parcelable, Filter {
* Base class for all actions that can be performed on an
* inflated view.
*
+ * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
*/
private abstract static class Action implements Parcelable {
public abstract void apply(View root) throws ActionException;
@@ -568,6 +569,14 @@ public class RemoteViews implements Parcelable, Filter {
}
}
+ public RemoteViews clone() {
+ final RemoteViews that = new RemoteViews(mPackage, mLayoutId);
+ if (mActions != null) {
+ that.mActions = (ArrayList<Action>)mActions.clone();
+ }
+ return that;
+ }
+
public String getPackage() {
return mPackage;
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 959e982cc3c8..2ba1c4791705 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -19,8 +19,11 @@ package android.widget;
import com.android.internal.R;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.FocusFinder;
import android.view.KeyEvent;
@@ -59,7 +62,9 @@ public class ScrollView extends FrameLayout {
private long mLastScroll;
private final Rect mTempRect = new Rect();
- private Scroller mScroller;
+ private OverScroller mScroller;
+ private EdgeGlow mEdgeGlowTop;
+ private EdgeGlow mEdgeGlowBottom;
/**
* Flag to indicate that we are moving focus ourselves. This is so the
@@ -113,6 +118,9 @@ public class ScrollView extends FrameLayout {
private int mMinimumVelocity;
private int mMaximumVelocity;
+ private int mOverscrollDistance;
+ private int mOverflingDistance;
+
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
@@ -185,7 +193,7 @@ public class ScrollView extends FrameLayout {
private void initScrollView() {
- mScroller = new Scroller(getContext());
+ mScroller = new OverScroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
@@ -193,6 +201,8 @@ public class ScrollView extends FrameLayout {
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mOverscrollDistance = configuration.getScaledOverscrollDistance();
+ mOverflingDistance = configuration.getScaledOverflingDistance();
}
@Override
@@ -453,6 +463,9 @@ public class ScrollView extends FrameLayout {
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
+ if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
+ invalidate();
+ }
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
@@ -510,7 +523,26 @@ public class ScrollView extends FrameLayout {
final int deltaY = (int) (mLastMotionY - y);
mLastMotionY = y;
- scrollBy(0, deltaY);
+ final int oldX = mScrollX;
+ final int oldY = mScrollY;
+ final int range = getScrollRange();
+ if (overscrollBy(0, deltaY, 0, mScrollY, 0, range,
+ 0, mOverscrollDistance, true)) {
+ // Break our velocity if we hit a scroll barrier.
+ mVelocityTracker.clear();
+ }
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+ final int overscrollMode = getOverscrollMode();
+ if (overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+ final int pulledToY = oldY + deltaY;
+ if (pulledToY < 0) {
+ mEdgeGlowTop.onPull((float) deltaY / getHeight());
+ } else if (pulledToY > range) {
+ mEdgeGlowBottom.onPull((float) deltaY / getHeight());
+ }
+ }
}
break;
case MotionEvent.ACTION_UP:
@@ -519,8 +551,15 @@ public class ScrollView extends FrameLayout {
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
- if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
- fling(-initialVelocity);
+ if (getChildCount() > 0) {
+ if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+ fling(-initialVelocity);
+ } else {
+ final int bottom = getScrollRange();
+ if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) {
+ invalidate();
+ }
+ }
}
mActivePointerId = INVALID_POINTER;
@@ -530,16 +569,27 @@ public class ScrollView extends FrameLayout {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
+ }
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
+ if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
+ invalidate();
+ }
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.onRelease();
+ mEdgeGlowBottom.onRelease();
+ }
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -566,6 +616,22 @@ public class ScrollView extends FrameLayout {
}
}
+ @Override
+ protected void onOverscrolled(int scrollX, int scrollY,
+ boolean clampedX, boolean clampedY) {
+ // Treat animating scrolls differently; see #computeScroll() for why.
+ if (!mScroller.isFinished()) {
+ mScrollX = scrollX;
+ mScrollY = scrollY;
+ if (clampedY) {
+ mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
+ }
+ } else {
+ super.scrollTo(scrollX, scrollY);
+ }
+ awakenScrollBars();
+ }
+
private int getScrollRange() {
int scrollRange = 0;
if (getChildCount() > 0) {
@@ -952,7 +1018,16 @@ public class ScrollView extends FrameLayout {
return contentHeight;
}
- return getChildAt(0).getBottom();
+ int scrollRange = getChildAt(0).getBottom();
+ final int scrollY = mScrollY;
+ final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
+ if (scrollY < 0) {
+ scrollRange -= scrollY;
+ } else if (scrollY > overscrollBottom) {
+ scrollRange += scrollY - overscrollBottom;
+ }
+
+ return scrollRange;
}
@Override
@@ -1013,14 +1088,20 @@ public class ScrollView extends FrameLayout {
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
- if (getChildCount() > 0) {
- View child = getChildAt(0);
- x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
- y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
- if (x != oldX || y != oldY) {
- mScrollX = x;
- mScrollY = y;
- onScrollChanged(x, y, oldX, oldY);
+ if (oldX != x || oldY != y) {
+ overscrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(),
+ 0, mOverflingDistance, false);
+ onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+ final int range = getScrollRange();
+ final int overscrollMode = getOverscrollMode();
+ if (overscrollMode == OVERSCROLL_ALWAYS ||
+ (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+ if (y < 0 && oldY >= 0) {
+ mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
+ } else if (y > range && oldY <= range) {
+ mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
+ }
}
}
awakenScrollBars();
@@ -1258,7 +1339,7 @@ public class ScrollView extends FrameLayout {
int bottom = getChildAt(0).getHeight();
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
- Math.max(0, bottom - height));
+ Math.max(0, bottom - height), 0, height/2);
final boolean movingDown = velocityY > 0;
@@ -1296,6 +1377,55 @@ public class ScrollView extends FrameLayout {
}
}
+ @Override
+ public void setOverscrollMode(int mode) {
+ if (mode != OVERSCROLL_NEVER) {
+ if (mEdgeGlowTop == null) {
+ final Resources res = getContext().getResources();
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+ final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+ mEdgeGlowTop = new EdgeGlow(edge, glow);
+ mEdgeGlowBottom = new EdgeGlow(edge, glow);
+ }
+ } else {
+ mEdgeGlowTop = null;
+ mEdgeGlowBottom = null;
+ }
+ super.setOverscrollMode(mode);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mEdgeGlowTop != null) {
+ final int scrollY = mScrollY;
+ if (!mEdgeGlowTop.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+
+ canvas.translate(-width / 2, Math.min(0, scrollY));
+ mEdgeGlowTop.setSize(width * 2, getHeight());
+ if (mEdgeGlowTop.draw(canvas)) {
+ invalidate();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ if (!mEdgeGlowBottom.isFinished()) {
+ final int restoreCount = canvas.save();
+ final int width = getWidth();
+ final int height = getHeight();
+
+ canvas.translate(-width / 2, Math.max(getScrollRange(), scrollY) + height);
+ canvas.rotate(180, width, 0);
+ mEdgeGlowBottom.setSize(width * 2, height);
+ if (mEdgeGlowBottom.draw(canvas)) {
+ invalidate();
+ }
+ canvas.restoreToCount(restoreCount);
+ }
+ }
+ }
+
private int clamp(int n, int my, int child) {
if (my >= child || n < 0) {
/* my >= child is this case:
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index 4cb0839036f2..23f72b6be989 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -50,8 +50,6 @@ public class Scroller {
private float mDurationReciprocal;
private float mDeltaX;
private float mDeltaY;
- private float mViscousFluidScale;
- private float mViscousFluidNormalize;
private boolean mFinished;
private Interpolator mInterpolator;
@@ -65,6 +63,17 @@ public class Scroller {
private final float mDeceleration;
+ private static float sViscousFluidScale;
+ private static float sViscousFluidNormalize;
+
+ static {
+ // This controls the viscous fluid effect (how much of it)
+ sViscousFluidScale = 8.0f;
+ // must be set to 1.0 (used in viscousFluid())
+ sViscousFluidNormalize = 1.0f;
+ sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+ }
+
/**
* Create a Scroller with the default duration and interpolator.
*/
@@ -277,11 +286,6 @@ public class Scroller {
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
- // This controls the viscous fluid effect (how much of it)
- mViscousFluidScale = 8.0f;
- // must be set to 1.0 (used in viscousFluid())
- mViscousFluidNormalize = 1.0f;
- mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
}
/**
@@ -339,11 +343,9 @@ public class Scroller {
mFinalY = Math.max(mFinalY, mMinY);
}
-
-
- private float viscousFluid(float x)
+ static float viscousFluid(float x)
{
- x *= mViscousFluidScale;
+ x *= sViscousFluidScale;
if (x < 1.0f) {
x -= (1.0f - (float)Math.exp(-x));
} else {
@@ -351,7 +353,7 @@ public class Scroller {
x = 1.0f - (float)Math.exp(1.0f - x);
x = start + x * (1.0f - start);
}
- x *= mViscousFluidNormalize;
+ x *= sViscousFluidNormalize;
return x;
}
diff --git a/core/java/android/widget/SimpleCursorTreeAdapter.java b/core/java/android/widget/SimpleCursorTreeAdapter.java
index a1c65f0a81ab..a0335426ddf5 100644
--- a/core/java/android/widget/SimpleCursorTreeAdapter.java
+++ b/core/java/android/widget/SimpleCursorTreeAdapter.java
@@ -38,6 +38,10 @@ import android.view.View;
* binding can be found, an {@link IllegalStateException} is thrown.
*/
public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter {
+
+ /** The name of the columns that contain the data to display for a group. */
+ private String[] mGroupFromNames;
+
/** The indices of columns that contain data to display for a group. */
private int[] mGroupFrom;
/**
@@ -46,6 +50,9 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter
*/
private int[] mGroupTo;
+ /** The name of the columns that contain the data to display for a child. */
+ private String[] mChildFromNames;
+
/** The indices of columns that contain data to display for a child. */
private int[] mChildFrom;
/**
@@ -171,38 +178,12 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter
private void init(String[] groupFromNames, int[] groupTo, String[] childFromNames,
int[] childTo) {
+
+ mGroupFromNames = groupFromNames;
mGroupTo = groupTo;
+ mChildFromNames = childFromNames;
mChildTo = childTo;
-
- // Get the group cursor column indices, the child cursor column indices will come
- // when needed
- initGroupFromColumns(groupFromNames);
-
- // Get a temporary child cursor to init the column indices
- if (getGroupCount() > 0) {
- MyCursorHelper tmpCursorHelper = getChildrenCursorHelper(0, true);
- if (tmpCursorHelper != null) {
- initChildrenFromColumns(childFromNames, tmpCursorHelper.getCursor());
- deactivateChildrenCursorHelper(0);
- }
- }
- }
-
- private void initFromColumns(Cursor cursor, String[] fromColumnNames, int[] fromColumns) {
- for (int i = fromColumnNames.length - 1; i >= 0; i--) {
- fromColumns[i] = cursor.getColumnIndexOrThrow(fromColumnNames[i]);
- }
- }
-
- private void initGroupFromColumns(String[] groupFromNames) {
- mGroupFrom = new int[groupFromNames.length];
- initFromColumns(mGroupCursorHelper.getCursor(), groupFromNames, mGroupFrom);
- }
-
- private void initChildrenFromColumns(String[] childFromNames, Cursor childCursor) {
- mChildFrom = new int[childFromNames.length];
- initFromColumns(childCursor, childFromNames, mChildFrom);
}
/**
@@ -257,13 +238,29 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter
}
}
+ private void initFromColumns(Cursor cursor, String[] fromColumnNames, int[] fromColumns) {
+ for (int i = fromColumnNames.length - 1; i >= 0; i--) {
+ fromColumns[i] = cursor.getColumnIndexOrThrow(fromColumnNames[i]);
+ }
+ }
+
@Override
protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
+ if (mChildFrom == null) {
+ mChildFrom = new int[mChildFromNames.length];
+ initFromColumns(cursor, mChildFromNames, mChildFrom);
+ }
+
bindView(view, context, cursor, mChildFrom, mChildTo);
}
@Override
protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) {
+ if (mGroupFrom == null) {
+ mGroupFrom = new int[mGroupFromNames.length];
+ initFromColumns(cursor, mGroupFromNames, mGroupFrom);
+ }
+
bindView(view, context, cursor, mGroupFrom, mGroupTo);
}
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index 48d12df8eb16..b612004c5b77 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -387,13 +387,13 @@ public class TableRow extends LinearLayout {
/**
* <p>The column index of the cell represented by the widget.</p>
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public int column;
/**
* <p>The number of columns the widgets spans over.</p>
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "layout")
public int span;
private static final int LOCATION = 0;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 64c9c9964d16..e97bbfb89116 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -34,6 +35,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
+import android.inputmethodservice.ExtractEditText;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -61,6 +63,7 @@ import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.method.ArrowKeyMovementMethod;
import android.text.method.DateKeyListener;
import android.text.method.DateTimeKeyListener;
import android.text.method.DialerKeyListener;
@@ -89,10 +92,11 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewDebug;
+import android.view.ViewGroup.LayoutParams;
import android.view.ViewRoot;
import android.view.ViewTreeObserver;
-import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
@@ -185,7 +189,7 @@ import java.util.ArrayList;
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
- static final String TAG = "TextView";
+ static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
private static int PRIORITY = 100;
@@ -321,6 +325,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
+ @SuppressWarnings("deprecation")
public TextView(Context context,
AttributeSet attrs,
int defStyle) {
@@ -695,9 +700,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
try {
setInputExtras(a.getResourceId(attr, 0));
} catch (XmlPullParserException e) {
- Log.w("TextView", "Failure reading input extras", e);
+ Log.w(LOG_TAG, "Failure reading input extras", e);
} catch (IOException e) {
- Log.w("TextView", "Failure reading input extras", e);
+ Log.w(LOG_TAG, "Failure reading input extras", e);
}
break;
}
@@ -706,15 +711,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
BufferType bufferType = BufferType.EDITABLE;
- if ((inputType&(EditorInfo.TYPE_MASK_CLASS
- |EditorInfo.TYPE_MASK_VARIATION))
- == (EditorInfo.TYPE_CLASS_TEXT
- |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
+ if ((inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
+ == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
password = true;
}
if (inputMethod != null) {
- Class c;
+ Class<?> c;
try {
c = Class.forName(inputMethod.toString());
@@ -797,6 +800,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else if (editable) {
mInput = TextKeyListener.getInstance();
mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ if (!singleLine) {
+ mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
} else {
mInput = null;
@@ -923,6 +929,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setFocusable(focusable);
setClickable(clickable);
setLongClickable(longClickable);
+
+ prepareCursorControllers();
}
private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -1128,6 +1136,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setText(mText);
fixFocusableAndClickableSettings();
+
+ // SelectionModifierCursorController depends on canSelectText, which depends on mMovement
+ prepareCursorControllers();
}
private void fixFocusableAndClickableSettings() {
@@ -2335,6 +2346,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return str + "}";
}
+ @SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
@@ -2369,8 +2381,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int end = 0;
if (mText != null) {
- start = Selection.getSelectionStart(mText);
- end = Selection.getSelectionEnd(mText);
+ start = getSelectionStart();
+ end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// Or save state if there is a selection
save = true;
@@ -2442,7 +2454,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
restored = "(restored) ";
}
- Log.e("TextView", "Saved cursor position " + ss.selStart +
+ Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
"/" + ss.selEnd + " out of range for " + restored +
"text " + mText);
} else {
@@ -2694,6 +2706,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (needEditableForNotification) {
sendAfterTextChanged((Editable) text);
}
+
+ // SelectionModifierCursorController depends on canSelectText, which depends on text
+ prepareCursorControllers();
}
/**
@@ -2756,6 +2771,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return mChars[off + mStart];
}
+ @Override
public String toString() {
return new String(mChars, mStart, mLength);
}
@@ -2947,8 +2963,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int cls = type & EditorInfo.TYPE_MASK_CLASS;
KeyListener input;
if (cls == EditorInfo.TYPE_CLASS_TEXT) {
- boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT)
- != 0;
+ boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
TextKeyListener.Capitalize cap;
if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
cap = TextKeyListener.Capitalize.CHARACTERS;
@@ -2981,7 +2996,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
input = TextKeyListener.getInstance();
}
- mInputType = type;
+ setRawInputType(type);
if (direct) mInput = input;
else {
setKeyListenerOnly(input);
@@ -3198,7 +3213,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @param create If true, the extras will be created if they don't already
* exist. Otherwise, null will be returned if none have been created.
- * @see #setInputExtras(int)View
+ * @see #setInputExtras(int)
* @see EditorInfo#extras
* @attr ref android.R.styleable#TextView_editorExtras
*/
@@ -3312,7 +3327,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private static class ErrorPopup extends PopupWindow {
private boolean mAbove = false;
- private TextView mView;
+ private final TextView mView;
ErrorPopup(TextView v, int width, int height) {
super(v, width, height);
@@ -3585,7 +3600,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void invalidateCursor() {
- int where = Selection.getSelectionEnd(mText);
+ int where = getSelectionEnd();
invalidateCursor(where, where, where);
}
@@ -3660,8 +3675,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean changed = false;
+ SelectionModifierCursorController selectionController = null;
+ if (mSelectionModifierCursorController != null) {
+ selectionController = (SelectionModifierCursorController)
+ mSelectionModifierCursorController;
+ }
+
+
if (mMovement != null) {
- int curs = Selection.getSelectionEnd(mText);
+ /* This code also provides auto-scrolling when a cursor is moved using a
+ * CursorController (insertion point or selection limits).
+ * For selection, ensure start or end is visible depending on controller's state.
+ */
+ int curs = getSelectionEnd();
+ if (selectionController != null && selectionController.isSelectionStartDragged()) {
+ curs = getSelectionStart();
+ }
/*
* TODO: This should really only keep the end in view if
@@ -3680,6 +3709,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
changed = bringTextIntoView();
}
+ // This has to be checked here since:
+ // - onFocusChanged cannot start it when focus is given to a view with selected text (after
+ // a screen rotation) since layout is not yet initialized at that point.
+ // - ExtractEditText does not call onFocus when it is displayed. Fixing this issue would
+ // allow to test for hasSelection in onFocusChanged, which would trigger a
+ // startTextSelectionMode here. TODO
+ if (selectionController != null && hasSelection()) {
+ startTextSelectionMode();
+ }
+
mPreDrawState = PREDRAW_DONE;
return !changed;
}
@@ -3954,8 +3993,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// XXX This is not strictly true -- a program could set the
// selection manually if it really wanted to.
if (mMovement != null && (isFocused() || isPressed())) {
- selStart = Selection.getSelectionStart(mText);
- selEnd = Selection.getSelectionEnd(mText);
+ selStart = getSelectionStart();
+ selEnd = getSelectionEnd();
if (mCursorVisible && selStart >= 0 && isEnabled()) {
if (mHighlightPath == null)
@@ -4061,6 +4100,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
canvas.restore();
+
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.draw(canvas);
+ }
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.draw(canvas);
+ }
}
@Override
@@ -4267,6 +4313,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (shouldAdvanceFocusOnEnter()) {
return 0;
}
+ break;
+
+ // Has to be done on key down (and not on key up) to correctly be intercepted.
+ case KeyEvent.KEYCODE_BACK:
+ if (mIsInTextSelectionMode) {
+ stopTextSelectionMode();
+ return -1;
+ }
+ break;
}
if (mInput != null) {
@@ -4345,6 +4400,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
}
+ hideControllers();
+
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
/*
@@ -4418,6 +4475,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
}
+ break;
}
if (mInput != null)
@@ -4475,8 +4533,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
outAttrs.hintText = mHint;
if (mText instanceof Editable) {
InputConnection ic = new EditableInputConnection(this);
- outAttrs.initialSelStart = Selection.getSelectionStart(mText);
- outAttrs.initialSelEnd = Selection.getSelectionEnd(mText);
+ outAttrs.initialSelStart = getSelectionStart();
+ outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
return ic;
}
@@ -4547,6 +4605,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
outText.text = TextUtils.substring(content, partialStartOffset,
partialEndOffset);
}
+ } else {
+ outText.partialStartOffset = 0;
+ outText.partialEndOffset = 0;
+ outText.text = "";
}
outText.flags = 0;
if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
@@ -4556,8 +4618,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
outText.flags |= ExtractedText.FLAG_SINGLE_LINE;
}
outText.startOffset = 0;
- outText.selectionStart = Selection.getSelectionStart(content);
- outText.selectionEnd = Selection.getSelectionEnd(content);
+ outText.selectionStart = getSelectionStart();
+ outText.selectionEnd = getSelectionEnd();
return true;
}
return false;
@@ -4574,7 +4636,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (req != null) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
- if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
+ ims.mChangedStart + " end=" + ims.mChangedEnd
+ " delta=" + ims.mChangedDelta);
if (ims.mChangedStart < 0 && !contentChanged) {
@@ -4582,7 +4644,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
ims.mChangedDelta, ims.mTmpExtracted)) {
- if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
+ ims.mTmpExtracted.partialStartOffset
+ " end=" + ims.mTmpExtracted.partialEndOffset
+ ": " + ims.mTmpExtracted.text);
@@ -4732,7 +4794,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
void updateAfterEdit() {
invalidate();
- int curs = Selection.getSelectionStart(mText);
+ int curs = getSelectionStart();
if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) ==
Gravity.BOTTOM) {
@@ -4874,7 +4936,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
w, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad);
}
- // Log.e("aaa", "Boring: " + mTransformed);
mSavedLayout = (BoringLayout) mLayout;
} else if (shouldEllipsize && boring.width <= w) {
@@ -4900,7 +4961,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mLayout = new StaticLayout(mTransformed, mTextPaint,
w, alignment, mSpacingMult, mSpacingAdd,
mIncludePad);
- // Log.e("aaa", "Boring but wide: " + mTransformed);
}
} else if (shouldEllipsize) {
mLayout = new StaticLayout(mTransformed,
@@ -4998,6 +5058,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
}
+
+ // CursorControllers need a non-null mLayout
+ prepareCursorControllers();
}
private boolean compressText(float width) {
@@ -5469,7 +5532,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// FIXME: Is it okay to truncate this, or should we round?
final int x = (int)mLayout.getPrimaryHorizontal(offset);
final int top = mLayout.getLineTop(line);
- final int bottom = mLayout.getLineTop(line+1);
+ final int bottom = mLayout.getLineTop(line + 1);
int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
@@ -5606,8 +5669,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// viewport coordinates, but requestRectangleOnScreen()
// is in terms of content coordinates.
- Rect r = new Rect();
- getInterestingRect(r, x, top, bottom, line);
+ Rect r = new Rect(x, top, x + 1, bottom);
+ getInterestingRect(r, line);
r.offset(mScrollX, mScrollY);
if (requestRectangleOnScreen(r)) {
@@ -5623,13 +5686,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* to the user. This will not move the cursor if it represents more than
* one character (a selection range). This will only work if the
* TextView contains spannable text; otherwise it will do nothing.
+ *
+ * @return True if the cursor was actually moved, false otherwise.
*/
public boolean moveCursorToVisibleOffset() {
if (!(mText instanceof Spannable)) {
return false;
}
- int start = Selection.getSelectionStart(mText);
- int end = Selection.getSelectionEnd(mText);
+ int start = getSelectionStart();
+ int end = getSelectionEnd();
if (start != end) {
return false;
}
@@ -5639,7 +5704,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int line = mLayout.getLineForOffset(start);
final int top = mLayout.getLineTop(line);
- final int bottom = mLayout.getLineTop(line+1);
+ final int bottom = mLayout.getLineTop(line + 1);
final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
int vslack = (bottom - top) / 2;
if (vslack > vspace / 4)
@@ -5685,23 +5750,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void getInterestingRect(Rect r, int h, int top, int bottom,
- int line) {
- int paddingTop = getExtendedPaddingTop();
- if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
- paddingTop += getVerticalOffset(false);
- }
- top += paddingTop;
- bottom += paddingTop;
- h += getCompoundPaddingLeft();
+ private void getInterestingRect(Rect r, int line) {
+ convertFromViewportToContentCoordinates(r);
+
+ // Rectangle can can be expanded on first and last line to take
+ // padding into account.
+ // TODO Take left/right padding into account too?
+ if (line == 0) r.top -= getExtendedPaddingTop();
+ if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
+ }
- if (line == 0)
- top -= getExtendedPaddingTop();
- if (line == mLayout.getLineCount() - 1)
- bottom += getExtendedPaddingBottom();
+ private void convertFromViewportToContentCoordinates(Rect r) {
+ final int horizontalOffset = viewportToContentHorizontalOffset();
+ r.left += horizontalOffset;
+ r.right += horizontalOffset;
- r.set(h, top, h+1, bottom);
- r.offset(-mScrollX, -mScrollY);
+ final int verticalOffset = viewportToContentVerticalOffset();
+ r.top += verticalOffset;
+ r.bottom += verticalOffset;
+ }
+
+ private int viewportToContentHorizontalOffset() {
+ return getCompoundPaddingLeft() - mScrollX;
+ }
+
+ private int viewportToContentVerticalOffset() {
+ int offset = getExtendedPaddingTop() - mScrollY;
+ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+ offset += getVerticalOffset(false);
+ }
+ return offset;
}
@Override
@@ -5729,7 +5807,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Convenience for {@link Selection#getSelectionStart}.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "text")
public int getSelectionStart() {
return Selection.getSelectionStart(getText());
}
@@ -5737,7 +5815,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Convenience for {@link Selection#getSelectionEnd}.
*/
- @ViewDebug.ExportedProperty
+ @ViewDebug.ExportedProperty(category = "text")
public int getSelectionEnd() {
return Selection.getSelectionEnd(getText());
}
@@ -5746,7 +5824,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* Return true iff there is a selection inside this text view.
*/
public boolean hasSelection() {
- return getSelectionStart() != getSelectionEnd();
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ return selectionStart >= 0 && selectionStart != selectionEnd;
}
/**
@@ -5868,6 +5949,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else if (mBlink != null) {
mBlink.removeCallbacks(mBlink);
}
+
+ // InsertionPointCursorController depends on mCursorVisible
+ prepareCursorControllers();
}
private boolean canMarquee() {
@@ -5926,7 +6010,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private final WeakReference<TextView> mView;
private byte mStatus = MARQUEE_STOPPED;
- private float mScrollUnit;
+ private final float mScrollUnit;
private float mMaxScroll;
float mMaxFadeScroll;
private float mGhostStart;
@@ -5938,7 +6022,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Marquee(TextView v) {
final float density = v.getContext().getResources().getDisplayMetrics().density;
- mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION;
+ mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
mView = new WeakReference<TextView>(v);
}
@@ -6171,6 +6255,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendOnTextChanged(buffer, start, before, after);
onTextChanged(buffer, start, before, after);
+ hideControllers();
}
/**
@@ -6282,7 +6367,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
} else {
- if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
+ oldStart + "-" + oldEnd + ","
+ newStart + "-" + newEnd + what);
ims.mContentChanged = true;
@@ -6298,7 +6383,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void beforeTextChanged(CharSequence buffer, int start,
int before, int after) {
- if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
if (AccessibilityManager.getInstance(mContext).isEnabled()
@@ -6311,7 +6396,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void onTextChanged(CharSequence buffer, int start,
int before, int after) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
TextView.this.handleTextChanged(buffer, start, before, after);
@@ -6324,7 +6409,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public void afterTextChanged(Editable buffer) {
- if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
TextView.this.sendAfterTextChanged(buffer);
if (MetaKeyKeyListener.getMetaState(buffer,
@@ -6335,19 +6420,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void onSpanChanged(Spannable buf,
Object what, int s, int e, int st, int en) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
+ " st=" + st + " en=" + en + " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, s, st, e, en);
}
public void onSpanAdded(Spannable buf, Object what, int s, int e) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
+ " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, -1, s, -1, e);
}
public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
- if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
+ " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, s, -1, e, -1);
}
@@ -6406,13 +6491,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mShowCursor = SystemClock.uptimeMillis();
ensureEndedBatchEdit();
-
+
if (focused) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
- boolean selMoved = mSelectionMoved;
+ // Has to be done before onTakeFocus, which can be overloaded.
+ if (mLastTouchOffset >= 0) {
+ // Can happen when a TextView is displayed after its content has been deleted.
+ mLastTouchOffset = Math.min(mLastTouchOffset, mText.length());
+ Selection.setSelection((Spannable) mText, mLastTouchOffset);
+ }
if (mMovement != null) {
mMovement.onTakeFocus(this, (Spannable) mText, direction);
@@ -6422,7 +6512,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Selection.setSelection((Spannable) mText, 0, mText.length());
}
- if (selMoved && selStart >= 0 && selEnd >= 0) {
+ // The DecorView does not have focus when the 'Done' ExtractEditText button is
+ // pressed. Since it is the ViewRoot's mView, it requests focus before
+ // ExtractEditText clears focus, which gives focus to the ExtractEditText.
+ // This special case ensure that we keep current selection in that case.
+ // It would be better to know why the DecorView does not have focus at that time.
+ if (((this instanceof ExtractEditText) || mSelectionMoved) &&
+ selStart >= 0 && selEnd >= 0) {
/*
* Someone intentionally set the selection, so let them
* do whatever it is that they wanted to do instead of
@@ -6432,7 +6528,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* just setting the selection in theirs and we still
* need to go through that path.
*/
-
Selection.setSelection((Spannable) mText, selStart, selEnd);
}
mTouchFocusSelected = true;
@@ -6457,13 +6552,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Don't leave us in the middle of a batch edit.
onEndBatchEdit();
+
+ hideInsertionPointCursorController();
+ if (this instanceof ExtractEditText) {
+ // terminateTextSelectionMode would remove selection, which we want to keep when
+ // ExtractEditText goes out of focus.
+ mIsInTextSelectionMode = false;
+ } else {
+ terminateTextSelectionMode();
+ }
}
startStopMarquee(focused);
if (mTransformation != null) {
- mTransformation.onFocusChanged(this, mText, focused, direction,
- previouslyFocusedRect);
+ mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
@@ -6522,38 +6625,66 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ private void onTapUpEvent(int prevStart, int prevEnd) {
+ final int start = getSelectionStart();
+ final int end = getSelectionEnd();
+
+ if (start == end) {
+ if (start >= prevStart && start < prevEnd) {
+ // Restore previous selection
+ Selection.setSelection((Spannable)mText, prevStart, prevEnd);
+ // Tapping inside the selection displays the cut/copy/paste context menu.
+ showContextMenu();
+ return;
+ } else {
+ // Tapping outside stops selection mode, if any
+ stopTextSelectionMode();
+
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.show();
+ }
+ }
+ }
+ }
+
class CommitSelectionReceiver extends ResultReceiver {
- int mNewStart;
- int mNewEnd;
+ private final int mPrevStart, mPrevEnd;
- CommitSelectionReceiver() {
+ public CommitSelectionReceiver(int prevStart, int prevEnd) {
super(getHandler());
+ mPrevStart = prevStart;
+ mPrevEnd = prevEnd;
}
+ @Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode != InputMethodManager.RESULT_SHOWN) {
+ // If this tap was actually used to show the IMM, leave cursor or selection unchanged
+ // by restoring its previous position.
+ if (resultCode == InputMethodManager.RESULT_SHOWN) {
final int len = mText.length();
- if (mNewStart > len) {
- mNewStart = len;
+ int start = Math.min(len, mPrevStart);
+ int end = Math.min(len, mPrevEnd);
+ Selection.setSelection((Spannable)mText, start, end);
+
+ if (hasSelection()) {
+ startTextSelectionMode();
+ } else if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.show();
}
- if (mNewEnd > len) {
- mNewEnd = len;
- }
- Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
- final int action = event.getAction();
+ final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
// Reset this state; it will be re-set if super.onTouchEvent
// causes focus to move to the view.
mTouchFocusSelected = false;
mScrolled = false;
}
-
+
final boolean superResult = super.onTouchEvent(event);
/*
@@ -6567,43 +6698,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) {
-
+
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.onTouchEvent(event);
+ }
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.onTouchEvent(event);
+ }
+
boolean handled = false;
-
- int oldSelStart = Selection.getSelectionStart(mText);
- int oldSelEnd = Selection.getSelectionEnd(mText);
+
+ // Save previous selection, in case this event is used to show the IME.
+ int oldSelStart = getSelectionStart();
+ int oldSelEnd = getSelectionEnd();
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
- if (mText instanceof Editable && onCheckIsTextEditor()) {
+ if (isTextEditable()) {
if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
- // This is going to be gross... if tapping on the text view
- // causes the IME to be displayed, we don't want the selection
- // to change. But the selection has already changed, and
- // we won't know right away whether the IME is getting
- // displayed, so...
-
- int newSelStart = Selection.getSelectionStart(mText);
- int newSelEnd = Selection.getSelectionEnd(mText);
+ getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+
CommitSelectionReceiver csr = null;
- if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) {
- csr = new CommitSelectionReceiver();
- csr.mNewStart = newSelStart;
- csr.mNewEnd = newSelEnd;
- }
-
- if (imm.showSoftInput(this, 0, csr) && csr != null) {
- // The IME might get shown -- revert to the old
- // selection, and change to the new when we finally
- // find out of it is okay.
- Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd);
- handled = true;
+ if (getSelectionStart() != oldSelStart || getSelectionEnd() != oldSelEnd ||
+ didTouchFocusSelect()) {
+ csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd);
}
+
+ handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
+
+ // Cannot be done by CommitSelectionReceiver, which might not always be called,
+ // for instance when dealing with an ExtractEditText.
+ onTapUpEvent(oldSelStart, oldSelEnd);
}
}
@@ -6615,6 +6743,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return superResult;
}
+ private void prepareCursorControllers() {
+ // TODO Add an extra android:cursorController flag to disable the controller?
+ if (mCursorVisible && mLayout != null) {
+ if (mInsertionPointCursorController == null) {
+ mInsertionPointCursorController = new InsertionPointCursorController();
+ }
+ } else {
+ mInsertionPointCursorController = null;
+ }
+
+ if (canSelectText() && mLayout != null) {
+ if (mSelectionModifierCursorController == null) {
+ mSelectionModifierCursorController = new SelectionModifierCursorController();
+ }
+ } else {
+ // Stop selection mode if the controller becomes unavailable.
+ stopTextSelectionMode();
+ mSelectionModifierCursorController = null;
+ }
+ }
+
+ /**
+ * @return True iff this TextView contains a text that can be edited.
+ */
+ private boolean isTextEditable() {
+ return mText instanceof Editable && onCheckIsTextEditor();
+ }
+
/**
* Returns true, only while processing a touch gesture, if the initial
* touch down event caused focus to move to the text view and as a result
@@ -6648,7 +6804,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private static class Blink extends Handler implements Runnable {
- private WeakReference<TextView> mView;
+ private final WeakReference<TextView> mView;
private boolean mCancelled;
public Blink(TextView v) {
@@ -6665,8 +6821,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
TextView tv = mView.get();
if (tv != null && tv.isFocused()) {
- int st = Selection.getSelectionStart(tv.mText);
- int en = Selection.getSelectionEnd(tv.mText);
+ int st = tv.getSelectionStart();
+ int en = tv.getSelectionEnd();
if (st == en && st >= 0 && en >= 0) {
if (tv.mLayout != null) {
@@ -6847,21 +7003,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private boolean canSelectAll() {
- if (mText instanceof Spannable && mText.length() != 0 &&
- mMovement != null && mMovement.canSelectArbitrarily()) {
- return true;
- }
-
- return false;
+ return canSelectText() && mText.length() != 0;
}
private boolean canSelectText() {
- if (mText instanceof Spannable && mText.length() != 0 &&
- mMovement != null && mMovement.canSelectArbitrarily()) {
- return true;
- }
-
- return false;
+ // prepareCursorController() relies on this method.
+ // If you change this condition, make sure prepareCursorController is called anywhere
+ // the value of this condition might be changed.
+ return (mText instanceof Spannable &&
+ mMovement != null &&
+ mMovement.canSelectArbitrarily());
}
private boolean canCut() {
@@ -6869,7 +7020,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
- if (mText.length() > 0 && getSelectionStart() >= 0) {
+ if (mText.length() > 0 && hasSelection()) {
if (mText instanceof Editable && mInput != null) {
return true;
}
@@ -6883,7 +7034,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
- if (mText.length() > 0 && getSelectionStart() >= 0) {
+ if (mText.length() > 0 && hasSelection()) {
return true;
}
@@ -6891,23 +7042,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private boolean canPaste() {
- if (mText instanceof Editable && mInput != null &&
- getSelectionStart() >= 0 && getSelectionEnd() >= 0) {
- ClipboardManager clip = (ClipboardManager)getContext()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- if (clip.hasText()) {
- return true;
- }
- }
-
- return false;
+ return (mText instanceof Editable &&
+ mInput != null &&
+ getSelectionStart() >= 0 &&
+ getSelectionEnd() >= 0 &&
+ ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
+ hasText());
}
/**
- * Returns a word to add to the dictionary from the context menu,
- * or null if there is no cursor or no word at the cursor.
+ * Returns the offsets delimiting the 'word' located at position offset.
+ *
+ * @param offset An offset in the text.
+ * @return The offsets for the start and end of the word located at <code>offset</code>.
+ * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits.
+ * Returns a negative value if no valid word was found.
*/
- private String getWordForDictionary() {
+ private long getWordLimitsAt(int offset) {
/*
* Quick return if the input type is one where adding words
* to the dictionary doesn't make any sense.
@@ -6916,7 +7067,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (klass == InputType.TYPE_CLASS_NUMBER ||
klass == InputType.TYPE_CLASS_PHONE ||
klass == InputType.TYPE_CLASS_DATETIME) {
- return null;
+ return -1;
}
int variation = mInputType & InputType.TYPE_MASK_VARIATION;
@@ -6925,17 +7076,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ||
variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
- return null;
+ return -1;
}
- int end = getSelectionEnd();
+ int len = mText.length();
+ int end = Math.min(offset, len);
if (end < 0) {
- return null;
+ return -1;
}
int start = end;
- int len = mText.length();
for (; start > 0; start--) {
char c = mTransformed.charAt(start - 1);
@@ -6965,6 +7116,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ if (start == end) {
+ return -1;
+ }
+
+ if (end - start > 48) {
+ return -1;
+ }
+
boolean hasLetter = false;
for (int i = start; i < end; i++) {
if (Character.isLetter(mTransformed.charAt(i))) {
@@ -6972,21 +7131,105 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
}
}
+
if (!hasLetter) {
- return null;
+ return -1;
}
- if (start == end) {
- return null;
+ // Two ints packed in a long
+ return (((long) start) << 32) | end;
+ }
+
+ private void selectCurrentWord() {
+ // In case selection mode is started after an orientation change or after a select all,
+ // use the current selection instead of creating one
+ if (hasSelection()) {
+ return;
}
- if (end - start > 48) {
- return null;
+ int selectionStart, selectionEnd;
+
+ // selectionModifierCursorController is not null at that point
+ SelectionModifierCursorController selectionModifierCursorController =
+ ((SelectionModifierCursorController) mSelectionModifierCursorController);
+ int minOffset = selectionModifierCursorController.getMinTouchOffset();
+ int maxOffset = selectionModifierCursorController.getMaxTouchOffset();
+
+ if (minOffset == maxOffset) {
+ int offset = Math.max(0, Math.min(minOffset, mTransformed.length()));
+
+ // Tolerance, number of charaters around tapped position
+ final int range = 1;
+ final int max = mTransformed.length() - 1;
+
+ // 'Smart' word selection: detect position between words
+ for (int i = -range; i <= range; i++) {
+ int index = offset + i;
+ if (index >= 0 && index <= max) {
+ if (Character.isSpaceChar(mTransformed.charAt(index))) {
+ // Select current space
+ selectionStart = index;
+ selectionEnd = selectionStart + 1;
+
+ // Extend selection to maximum space range
+ while (selectionStart > 0 &&
+ Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) {
+ selectionStart--;
+ }
+ while (selectionEnd < max &&
+ Character.isSpaceChar(mTransformed.charAt(selectionEnd))) {
+ selectionEnd++;
+ }
+
+ Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+ return;
+ }
+ }
+ }
+
+ // 'Smart' word selection: detect position at beginning or end of text.
+ if (offset <= range) {
+ Selection.setSelection((Spannable) mText, 0, 0);
+ return;
+ }
+ if (offset >= (max - range)) {
+ Selection.setSelection((Spannable) mText, max + 1, max + 1);
+ return;
+ }
}
- return TextUtils.substring(mTransformed, start, end);
+ long wordLimits = getWordLimitsAt(minOffset);
+ if (wordLimits >= 0) {
+ selectionStart = (int) (wordLimits >>> 32);
+ } else {
+ selectionStart = Math.max(minOffset - 5, 0);
+ }
+
+ wordLimits = getWordLimitsAt(maxOffset);
+ if (wordLimits >= 0) {
+ selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL);
+ } else {
+ selectionEnd = Math.min(maxOffset + 5, mText.length());
+ }
+
+ Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
}
+
+ private String getWordForDictionary() {
+ if (mLastTouchOffset < 0) {
+ return null;
+ }
+ long wordLimits = getWordLimitsAt(mLastTouchOffset);
+ if (wordLimits >= 0) {
+ int start = (int) (wordLimits >>> 32);
+ int end = (int) (wordLimits & 0x00000000FFFFFFFFL);
+ return mTransformed.subSequence(start, end).toString();
+ } else {
+ return null;
+ }
+ }
+
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
if (!isShown()) {
@@ -7028,114 +7271,98 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
super.onCreateContextMenu(menu);
boolean added = false;
- if (!isFocused()) {
- if (isFocusable() && mInput != null) {
- if (canCopy()) {
- MenuHandler handler = new MenuHandler();
- int name = com.android.internal.R.string.copyAll;
-
- menu.add(0, ID_COPY, 0, name).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('c');
- menu.setHeaderTitle(com.android.internal.R.string.
- editTextMenuTitle);
- }
+ if (mIsInTextSelectionMode) {
+ MenuHandler handler = new MenuHandler();
+
+ if (canCut()) {
+ menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('x');
+ added = true;
}
- return;
- }
-
- MenuHandler handler = new MenuHandler();
-
- if (canSelectAll()) {
- menu.add(0, ID_SELECT_ALL, 0,
- com.android.internal.R.string.selectAll).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('a');
- added = true;
- }
-
- boolean selection = getSelectionStart() != getSelectionEnd();
-
- if (canSelectText()) {
- if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) {
- menu.add(0, ID_STOP_SELECTING_TEXT, 0,
- com.android.internal.R.string.stopSelectingText).
- setOnMenuItemClickListener(handler);
- added = true;
- } else {
- menu.add(0, ID_START_SELECTING_TEXT, 0,
- com.android.internal.R.string.selectText).
- setOnMenuItemClickListener(handler);
+ if (canCopy()) {
+ menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('c');
added = true;
}
- }
- if (canCut()) {
- int name;
- if (selection) {
- name = com.android.internal.R.string.cut;
- } else {
- name = com.android.internal.R.string.cutAll;
+ if (canPaste()) {
+ menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('v');
+ added = true;
}
+ } else {
+ /*
+ if (!isFocused()) {
+ if (isFocusable() && mInput != null) {
+ if (canCopy()) {
+ MenuHandler handler = new MenuHandler();
+ menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('c');
+ menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
+ }
+ }
- menu.add(0, ID_CUT, 0, name).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('x');
- added = true;
- }
-
- if (canCopy()) {
- int name;
- if (selection) {
- name = com.android.internal.R.string.copy;
- } else {
- name = com.android.internal.R.string.copyAll;
+ //return;
}
+ */
+ MenuHandler handler = new MenuHandler();
- menu.add(0, ID_COPY, 0, name).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('c');
- added = true;
- }
-
- if (canPaste()) {
- menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
- setOnMenuItemClickListener(handler).
- setAlphabeticShortcut('v');
- added = true;
- }
+ if (canSelectText()) {
+ menu.add(0, ID_START_SELECTING_TEXT, 0, com.android.internal.R.string.selectText).
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
+
+ if (canSelectAll()) {
+ menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('a');
+ added = true;
+ }
- if (mText instanceof Spanned) {
- int selStart = getSelectionStart();
- int selEnd = getSelectionEnd();
+ if (mText instanceof Spanned) {
+ int selStart = getSelectionStart();
+ int selEnd = getSelectionEnd();
- int min = Math.min(selStart, selEnd);
- int max = Math.max(selStart, selEnd);
+ int min = Math.min(selStart, selEnd);
+ int max = Math.max(selStart, selEnd);
- URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
- URLSpan.class);
- if (urls.length == 1) {
- menu.add(0, ID_COPY_URL, 0,
- com.android.internal.R.string.copyUrl).
- setOnMenuItemClickListener(handler);
+ URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
+ URLSpan.class);
+ if (urls.length == 1) {
+ menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
+ }
+
+ // Paste location is too imprecise. Only allow on empty text fields.
+ if (canPaste() && textIsOnlySpaces()) {
+ menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
+ setOnMenuItemClickListener(handler).
+ setAlphabeticShortcut('v');
added = true;
}
- }
- if (isInputMethodTarget()) {
- menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
- setOnMenuItemClickListener(handler);
- added = true;
- }
+ if (isInputMethodTarget()) {
+ menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod).
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
- String word = getWordForDictionary();
- if (word != null) {
- menu.add(1, ID_ADD_TO_DICTIONARY, 0,
+ String word = getWordForDictionary();
+ if (word != null) {
+ menu.add(1, ID_ADD_TO_DICTIONARY, 0,
getContext().getString(com.android.internal.R.string.addToDictionary, word)).
- setOnMenuItemClickListener(handler);
- added = true;
+ setOnMenuItemClickListener(handler);
+ added = true;
+ }
}
if (added) {
@@ -7143,6 +7370,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ private boolean textIsOnlySpaces() {
+ final int length = mTransformed.length();
+ for (int i = 0; i < length; i++) {
+ if (!Character.isSpaceChar(mTransformed.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Returns whether this text view is a current input method target. The
* default implementation just checks with {@link InputMethodManager}.
@@ -7152,9 +7389,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return imm != null && imm.isActive(this);
}
+ // Context menu entries
private static final int ID_SELECT_ALL = android.R.id.selectAll;
private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText;
- private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText;
private static final int ID_CUT = android.R.id.cut;
private static final int ID_COPY = android.R.id.copy;
private static final int ID_PASTE = android.R.id.paste;
@@ -7171,28 +7408,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Called when a context menu option for the text view is selected. Currently
* this will be one of: {@link android.R.id#selectAll},
- * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText},
+ * {@link android.R.id#startSelectingText},
* {@link android.R.id#cut}, {@link android.R.id#copy},
* {@link android.R.id#paste}, {@link android.R.id#copyUrl},
* or {@link android.R.id#switchInputMethod}.
*/
public boolean onTextContextMenuItem(int id) {
- int selStart = getSelectionStart();
- int selEnd = getSelectionEnd();
+ int min = 0;
+ int max = mText.length();
- if (!isFocused()) {
- selStart = 0;
- selEnd = mText.length();
- }
-
- int min = Math.min(selStart, selEnd);
- int max = Math.max(selStart, selEnd);
+ if (isFocused()) {
+ final int selStart = getSelectionStart();
+ final int selEnd = getSelectionEnd();
- if (min < 0) {
- min = 0;
- }
- if (max < 0) {
- max = 0;
+ min = Math.max(0, Math.min(selStart, selEnd));
+ max = Math.max(0, Math.max(selStart, selEnd));
}
ClipboardManager clip = (ClipboardManager)getContext()
@@ -7200,63 +7430,70 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (id) {
case ID_SELECT_ALL:
- Selection.setSelection((Spannable) mText, 0,
- mText.length());
+ Selection.setSelection((Spannable) mText, 0, mText.length());
+ startTextSelectionMode();
return true;
case ID_START_SELECTING_TEXT:
- MetaKeyKeyListener.startSelecting(this, (Spannable) mText);
+ startTextSelectionMode();
return true;
- case ID_STOP_SELECTING_TEXT:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
- Selection.setSelection((Spannable) mText, getSelectionEnd());
- return true;
-
- case ID_CUT:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
- if (min == max) {
- min = 0;
- max = mText.length();
- }
-
+ case ID_CUT:
clip.setText(mTransformed.subSequence(min, max));
((Editable) mText).delete(min, max);
+ stopTextSelectionMode();
return true;
case ID_COPY:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
- if (min == max) {
- min = 0;
- max = mText.length();
- }
-
clip.setText(mTransformed.subSequence(min, max));
+ stopTextSelectionMode();
return true;
case ID_PASTE:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
CharSequence paste = clip.getText();
- if (paste != null) {
+ if (paste != null && paste.length() > 0) {
+ // Paste adds/removes spaces before or after insertion as needed.
+
+ if (Character.isSpaceChar(paste.charAt(0))) {
+ if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+ // Two spaces at beginning of paste: remove one
+ ((Editable) mText).replace(min - 1, min, "");
+ min = min - 1;
+ max = max - 1;
+ }
+ } else {
+ if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) {
+ // No space at beginning of paste: add one
+ ((Editable) mText).replace(min, min, " ");
+ min = min + 1;
+ max = max + 1;
+ }
+ }
+
+ if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) {
+ if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) {
+ // Two spaces at end of paste: remove one
+ ((Editable) mText).replace(max, max + 1, "");
+ }
+ } else {
+ if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) {
+ // No space at end of paste: add one
+ ((Editable) mText).replace(max, max, " ");
+ }
+ }
+
Selection.setSelection((Spannable) mText, max);
((Editable) mText).replace(min, max, paste);
+ stopTextSelectionMode();
}
-
return true;
case ID_COPY_URL:
- MetaKeyKeyListener.stopSelecting(this, (Spannable) mText);
-
- URLSpan[] urls = ((Spanned) mText).getSpans(min, max,
- URLSpan.class);
+ URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
clip.setText(urls[0].getURL());
}
-
return true;
case ID_SWITCH_INPUT_METHOD:
@@ -7275,13 +7512,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
}
-
return true;
}
return false;
}
+ @Override
public boolean performLongClick() {
if (super.performLongClick()) {
mEatTouchRelease = true;
@@ -7291,6 +7528,618 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
+ private void startTextSelectionMode() {
+ if (!mIsInTextSelectionMode) {
+ if (mSelectionModifierCursorController == null) {
+ Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled.");
+ return;
+ }
+
+ if (!requestFocus()) {
+ return;
+ }
+
+ selectCurrentWord();
+ mSelectionModifierCursorController.show();
+ mIsInTextSelectionMode = true;
+ }
+ }
+
+ /**
+ * Same as {@link #stopTextSelectionMode()}, except that there is no cursor controller
+ * fade out animation. Needed since the drawable and their alpha values are shared by all
+ * TextViews. Switching from one TextView to another would fade the cursor controllers in the
+ * new one otherwise.
+ */
+ private void terminateTextSelectionMode() {
+ stopTextSelectionMode();
+ if (mSelectionModifierCursorController != null) {
+ SelectionModifierCursorController selectionModifierCursorController =
+ (SelectionModifierCursorController) mSelectionModifierCursorController;
+ selectionModifierCursorController.cancelFadeOutAnimation();
+ }
+ }
+
+ private void stopTextSelectionMode() {
+ if (mIsInTextSelectionMode) {
+ Selection.setSelection((Spannable) mText, getSelectionEnd());
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.hide();
+ }
+
+ mIsInTextSelectionMode = false;
+ }
+ }
+
+ /**
+ * A CursorController instance can be used to control a cursor in the text.
+ *
+ * It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events
+ * and send them to this object instead of the cursor.
+ */
+ public interface CursorController {
+ /* Cursor fade-out animation duration, in milliseconds. */
+ static final int FADE_OUT_DURATION = 400;
+
+ /**
+ * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
+ * See also {@link #hide()}.
+ */
+ public void show();
+
+ /**
+ * Hide the cursor controller from screen.
+ * See also {@link #show()}.
+ */
+ public void hide();
+
+ /**
+ * Update the controller's position.
+ */
+ public void updatePosition(int x, int y);
+
+ /**
+ * The controller and the cursor's positions can be link by a fixed offset,
+ * computed when the controller is touched, and then maintained as it moves
+ * @return Horizontal offset between the controller and the cursor.
+ */
+ public float getOffsetX();
+
+ /**
+ * @return Vertical offset between the controller and the cursor.
+ */
+ public float getOffsetY();
+
+ /**
+ * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
+ * a chance to become active and/or visible.
+ * @param event The touch event
+ */
+ public void onTouchEvent(MotionEvent event);
+
+ /**
+ * Draws a visual representation of the controller on the canvas.
+ *
+ * Called at the end of {@link #draw(Canvas)}, in the content coordinates system.
+ * @param canvas The Canvas used by this TextView.
+ */
+ public void draw(Canvas canvas);
+ }
+
+ private class Handle {
+ Drawable mDrawable;
+ // Vertical extension of the touch region
+ int mTopExtension, mBottomExtension;
+ // Position of the virtual finger position on screen
+ int mHotSpotVerticalPosition;
+
+ Handle(Drawable drawable) {
+ mDrawable = drawable;
+ }
+
+ void positionAtCursor(final int offset, boolean bottom) {
+ final int drawableWidth = mDrawable.getIntrinsicWidth();
+ final int drawableHeight = mDrawable.getIntrinsicHeight();
+ final int line = mLayout.getLineForOffset(offset);
+ final int lineTop = mLayout.getLineTop(line);
+ final int lineBottom = mLayout.getLineBottom(line);
+
+ mHotSpotVerticalPosition = lineTop;
+
+ final Rect bounds = sCursorControllerTempRect;
+ bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0)
+ + mScrollX;
+ bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2 + mScrollY;
+
+ mTopExtension = bottom ? 0 : drawableHeight / 2;
+ mBottomExtension = drawableHeight;
+
+ // Extend touch region up when editing the last line of text (or a single line) so that
+ // it is easier to grab.
+ if (line == mLayout.getLineCount() - 1) {
+ mTopExtension = (lineBottom - lineTop) - drawableHeight / 2;
+ }
+
+ bounds.right = bounds.left + drawableWidth;
+ bounds.bottom = bounds.top + drawableHeight;
+
+ convertFromViewportToContentCoordinates(bounds);
+ mDrawable.setBounds(bounds);
+ postInvalidate();
+ }
+
+ boolean hasFingerOn(float x, float y) {
+ // Simulate a 'fat finger' to ease grabbing of the controller.
+ // Expands according to controller image size instead of using dip distance.
+ // Assumes controller imager has a sensible size, proportionnal to screen density.
+ final int drawableWidth = mDrawable.getIntrinsicWidth();
+ final Rect fingerRect = sCursorControllerTempRect;
+ fingerRect.set((int) (x - drawableWidth / 2.0),
+ (int) (y - mBottomExtension),
+ (int) (x + drawableWidth / 2.0),
+ (int) (y + mTopExtension));
+ fingerRect.offset(mScrollX, mScrollY);
+ return Rect.intersects(mDrawable.getBounds(), fingerRect);
+ }
+
+ void postInvalidate() {
+ final Rect bounds = mDrawable.getBounds();
+ TextView.this.postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ }
+
+ void postInvalidateDelayed(long delay) {
+ final Rect bounds = mDrawable.getBounds();
+ TextView.this.postInvalidateDelayed(delay, bounds.left, bounds.top,
+ bounds.right, bounds.bottom);
+ }
+ }
+
+ class InsertionPointCursorController implements CursorController {
+ private static final int DELAY_BEFORE_FADE_OUT = 2100;
+
+ // Whether or not the cursor control is currently visible
+ private boolean mIsVisible = false;
+ // Starting time of the fade timer
+ private long mFadeOutTimerStart;
+ // The cursor controller image
+ private final Handle mHandle;
+ // Used to detect a tap (vs drag) on the controller
+ private long mOnDownTimerStart;
+ // Offset between finger hot point on cursor controller and actual cursor
+ private float mOffsetX, mOffsetY;
+
+ InsertionPointCursorController() {
+ Resources res = mContext.getResources();
+ mHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+ }
+
+ public void show() {
+ updateDrawablePosition();
+ // Has to be done after updateDrawablePosition, so that previous position invalidate
+ // in only done if necessary.
+ mIsVisible = true;
+ }
+
+ public void hide() {
+ if (mIsVisible) {
+ long time = System.currentTimeMillis();
+ // Start fading out, only if not already in progress
+ if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) {
+ mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT;
+ mHandle.postInvalidate();
+ }
+ }
+ }
+
+ public void draw(Canvas canvas) {
+ if (mIsVisible) {
+ int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+ if (time <= DELAY_BEFORE_FADE_OUT) {
+ mHandle.postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time);
+ } else {
+ time -= DELAY_BEFORE_FADE_OUT;
+ if (time <= FADE_OUT_DURATION) {
+ final int alpha = (int)
+ ((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION);
+ mHandle.mDrawable.setAlpha(alpha);
+ mHandle.postInvalidateDelayed(30);
+ } else {
+ mHandle.mDrawable.setAlpha(0);
+ mIsVisible = false;
+ }
+ }
+ mHandle.mDrawable.draw(canvas);
+ }
+ }
+
+ public void updatePosition(int x, int y) {
+ final int previousOffset = getSelectionStart();
+ int offset = getHysteresisOffset(x, y, previousOffset);
+
+ if (offset != previousOffset) {
+ Selection.setSelection((Spannable) mText, offset);
+ updateDrawablePosition();
+ }
+ }
+
+ private void updateDrawablePosition() {
+ if (mIsVisible) {
+ // Clear previous cursor controller before bounds are updated
+ mHandle.postInvalidate();
+ }
+
+ final int offset = getSelectionStart();
+
+ if (offset < 0) {
+ // Should never happen, safety check.
+ Log.w(LOG_TAG, "Update cursor controller position called with no cursor");
+ mIsVisible = false;
+ return;
+ }
+
+ mHandle.positionAtCursor(offset, true);
+
+ mFadeOutTimerStart = System.currentTimeMillis();
+ mHandle.mDrawable.setAlpha(255);
+ }
+
+ public void onTouchEvent(MotionEvent event) {
+ if (isFocused() && isTextEditable() && mIsVisible) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN : {
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (mHandle.hasFingerOn(x, y)) {
+ show();
+
+ if (mMovement instanceof ArrowKeyMovementMethod) {
+ ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+ }
+
+ if (mParent != null) {
+ // Prevent possible scrollView parent from scrolling, so that
+ // we can use auto-scrolling.
+ mParent.requestDisallowInterceptTouchEvent(true);
+ }
+
+ final Rect bounds = mHandle.mDrawable.getBounds();
+ mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+ mOffsetY = mHandle.mHotSpotVerticalPosition - y;
+
+ mOffsetX += viewportToContentHorizontalOffset();
+ mOffsetY += viewportToContentVerticalOffset();
+
+ mOnDownTimerStart = event.getEventTime();
+ }
+ break;
+ }
+
+ case MotionEvent.ACTION_UP : {
+ int time = (int) (event.getEventTime() - mOnDownTimerStart);
+
+ if (time <= ViewConfiguration.getTapTimeout()) {
+ // A tap on the controller (not a drag) will move the cursor
+ int offset = getOffset((int) event.getX(), (int) event.getY());
+ Selection.setSelection((Spannable) mText, offset);
+
+ // Modified by cancelLongPress and prevents the cursor from changing
+ mScrolled = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ public float getOffsetX() {
+ return mOffsetX;
+ }
+
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+ }
+
+ class SelectionModifierCursorController implements CursorController {
+ // Whether or not the selection controls are currently visible
+ private boolean mIsVisible = false;
+ // Whether that start or the end of selection controller is dragged
+ private boolean mStartIsDragged = false;
+ // Starting time of the fade timer
+ private long mFadeOutTimerStart;
+ // Used to detect a tap (vs drag) on the controller
+ private long mOnDownTimerStart;
+ // The cursor controller images
+ private final Handle mStartHandle, mEndHandle;
+ // Offset between finger hot point on active cursor controller and actual cursor
+ private float mOffsetX, mOffsetY;
+ // The offsets of that last touch down event. Remembered to start selection there.
+ private int mMinTouchOffset, mMaxTouchOffset;
+
+ SelectionModifierCursorController() {
+ Resources res = mContext.getResources();
+ mStartHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+ mEndHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle));
+ }
+
+ public void show() {
+ updateDrawablesPositions();
+ // Has to be done after updateDrawablePositions, so that previous position invalidate
+ // in only done if necessary.
+ mIsVisible = true;
+ mFadeOutTimerStart = -1;
+ hideInsertionPointCursorController();
+ }
+
+ public void hide() {
+ if (mIsVisible && (mFadeOutTimerStart < 0)) {
+ mFadeOutTimerStart = System.currentTimeMillis();
+ mStartHandle.postInvalidate();
+ mEndHandle.postInvalidate();
+ }
+ }
+
+ public void cancelFadeOutAnimation() {
+ mIsVisible = false;
+ mStartHandle.postInvalidate();
+ mEndHandle.postInvalidate();
+ }
+
+ public void draw(Canvas canvas) {
+ if (mIsVisible) {
+ if (mFadeOutTimerStart >= 0) {
+ int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
+ if (time <= FADE_OUT_DURATION) {
+ final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION;
+ mStartHandle.mDrawable.setAlpha(alpha);
+ mEndHandle.mDrawable.setAlpha(alpha);
+ mStartHandle.postInvalidateDelayed(30);
+ mEndHandle.postInvalidateDelayed(30);
+ } else {
+ mStartHandle.mDrawable.setAlpha(0);
+ mEndHandle.mDrawable.setAlpha(0);
+ mIsVisible = false;
+ }
+ }
+ mStartHandle.mDrawable.draw(canvas);
+ mEndHandle.mDrawable.draw(canvas);
+ }
+ }
+
+ public void updatePosition(int x, int y) {
+ int selectionStart = getSelectionStart();
+ int selectionEnd = getSelectionEnd();
+
+ final int previousOffset = mStartIsDragged ? selectionStart : selectionEnd;
+ int offset = getHysteresisOffset(x, y, previousOffset);
+
+ // Handle the case where start and end are swapped, making sure start <= end
+ if (mStartIsDragged) {
+ if (offset <= selectionEnd) {
+ if (selectionStart == offset) {
+ return; // no change, no need to redraw;
+ }
+ selectionStart = offset;
+ } else {
+ selectionStart = selectionEnd;
+ selectionEnd = offset;
+ mStartIsDragged = false;
+ }
+ } else {
+ if (offset >= selectionStart) {
+ if (selectionEnd == offset) {
+ return; // no change, no need to redraw;
+ }
+ selectionEnd = offset;
+ } else {
+ selectionEnd = selectionStart;
+ selectionStart = offset;
+ mStartIsDragged = true;
+ }
+ }
+
+ Selection.setSelection((Spannable) mText, selectionStart, selectionEnd);
+ updateDrawablesPositions();
+ }
+
+ private void updateDrawablesPositions() {
+ if (mIsVisible) {
+ // Clear previous cursor controller before bounds are updated
+ mStartHandle.postInvalidate();
+ mEndHandle.postInvalidate();
+ }
+
+ final int selectionStart = getSelectionStart();
+ final int selectionEnd = getSelectionEnd();
+
+ if ((selectionStart < 0) || (selectionEnd < 0)) {
+ // Should never happen, safety check.
+ Log.w(LOG_TAG, "Update selection controller position called with no cursor");
+ mIsVisible = false;
+ return;
+ }
+
+ boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) ==
+ mLayout.getLineForOffset(selectionEnd);
+ mStartHandle.positionAtCursor(selectionStart, oneLineSelection);
+ mEndHandle.positionAtCursor(selectionEnd, true);
+
+ mStartHandle.mDrawable.setAlpha(255);
+ mEndHandle.mDrawable.setAlpha(255);
+ }
+
+ public void onTouchEvent(MotionEvent event) {
+ if (isTextEditable()) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+
+ // Remember finger down position, to be able to start selection from there
+ mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y);
+
+ if (mIsVisible) {
+ if (mMovement instanceof ArrowKeyMovementMethod) {
+ boolean isOnStart = mStartHandle.hasFingerOn(x, y);
+ boolean isOnEnd = mEndHandle.hasFingerOn(x, y);
+ if (isOnStart || isOnEnd) {
+ if (mParent != null) {
+ // Prevent possible scrollView parent from scrolling, so
+ // that we can use auto-scrolling.
+ mParent.requestDisallowInterceptTouchEvent(true);
+ }
+
+ // In case both controllers are under finger (very small
+ // selection region), arbitrarily pick end controller.
+ mStartIsDragged = !isOnEnd;
+ final Handle draggedHandle =
+ mStartIsDragged ? mStartHandle : mEndHandle;
+ final Rect bounds = draggedHandle.mDrawable.getBounds();
+ mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
+ mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;
+
+ mOffsetX += viewportToContentHorizontalOffset();
+ mOffsetY += viewportToContentVerticalOffset();
+
+ mOnDownTimerStart = event.getEventTime();
+ ((ArrowKeyMovementMethod)mMovement).setCursorController(this);
+ }
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ int time = (int) (event.getEventTime() - mOnDownTimerStart);
+
+ if (time <= ViewConfiguration.getTapTimeout()) {
+ // A tap on the controller (not a drag) opens the contextual Copy menu
+ showContextMenu();
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ // Handle multi-point gestures. Keep min and max offset positions.
+ // Only activated for devices that correctly handle multi-touch.
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) {
+ updateMinAndMaxOffsets(event);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param event
+ */
+ private void updateMinAndMaxOffsets(MotionEvent event) {
+ int pointerCount = event.getPointerCount();
+ for (int index = 0; index < pointerCount; index++) {
+ final int x = (int) event.getX(index);
+ final int y = (int) event.getY(index);
+ int offset = getOffset(x, y);
+ if (offset < mMinTouchOffset) mMinTouchOffset = offset;
+ if (offset > mMaxTouchOffset) mMaxTouchOffset = offset;
+ }
+ }
+
+ public int getMinTouchOffset() {
+ return mMinTouchOffset;
+ }
+
+ public int getMaxTouchOffset() {
+ return mMaxTouchOffset;
+ }
+
+ public float getOffsetX() {
+ return mOffsetX;
+ }
+
+ public float getOffsetY() {
+ return mOffsetY;
+ }
+
+ /**
+ * @return true iff this controller is currently used to move the selection start.
+ */
+ public boolean isSelectionStartDragged() {
+ return mIsVisible && mStartIsDragged;
+ }
+ }
+
+ private void hideInsertionPointCursorController() {
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.hide();
+ }
+ }
+
+ private void hideControllers() {
+ hideInsertionPointCursorController();
+ stopTextSelectionMode();
+ }
+
+ private int getOffsetForHorizontal(int line, int x) {
+ x -= getTotalPaddingLeft();
+ // Clamp the position to inside of the view.
+ x = Math.max(0, x);
+ x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
+ x += getScrollX();
+ return getLayout().getOffsetForHorizontal(line, x);
+ }
+
+ /**
+ * Get the offset character closest to the specified absolute position.
+ *
+ * @param x The horizontal absolute position of a point on screen
+ * @param y The vertical absolute position of a point on screen
+ * @return the character offset for the character whose position is closest to the specified
+ * position. Returns -1 if there is no layout.
+ *
+ * @hide
+ */
+ public int getOffset(int x, int y) {
+ if (getLayout() == null) return -1;
+
+ y -= getTotalPaddingTop();
+ // Clamp the position to inside of the view.
+ y = Math.max(0, y);
+ y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
+ y += getScrollY();
+
+ final int line = getLayout().getLineForVertical(y);
+ final int offset = getOffsetForHorizontal(line, x);
+ return offset;
+ }
+
+ int getHysteresisOffset(int x, int y, int previousOffset) {
+ final Layout layout = getLayout();
+ if (layout == null) return -1;
+
+ y -= getTotalPaddingTop();
+ // Clamp the position to inside of the view.
+ y = Math.max(0, y);
+ y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
+ y += getScrollY();
+
+ int line = getLayout().getLineForVertical(y);
+
+ final int previousLine = layout.getLineForOffset(previousOffset);
+ final int previousLineTop = layout.getLineTop(previousLine);
+ final int previousLineBottom = layout.getLineBottom(previousLine);
+ final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
+
+ // If new line is just before or after previous line and y position is less than
+ // hysteresisThreshold away from previous line, keep cursor on previous line.
+ if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) ||
+ ((line == previousLine - 1) && ((previousLineTop - y) < hysteresisThreshold))) {
+ line = previousLine;
+ }
+
+ return getOffsetForHorizontal(line, x);
+ }
+
@ViewDebug.ExportedProperty
private CharSequence mText;
private CharSequence mTransformed;
@@ -7309,16 +8158,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private ArrayList<TextWatcher> mListeners = null;
// display attributes
- private TextPaint mTextPaint;
+ private final TextPaint mTextPaint;
private boolean mUserSetTextScaleX;
- private Paint mHighlightPaint;
- private int mHighlightColor = 0xFFBBDDFF;
+ private final Paint mHighlightPaint;
+ private int mHighlightColor = 0xCC475925;
private Layout mLayout;
private long mShowCursor;
private Blink mBlink;
private boolean mCursorVisible = true;
+ // Cursor Controllers. Null when disabled.
+ private CursorController mInsertionPointCursorController;
+ private CursorController mSelectionModifierCursorController;
+ private boolean mIsInTextSelectionMode = false;
+ private int mLastTouchOffset = -1;
+ // Created once and shared by different CursorController helper methods.
+ // Only one cursor controller is active at any time which prevent race conditions.
+ private static Rect sCursorControllerTempRect = new Rect();
+
private boolean mSelectAllOnFocus = false;
private int mGravity = Gravity.TOP | Gravity.LEFT;
diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
index 7e9bbd198c12..98dcb8bcbf07 100644
--- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
+++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java
@@ -23,13 +23,10 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
-import android.os.Handler;
import android.os.storage.IMountService;
-import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Environment;
-import android.widget.Toast;
import android.util.Log;
/**
@@ -38,7 +35,7 @@ import android.util.Log;
*/
public class ExternalMediaFormatActivity extends AlertActivity implements DialogInterface.OnClickListener {
- private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+ private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE;
/** Used to detect when the media state changes, in case we need to call finish() */
private BroadcastReceiver mStorageReceiver = new BroadcastReceiver() {
diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
new file mode 100644
index 000000000000..ada7f36e2af6
--- /dev/null
+++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import com.android.internal.R;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * This activity is displayed when the system attempts to start an Intent for
+ * which there is more than one matching activity, allowing the user to decide
+ * which to go to. It is not normally used directly by application developers.
+ */
+public class HeavyWeightSwitcherActivity extends Activity {
+ /** The PendingIntent of the new activity being launched. */
+ public static final String KEY_INTENT = "intent";
+ /** Set if the caller is requesting a result. */
+ public static final String KEY_HAS_RESULT = "has_result";
+ /** Package of current heavy-weight app. */
+ public static final String KEY_CUR_APP = "cur_app";
+ /** Task that current heavy-weight activity is running in. */
+ public static final String KEY_CUR_TASK = "cur_task";
+ /** Package of newly requested heavy-weight app. */
+ public static final String KEY_NEW_APP = "new_app";
+
+ IntentSender mStartIntent;
+ boolean mHasResult;
+ String mCurApp;
+ int mCurTask;
+ String mNewApp;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestWindowFeature(Window.FEATURE_LEFT_ICON);
+
+ mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT);
+ mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false);
+ mCurApp = getIntent().getStringExtra(KEY_CUR_APP);
+ mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0);
+ mNewApp = getIntent().getStringExtra(KEY_NEW_APP);
+
+ setContentView(com.android.internal.R.layout.heavy_weight_switcher);
+
+ setIconAndText(R.id.old_app_icon, R.id.old_app_action, R.id.old_app_description,
+ mCurApp, R.string.old_app_action, R.string.old_app_description);
+ setIconAndText(R.id.new_app_icon, R.id.new_app_action, R.id.new_app_description,
+ mNewApp, R.string.new_app_action, R.string.new_app_description);
+
+ View button = findViewById((R.id.switch_old));
+ button.setOnClickListener(mSwitchOldListener);
+ button = findViewById((R.id.switch_new));
+ button.setOnClickListener(mSwitchNewListener);
+ button = findViewById((R.id.cancel));
+ button.setOnClickListener(mCancelListener);
+
+ getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
+ android.R.drawable.ic_dialog_alert);
+ }
+
+ void setText(int id, CharSequence text) {
+ ((TextView)findViewById(id)).setText(text);
+ }
+
+ void setDrawable(int id, Drawable dr) {
+ if (dr != null) {
+ ((ImageView)findViewById(id)).setImageDrawable(dr);
+ }
+ }
+
+ void setIconAndText(int iconId, int actionId, int descriptionId,
+ String packageName, int actionStr, int descriptionStr) {
+ CharSequence appName = "";
+ Drawable appIcon = null;
+ if (mCurApp != null) {
+ try {
+ ApplicationInfo info = getPackageManager().getApplicationInfo(
+ packageName, 0);
+ appName = info.loadLabel(getPackageManager());
+ appIcon = info.loadIcon(getPackageManager());
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+
+ setDrawable(iconId, appIcon);
+ setText(actionId, getString(actionStr, appName));
+ setText(descriptionId, getText(descriptionStr));
+ }
+
+ private OnClickListener mSwitchOldListener = new OnClickListener() {
+ public void onClick(View v) {
+ try {
+ ActivityManagerNative.getDefault().moveTaskToFront(mCurTask);
+ } catch (RemoteException e) {
+ }
+ finish();
+ }
+ };
+
+ private OnClickListener mSwitchNewListener = new OnClickListener() {
+ public void onClick(View v) {
+ try {
+ ActivityManagerNative.getDefault().finishHeavyWeightApp();
+ } catch (RemoteException e) {
+ }
+ try {
+ if (mHasResult) {
+ startIntentSenderForResult(mStartIntent, -1, null,
+ Intent.FLAG_ACTIVITY_FORWARD_RESULT,
+ Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0);
+ } else {
+ startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0);
+ }
+ } catch (IntentSender.SendIntentException ex) {
+ Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex);
+ }
+ finish();
+ }
+ };
+
+ private OnClickListener mCancelListener = new OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ };
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index d5ccdeb3bfeb..bd87a0d05261 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -18,14 +18,25 @@ package com.android.internal.app;
import com.android.internal.os.BatteryStatsImpl;
+import android.os.WorkSource;
import android.telephony.SignalStrength;
interface IBatteryStats {
byte[] getStatistics();
- void noteStartWakelock(int uid, String name, int type);
- void noteStopWakelock(int uid, String name, int type);
+ void noteStartWakelock(int uid, int pid, String name, int type);
+ void noteStopWakelock(int uid, int pid, String name, int type);
+
+ /* DO NOT CHANGE the position of noteStartSensor without updating
+ SensorService.cpp */
void noteStartSensor(int uid, int sensor);
+
+ /* DO NOT CHANGE the position of noteStopSensor without updating
+ SensorService.cpp */
void noteStopSensor(int uid, int sensor);
+
+ void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+ void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+
void noteStartGps(int uid);
void noteStopGps(int uid);
void noteScreenOn();
@@ -50,8 +61,13 @@ interface IBatteryStats {
void noteScanWifiLockReleased(int uid);
void noteWifiMulticastEnabled(int uid);
void noteWifiMulticastDisabled(int uid);
- void setOnBattery(boolean onBattery, int level);
- void recordCurrentLevel(int level);
+ void noteFullWifiLockAcquiredFromSource(in WorkSource ws);
+ void noteFullWifiLockReleasedFromSource(in WorkSource ws);
+ void noteScanWifiLockAcquiredFromSource(in WorkSource ws);
+ void noteScanWifiLockReleasedFromSource(in WorkSource ws);
+ void noteWifiMulticastEnabledFromSource(in WorkSource ws);
+ void noteWifiMulticastDisabledFromSource(in WorkSource ws);
+ 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/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl
index 0f817b7a6bb4..5d1f632797a7 100755
--- a/core/java/com/android/internal/app/IMediaContainerService.aidl
+++ b/core/java/com/android/internal/app/IMediaContainerService.aidl
@@ -19,6 +19,7 @@ package com.android.internal.app;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.content.pm.PackageInfoLite;
+import android.content.res.ObbInfo;
interface IMediaContainerService {
String copyResourceToContainer(in Uri packageURI,
@@ -28,4 +29,5 @@ interface IMediaContainerService {
in ParcelFileDescriptor outStream);
PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags);
boolean checkFreeStorage(boolean external, in Uri fileUri);
-} \ No newline at end of file
+ ObbInfo getObbInfo(String filename);
+}
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
index 24818a84a130..36f45b24ee22 100755
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -23,14 +23,9 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.widget.Toast;
import android.util.Log;
import android.location.LocationManager;
-import com.android.internal.location.GpsLocationProvider;
import com.android.internal.location.GpsNetInitiatedHandler;
/**
@@ -44,8 +39,8 @@ public class NetInitiatedActivity extends AlertActivity implements DialogInterfa
private static final boolean DEBUG = true;
private static final boolean VERBOSE = false;
- private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
- private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON2;
+ private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE;
+ private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE;
// Dialog button text
public static final String BUTTON_TEXT_ACCEPT = "Accept";
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
new file mode 100644
index 000000000000..e1c5564bd528
--- /dev/null
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+public class PlatLogoActivity extends Activity {
+ Toast mToast;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mToast = Toast.makeText(this, "Zombie art by Jack Larson", Toast.LENGTH_SHORT);
+
+ ImageView content = new ImageView(this);
+ content.setImageResource(com.android.internal.R.drawable.platlogo);
+ content.setScaleType(ImageView.ScaleType.FIT_CENTER);
+
+ setContentView(content);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_UP) {
+ mToast.show();
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+}
diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java
index ddddabefba33..5569ffe1bb72 100644
--- a/core/java/com/android/internal/app/RingtonePickerActivity.java
+++ b/core/java/com/android/internal/app/RingtonePickerActivity.java
@@ -223,7 +223,7 @@ public final class RingtonePickerActivity extends AlertActivity implements
* On click of Ok/Cancel buttons
*/
public void onClick(DialogInterface dialog, int which) {
- boolean positiveResult = which == BUTTON1;
+ boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
// Stop playing the previous ringtone
mRingtoneManager.stopPreviousRingtone();
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index a96253bb4bc4..d1aff2a0bfb8 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -84,7 +84,7 @@ public final class ShutdownThread extends Thread {
public static void shutdown(final Context context, boolean confirm) {
// ensure that only one thread is trying to power down.
// any additional calls are just returned
- synchronized (sIsStartedGuard){
+ synchronized (sIsStartedGuard) {
if (sIsStarted) {
Log.d(TAG, "Request to shutdown already running, returning.");
return;
@@ -133,6 +133,10 @@ public final class ShutdownThread extends Thread {
private static void beginShutdownSequence(Context context) {
synchronized (sIsStartedGuard) {
+ if (sIsStarted) {
+ Log.d(TAG, "Request to shutdown already running, returning.");
+ return;
+ }
sIsStarted = true;
}
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
new file mode 100644
index 000000000000..6e11cff2c208
--- /dev/null
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -0,0 +1,326 @@
+package com.android.internal.content;
+
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.SystemProperties;
+import android.util.Config;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/**
+ * Native libraries helper.
+ *
+ * @hide
+ */
+public class NativeLibraryHelper {
+ private static final String TAG = "NativeHelper";
+
+ private static final boolean DEBUG_NATIVE = false;
+
+ /*
+ * The following constants are returned by listPackageSharedLibsForAbiLI
+ * to indicate if native shared libraries were found in the package.
+ * Values are:
+ * PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES => native libraries found and installed
+ * PACKAGE_INSTALL_NATIVE_NO_LIBRARIES => no native libraries in package
+ * PACKAGE_INSTALL_NATIVE_ABI_MISMATCH => native libraries for another ABI found
+ * in package (and not installed)
+ *
+ */
+ private static final int PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES = 0;
+ private static final int PACKAGE_INSTALL_NATIVE_NO_LIBRARIES = 1;
+ private static final int PACKAGE_INSTALL_NATIVE_ABI_MISMATCH = 2;
+
+ // Directory in the APK that holds all the native shared libraries.
+ private static final String APK_LIB = "lib/";
+ private static final int APK_LIB_LENGTH = APK_LIB.length();
+
+ // Prefix that native shared libraries must have.
+ private static final String LIB_PREFIX = "lib";
+ private static final int LIB_PREFIX_LENGTH = LIB_PREFIX.length();
+
+ // Suffix that the native shared libraries must have.
+ private static final String LIB_SUFFIX = ".so";
+ private static final int LIB_SUFFIX_LENGTH = LIB_SUFFIX.length();
+
+ // Name of the GDB binary.
+ private static final String GDBSERVER = "gdbserver";
+
+ // the minimum length of a valid native shared library of the form
+ // lib/<something>/lib<name>.so.
+ private static final int MIN_ENTRY_LENGTH = APK_LIB_LENGTH + 2 + LIB_PREFIX_LENGTH + 1
+ + LIB_SUFFIX_LENGTH;
+
+ /*
+ * Find all files of the form lib/<cpuAbi>/lib<name>.so in the .apk
+ * and add them to a list to be installed later.
+ *
+ * NOTE: this method may throw an IOException if the library cannot
+ * be copied to its final destination, e.g. if there isn't enough
+ * room left on the data partition, or a ZipException if the package
+ * file is malformed.
+ */
+ private static int listPackageSharedLibsForAbiLI(ZipFile zipFile,
+ String cpuAbi, List<Pair<ZipEntry, String>> libEntries) throws IOException,
+ ZipException {
+ final int cpuAbiLen = cpuAbi.length();
+ boolean hasNativeLibraries = false;
+ boolean installedNativeLibraries = false;
+
+ if (DEBUG_NATIVE) {
+ Slog.d(TAG, "Checking " + zipFile.getName() + " for shared libraries of CPU ABI type "
+ + cpuAbi);
+ }
+
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+
+ // skip directories
+ if (entry.isDirectory()) {
+ continue;
+ }
+ String entryName = entry.getName();
+
+ /*
+ * Check that the entry looks like lib/<something>/lib<name>.so
+ * here, but don't check the ABI just yet.
+ *
+ * - must be sufficiently long
+ * - must end with LIB_SUFFIX, i.e. ".so"
+ * - must start with APK_LIB, i.e. "lib/"
+ */
+ if (entryName.length() < MIN_ENTRY_LENGTH || !entryName.endsWith(LIB_SUFFIX)
+ || !entryName.startsWith(APK_LIB)) {
+ continue;
+ }
+
+ // file name must start with LIB_PREFIX, i.e. "lib"
+ int lastSlash = entryName.lastIndexOf('/');
+
+ if (lastSlash < 0
+ || !entryName.regionMatches(lastSlash + 1, LIB_PREFIX, 0, LIB_PREFIX_LENGTH)) {
+ continue;
+ }
+
+ hasNativeLibraries = true;
+
+ // check the cpuAbi now, between lib/ and /lib<name>.so
+ if (lastSlash != APK_LIB_LENGTH + cpuAbiLen
+ || !entryName.regionMatches(APK_LIB_LENGTH, cpuAbi, 0, cpuAbiLen))
+ continue;
+
+ /*
+ * Extract the library file name, ensure it doesn't contain
+ * weird characters. we're guaranteed here that it doesn't contain
+ * a directory separator though.
+ */
+ String libFileName = entryName.substring(lastSlash+1);
+ if (!FileUtils.isFilenameSafe(new File(libFileName))) {
+ continue;
+ }
+
+ installedNativeLibraries = true;
+
+ if (DEBUG_NATIVE) {
+ Log.d(TAG, "Caching shared lib " + entry.getName());
+ }
+
+ libEntries.add(Pair.create(entry, libFileName));
+ }
+ if (!hasNativeLibraries)
+ return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
+
+ if (!installedNativeLibraries)
+ return PACKAGE_INSTALL_NATIVE_ABI_MISMATCH;
+
+ return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
+ }
+
+ /*
+ * Find the gdbserver executable program in a package at
+ * lib/<cpuAbi>/gdbserver and add it to the list of binaries
+ * to be copied out later.
+ *
+ * Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success,
+ * or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise.
+ */
+ private static int listPackageGdbServerLI(ZipFile zipFile, String cpuAbi,
+ List<Pair<ZipEntry, String>> nativeFiles) throws IOException, ZipException {
+ final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER;
+
+ Enumeration<? extends ZipEntry> entries = zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ // skip directories
+ if (entry.isDirectory()) {
+ continue;
+ }
+ String entryName = entry.getName();
+
+ if (!entryName.equals(apkGdbServerPath)) {
+ continue;
+ }
+
+ if (Config.LOGD) {
+ Log.d(TAG, "Found gdbserver: " + entry.getName());
+ }
+
+ final String installGdbServerPath = APK_LIB + GDBSERVER;
+ nativeFiles.add(Pair.create(entry, installGdbServerPath));
+
+ return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES;
+ }
+ return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES;
+ }
+
+ /*
+ * Examine shared libraries stored in the APK as
+ * lib/<cpuAbi>/lib<name>.so and add them to a list to be copied
+ * later.
+ *
+ * This function will first try the main CPU ABI defined by Build.CPU_ABI
+ * (which corresponds to ro.product.cpu.abi), and also try an alternate
+ * one if ro.product.cpu.abi2 is defined.
+ */
+ public static int listPackageNativeBinariesLI(ZipFile zipFile,
+ List<Pair<ZipEntry, String>> nativeFiles) throws ZipException, IOException {
+ String cpuAbi = Build.CPU_ABI;
+
+ int result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi, nativeFiles);
+
+ /*
+ * Some architectures are capable of supporting several CPU ABIs
+ * for example, 'armeabi-v7a' also supports 'armeabi' native code
+ * this is indicated by the definition of the ro.product.cpu.abi2
+ * system property.
+ *
+ * only scan the package twice in case of ABI mismatch
+ */
+ if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
+ final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2", null);
+ if (cpuAbi2 != null) {
+ result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi2, nativeFiles);
+ }
+
+ if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) {
+ Slog.w(TAG, "Native ABI mismatch from package file");
+ return PackageManager.INSTALL_FAILED_INVALID_APK;
+ }
+
+ if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+ cpuAbi = cpuAbi2;
+ }
+ }
+
+ /*
+ * Debuggable packages may have gdbserver embedded, so add it to
+ * the list to the list of items to be extracted (as lib/gdbserver)
+ * into the application's native library directory later.
+ */
+ if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) {
+ listPackageGdbServerLI(zipFile, cpuAbi, nativeFiles);
+ }
+ return PackageManager.INSTALL_SUCCEEDED;
+ }
+
+ public static int copyNativeBinariesLI(File scanFile, File sharedLibraryDir) {
+ /*
+ * Check all the native files that need to be copied and add
+ * that to the container size.
+ */
+ ZipFile zipFile;
+ try {
+ zipFile = new ZipFile(scanFile);
+
+ List<Pair<ZipEntry, String>> nativeFiles = new LinkedList<Pair<ZipEntry, String>>();
+
+ NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles);
+
+ final int N = nativeFiles.size();
+
+ for (int i = 0; i < N; i++) {
+ final Pair<ZipEntry, String> entry = nativeFiles.get(i);
+
+ File destFile = new File(sharedLibraryDir, entry.second);
+ copyNativeBinaryLI(zipFile, entry.first, sharedLibraryDir, destFile);
+ }
+ } catch (ZipException e) {
+ Slog.w(TAG, "Failed to extract data from package file", e);
+ return PackageManager.INSTALL_FAILED_INVALID_APK;
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to cache package shared libs", e);
+ return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ }
+
+ return PackageManager.INSTALL_SUCCEEDED;
+ }
+
+ private static void copyNativeBinaryLI(ZipFile zipFile, ZipEntry entry,
+ File binaryDir, File binaryFile) throws IOException {
+ InputStream inputStream = zipFile.getInputStream(entry);
+ try {
+ File tempFile = File.createTempFile("tmp", "tmp", binaryDir);
+ String tempFilePath = tempFile.getPath();
+ // XXX package manager can't change owner, so the executable files for
+ // now need to be left as world readable and owned by the system.
+ if (!FileUtils.copyToFile(inputStream, tempFile)
+ || !tempFile.setLastModified(entry.getTime())
+ || FileUtils.setPermissions(tempFilePath, FileUtils.S_IRUSR | FileUtils.S_IWUSR
+ | FileUtils.S_IRGRP | FileUtils.S_IXUSR | FileUtils.S_IXGRP
+ | FileUtils.S_IXOTH | FileUtils.S_IROTH, -1, -1) != 0
+ || !tempFile.renameTo(binaryFile)) {
+ // Failed to properly write file.
+ tempFile.delete();
+ throw new IOException("Couldn't create cached binary " + binaryFile + " in "
+ + binaryDir);
+ }
+ } finally {
+ inputStream.close();
+ }
+ }
+
+ // Remove the native binaries of a given package. This simply
+ // gets rid of the files in the 'lib' sub-directory.
+ public static void removeNativeBinariesLI(String nativeLibraryPath) {
+ if (DEBUG_NATIVE) {
+ Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryPath);
+ }
+
+ /*
+ * Just remove any file in the directory. Since the directory is owned
+ * by the 'system' UID, the application is not supposed to have written
+ * anything there.
+ */
+ File binaryDir = new File(nativeLibraryPath);
+ if (binaryDir.exists()) {
+ File[] binaries = binaryDir.listFiles();
+ if (binaries != null) {
+ for (int nn = 0; nn < binaries.length; nn++) {
+ if (DEBUG_NATIVE) {
+ Slog.d(TAG, " Deleting " + binaries[nn].getName());
+ }
+ if (!binaries[nn].delete()) {
+ Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath());
+ }
+ }
+ }
+ // Do not delete 'lib' directory itself, or this will prevent
+ // installation of future updates.
+ }
+ }
+}
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 4d0a9e055ac6..d6c43f93ea07 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -56,22 +56,22 @@ public class PackageHelper {
return null;
}
- public static String createSdDir(File tmpPackageFile, String cid,
+ public static String createSdDir(long sizeBytes, String cid,
String sdEncKey, int uid) {
// Create mount point via MountService
IMountService mountService = getMountService();
- long len = tmpPackageFile.length();
- int mbLen = (int) (len >> 20);
- if ((len - (mbLen * 1024 * 1024)) > 0) {
- mbLen++;
+ int sizeMb = (int) (sizeBytes >> 20);
+ if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) {
+ sizeMb++;
}
// Add buffer size
- mbLen++;
- if (localLOGV) Log.i(TAG, "Size of container " + mbLen + " MB " + len + " bytes");
+ sizeMb++;
+ if (localLOGV)
+ Log.i(TAG, "Size of container " + sizeMb + " MB " + sizeBytes + " bytes");
try {
int rc = mountService.createSecureContainer(
- cid, mbLen, "fat", sdEncKey, uid);
+ cid, sizeMb, "fat", sdEncKey, uid);
if (rc != StorageResultCode.OperationSucceeded) {
Log.e(TAG, "Failed to create secure container " + cid);
return null;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aadb576d2c65..753dbf0e4141 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -20,12 +20,16 @@ import com.android.internal.util.JournaledFile;
import android.bluetooth.BluetoothHeadset;
import android.net.TrafficStats;
+import android.os.BatteryManager;
import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFormatException;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
+import android.os.WorkSource;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
@@ -56,17 +60,21 @@ import java.util.concurrent.atomic.AtomicInteger;
public final class BatteryStatsImpl extends BatteryStats {
private static final String TAG = "BatteryStatsImpl";
private static final boolean DEBUG = false;
-
+ private static final boolean DEBUG_HISTORY = false;
+
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 43;
+ private static final int VERSION = 50;
+ // Maximum number of items we will record in the history.
+ private static final int MAX_HISTORY_ITEMS = 2000;
+
// The maximum number of names wakelocks we will keep track of
// per uid; once the limit is reached, we batch the remaining wakelocks
// in to one common name.
- private static final int MAX_WAKELOCKS_PER_UID = 20;
+ private static final int MAX_WAKELOCKS_PER_UID = 30;
private static final String BATCHED_WAKELOCK_NAME = "*overflow*";
@@ -74,6 +82,38 @@ public final class BatteryStatsImpl extends BatteryStats {
private final JournaledFile mFile;
+ static final int MSG_UPDATE_WAKELOCKS = 1;
+ static final int MSG_REPORT_POWER_CHANGE = 2;
+ static final long DELAY_UPDATE_WAKELOCKS = 15*1000;
+
+ public interface BatteryCallback {
+ public void batteryNeedsCpuUpdate();
+ public void batteryPowerChanged(boolean onBattery);
+ }
+
+ final class MyHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ BatteryCallback cb = mCallback;
+ switch (msg.what) {
+ case MSG_UPDATE_WAKELOCKS:
+ if (cb != null) {
+ cb.batteryNeedsCpuUpdate();
+ }
+ break;
+ case MSG_REPORT_POWER_CHANGE:
+ if (cb != null) {
+ cb.batteryPowerChanged(msg.arg1 != 0);
+ }
+ break;
+ }
+ }
+ }
+
+ private final MyHandler mHandler;
+
+ private BatteryCallback mCallback;
+
/**
* The statistics we have collected organized by uids.
*/
@@ -90,10 +130,25 @@ public final class BatteryStatsImpl extends BatteryStats {
final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers
= new SparseArray<ArrayList<StopwatchTimer>>();
+ // Last partial timers we use for distributing CPU usage.
+ final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>();
+
// These are the objects that will want to do something when the device
// is unplugged from power.
final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>();
+ boolean mShuttingDown;
+
+ long mHistoryBaseTime;
+ boolean mHaveBatteryLevel = false;
+ boolean mRecordingHistory = true;
+ int mNumHistoryItems;
+ HistoryItem mHistory;
+ HistoryItem mHistoryEnd;
+ HistoryItem mHistoryLastEnd;
+ HistoryItem mHistoryCache;
+ final HistoryItem mHistoryCur = new HistoryItem();
+
int mStartCount;
long mBatteryUptime;
@@ -166,7 +221,10 @@ public final class BatteryStatsImpl extends BatteryStats {
* These keep track of battery levels (1-100) at the last plug event and the last unplug event.
*/
int mDischargeStartLevel;
+ int mDischargeUnplugLevel;
int mDischargeCurrentLevel;
+ int mLowDischargeAmountSinceCharge;
+ int mHighDischargeAmountSinceCharge;
long mLastWriteTime = 0; // Milliseconds
@@ -220,6 +278,7 @@ public final class BatteryStatsImpl extends BatteryStats {
// For debugging
public BatteryStatsImpl() {
mFile = null;
+ mHandler = null;
}
public static interface Unpluggable {
@@ -232,28 +291,30 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
public static class Counter extends BatteryStats.Counter implements Unpluggable {
final AtomicInteger mCount = new AtomicInteger();
+ final ArrayList<Unpluggable> mUnpluggables;
int mLoadedCount;
int mLastCount;
int mUnpluggedCount;
int mPluggedCount;
Counter(ArrayList<Unpluggable> unpluggables, Parcel in) {
+ mUnpluggables = unpluggables;
mPluggedCount = in.readInt();
mCount.set(mPluggedCount);
mLoadedCount = in.readInt();
- mLastCount = in.readInt();
+ mLastCount = 0;
mUnpluggedCount = in.readInt();
unpluggables.add(this);
}
Counter(ArrayList<Unpluggable> unpluggables) {
+ mUnpluggables = unpluggables;
unpluggables.add(this);
}
public void writeToParcel(Parcel out) {
out.writeInt(mCount.get());
out.writeInt(mLoadedCount);
- out.writeInt(mLastCount);
out.writeInt(mUnpluggedCount);
}
@@ -289,9 +350,9 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mLastCount;
} else {
val = mCount.get();
- if (which == STATS_UNPLUGGED) {
+ if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedCount;
- } else if (which != STATS_TOTAL) {
+ } else if (which != STATS_SINCE_CHARGED) {
val -= mLoadedCount;
}
}
@@ -310,16 +371,30 @@ public final class BatteryStatsImpl extends BatteryStats {
mCount.incrementAndGet();
}
+ /**
+ * Clear state of this counter.
+ */
+ void reset(boolean detachIfReset) {
+ mCount.set(0);
+ mLoadedCount = mLastCount = mPluggedCount = mUnpluggedCount = 0;
+ if (detachIfReset) {
+ detach();
+ }
+ }
+
+ void detach() {
+ mUnpluggables.remove(this);
+ }
+
void writeSummaryFromParcelLocked(Parcel out) {
int count = mCount.get();
out.writeInt(count);
- out.writeInt(count - mLoadedCount);
}
void readSummaryFromParcelLocked(Parcel in) {
mLoadedCount = in.readInt();
mCount.set(mLoadedCount);
- mLastCount = in.readInt();
+ mLastCount = 0;
mUnpluggedCount = mPluggedCount = mLoadedCount;
}
}
@@ -344,7 +419,7 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
public static abstract class Timer extends BatteryStats.Timer implements Unpluggable {
final int mType;
-
+ final ArrayList<Unpluggable> mUnpluggables;
int mCount;
int mLoadedCount;
@@ -389,20 +464,22 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) {
mType = type;
+ mUnpluggables = unpluggables;
mCount = in.readInt();
mLoadedCount = in.readInt();
- mLastCount = in.readInt();
+ mLastCount = 0;
mUnpluggedCount = in.readInt();
mTotalTime = in.readLong();
mLoadedTime = in.readLong();
- mLastTime = in.readLong();
+ mLastTime = 0;
mUnpluggedTime = in.readLong();
unpluggables.add(this);
}
Timer(int type, ArrayList<Unpluggable> unpluggables) {
mType = type;
+ mUnpluggables = unpluggables;
unpluggables.add(this);
}
@@ -410,15 +487,29 @@ public final class BatteryStatsImpl extends BatteryStats {
protected abstract int computeCurrentCountLocked();
+ /**
+ * Clear state of this timer. Returns true if the timer is inactive
+ * so can be completely dropped.
+ */
+ boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ mTotalTime = mLoadedTime = mLastTime = 0;
+ mCount = mLoadedCount = mLastCount = 0;
+ if (detachIfReset) {
+ detach();
+ }
+ return true;
+ }
+
+ void detach() {
+ mUnpluggables.remove(this);
+ }
public void writeToParcel(Parcel out, long batteryRealtime) {
out.writeInt(mCount);
out.writeInt(mLoadedCount);
- out.writeInt(mLastCount);
out.writeInt(mUnpluggedCount);
out.writeLong(computeRunTimeLocked(batteryRealtime));
out.writeLong(mLoadedTime);
- out.writeLong(mLastTime);
out.writeLong(mUnpluggedTime);
}
@@ -474,9 +565,9 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mLastTime;
} else {
val = computeRunTimeLocked(batteryRealtime);
- if (which == STATS_UNPLUGGED) {
+ if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedTime;
- } else if (which != STATS_TOTAL) {
+ } else if (which != STATS_SINCE_CHARGED) {
val -= mLoadedTime;
}
}
@@ -491,9 +582,9 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mLastCount;
} else {
val = computeCurrentCountLocked();
- if (which == STATS_UNPLUGGED) {
+ if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedCount;
- } else if (which != STATS_TOTAL) {
+ } else if (which != STATS_SINCE_CHARGED) {
val -= mLoadedCount;
}
}
@@ -516,18 +607,16 @@ public final class BatteryStatsImpl extends BatteryStats {
long runTime = computeRunTimeLocked(batteryRealtime);
// Divide by 1000 for backwards compatibility
out.writeLong((runTime + 500) / 1000);
- out.writeLong(((runTime - mLoadedTime) + 500) / 1000);
out.writeInt(mCount);
- out.writeInt(mCount - mLoadedCount);
}
void readSummaryFromParcelLocked(Parcel in) {
// Multiply by 1000 for backwards compatibility
mTotalTime = mLoadedTime = in.readLong() * 1000;
- mLastTime = in.readLong() * 1000;
+ mLastTime = 0;
mUnpluggedTime = mTotalTime;
mCount = mLoadedCount = in.readInt();
- mLastCount = in.readInt();
+ mLastCount = 0;
mUnpluggedCount = mCount;
}
}
@@ -664,6 +753,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mTrackingReportedValues ? 1 : 0);
}
+ boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ super.reset(stats, detachIfReset);
+ setStale();
+ return true;
+ }
+
void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) {
super.writeSummaryFromParcelLocked(out, batteryRealtime);
out.writeLong(mCurrentReportedTotalTime);
@@ -683,9 +778,10 @@ public final class BatteryStatsImpl extends BatteryStats {
* State for keeping track of timing information.
*/
public static final class StopwatchTimer extends Timer {
+ final Uid mUid;
final ArrayList<StopwatchTimer> mTimerPool;
- int mNesting;
+ int mNesting;
/**
* The last time at which we updated the timer. If mNesting is > 0,
@@ -695,23 +791,31 @@ public final class BatteryStatsImpl extends BatteryStats {
long mUpdateTime;
/**
- * The total time at which the timer was acquired, to determine if
+ * The total time at which the timer was acquired, to determine if it
* was actually held for an interesting duration.
*/
long mAcquireTime;
long mTimeout;
- StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool,
+ /**
+ * For partial wake locks, keep track of whether we are in the list
+ * to consume CPU cycles.
+ */
+ boolean mInList;
+
+ StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
ArrayList<Unpluggable> unpluggables, Parcel in) {
super(type, unpluggables, in);
+ mUid = uid;
mTimerPool = timerPool;
mUpdateTime = in.readLong();
}
- StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool,
+ StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool,
ArrayList<Unpluggable> unpluggables) {
super(type, unpluggables);
+ mUid = uid;
mTimerPool = timerPool;
}
@@ -836,6 +940,24 @@ public final class BatteryStatsImpl extends BatteryStats {
return mCount;
}
+ boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ boolean canDetach = mNesting <= 0;
+ super.reset(stats, canDetach && detachIfReset);
+ if (mNesting > 0) {
+ mUpdateTime = stats.getBatteryRealtimeLocked(
+ SystemClock.elapsedRealtime() * 1000);
+ }
+ mAcquireTime = mTotalTime;
+ return canDetach;
+ }
+
+ void detach() {
+ super.detach();
+ if (mTimerPool != null) {
+ mTimerPool.remove(this);
+ }
+ }
+
void readSummaryFromParcelLocked(Parcel in) {
super.readSummaryFromParcelLocked(in);
mNesting = 0;
@@ -973,12 +1095,12 @@ public final class BatteryStatsImpl extends BatteryStats {
}
private void doDataPlug(long[] dataTransfer, long currentBytes) {
- dataTransfer[STATS_LAST] = dataTransfer[STATS_UNPLUGGED];
- dataTransfer[STATS_UNPLUGGED] = -1;
+ dataTransfer[STATS_LAST] = dataTransfer[STATS_SINCE_UNPLUGGED];
+ dataTransfer[STATS_SINCE_UNPLUGGED] = -1;
}
private void doDataUnplug(long[] dataTransfer, long currentBytes) {
- dataTransfer[STATS_UNPLUGGED] = currentBytes;
+ dataTransfer[STATS_SINCE_UNPLUGGED] = currentBytes;
}
/**
@@ -1042,7 +1164,81 @@ public final class BatteryStatsImpl extends BatteryStats {
mBtHeadset = headset;
}
- public void doUnplug(long batteryUptime, long batteryRealtime) {
+ void addHistoryRecordLocked(long curTime) {
+ if (!mHaveBatteryLevel || !mRecordingHistory) {
+ return;
+ }
+
+ // If the current time is basically the same as the last time,
+ // just collapse into one record.
+ if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE
+ && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)) {
+ // If the current is the same as the one before, then we no
+ // longer need the entry.
+ if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE
+ && mHistoryLastEnd.same(mHistoryCur)) {
+ mHistoryLastEnd.next = null;
+ mHistoryEnd.next = mHistoryCache;
+ mHistoryCache = mHistoryEnd;
+ mHistoryEnd = mHistoryLastEnd;
+ mHistoryLastEnd = null;
+ } else {
+ mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, mHistoryCur);
+ }
+ return;
+ }
+
+ if (mNumHistoryItems == MAX_HISTORY_ITEMS) {
+ addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW);
+ }
+
+ if (mNumHistoryItems >= MAX_HISTORY_ITEMS) {
+ // Once we've reached the maximum number of items, we only
+ // record changes to the battery level.
+ if (mHistoryEnd != null && mHistoryEnd.batteryLevel
+ == mHistoryCur.batteryLevel) {
+ return;
+ }
+ }
+
+ addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE);
+ }
+
+ void addHistoryRecordLocked(long curTime, byte cmd) {
+ HistoryItem rec = mHistoryCache;
+ if (rec != null) {
+ mHistoryCache = rec.next;
+ } else {
+ rec = new HistoryItem();
+ }
+ rec.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur);
+
+ addHistoryRecordLocked(rec);
+ }
+
+ void addHistoryRecordLocked(HistoryItem rec) {
+ mNumHistoryItems++;
+ rec.next = null;
+ mHistoryLastEnd = mHistoryEnd;
+ if (mHistoryEnd != null) {
+ mHistoryEnd.next = rec;
+ mHistoryEnd = rec;
+ } else {
+ mHistory = mHistoryEnd = rec;
+ }
+ }
+
+ void clearHistoryLocked() {
+ if (mHistory != null) {
+ mHistoryEnd.next = mHistoryCache;
+ mHistoryCache = mHistory;
+ mHistory = mHistoryLastEnd = mHistoryEnd = null;
+ }
+ mNumHistoryItems = 0;
+ mHistoryBaseTime = 0;
+ }
+
+ 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);
@@ -1066,7 +1262,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothPingCount = 0;
}
- public void doPlug(long batteryUptime, long batteryRealtime) {
+ public void doPlugLocked(long batteryUptime, long batteryRealtime) {
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
Uid u = mUidStats.valueAt(iu);
if (u.mStartedTcpBytesReceived >= 0) {
@@ -1094,31 +1290,264 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothPingStart = -1;
}
- public void noteStartGps(int uid) {
+ int mWakeLockNesting;
+
+ public void noteStartWakeLocked(int uid, int pid, String name, int type) {
+ if (type == WAKE_TYPE_PARTIAL) {
+ // Only care about partial wake locks, since full wake locks
+ // will be canceled when the user puts the screen to sleep.
+ if (mWakeLockNesting == 0) {
+ mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ mWakeLockNesting++;
+ }
+ if (uid >= 0) {
+ if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+ Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+ mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+ }
+ getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type);
+ }
+ }
+
+ public void noteStopWakeLocked(int uid, int pid, String name, int type) {
+ if (type == WAKE_TYPE_PARTIAL) {
+ mWakeLockNesting--;
+ if (mWakeLockNesting == 0) {
+ mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ }
+ if (uid >= 0) {
+ if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) {
+ Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS);
+ mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS);
+ }
+ getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type);
+ }
+ }
+
+ public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteStartWakeLocked(ws.get(i), pid, name, type);
+ }
+ }
+
+ public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteStopWakeLocked(ws.get(i), pid, name, type);
+ }
+ }
+
+ public int startAddingCpuLocked() {
+ mHandler.removeMessages(MSG_UPDATE_WAKELOCKS);
+
+ if (mScreenOn) {
+ return 0;
+ }
+
+ final int N = mPartialTimers.size();
+ if (N == 0) {
+ mLastPartialTimers.clear();
+ return 0;
+ }
+
+ // How many timers should consume CPU? Only want to include ones
+ // that have already been in the list.
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ if (st.mInList) {
+ Uid uid = st.mUid;
+ // We don't include the system UID, because it so often
+ // holds wake locks at one request or another of an app.
+ if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+ return 50;
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ public void finishAddingCpuLocked(int perc, int utime, int stime, long[] cpuSpeedTimes) {
+ final int N = mPartialTimers.size();
+ if (perc != 0) {
+ int num = 0;
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ if (st.mInList) {
+ Uid uid = st.mUid;
+ // We don't include the system UID, because it so often
+ // holds wake locks at one request or another of an app.
+ if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+ num++;
+ }
+ }
+ }
+ if (num != 0) {
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ if (st.mInList) {
+ Uid uid = st.mUid;
+ if (uid != null && uid.mUid != Process.SYSTEM_UID) {
+ int myUTime = utime/num;
+ int mySTime = stime/num;
+ utime -= myUTime;
+ stime -= mySTime;
+ num--;
+ Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*");
+ proc.addCpuTimeLocked(myUTime, mySTime);
+ proc.addSpeedStepTimes(cpuSpeedTimes);
+ }
+ }
+ }
+ }
+
+ // Just in case, collect any lost CPU time.
+ if (utime != 0 || stime != 0) {
+ Uid uid = getUidStatsLocked(Process.SYSTEM_UID);
+ if (uid != null) {
+ Uid.Proc proc = uid.getProcessStatsLocked("*lost*");
+ proc.addCpuTimeLocked(utime, stime);
+ proc.addSpeedStepTimes(cpuSpeedTimes);
+ }
+ }
+ }
+
+ final int NL = mLastPartialTimers.size();
+ boolean diff = N != NL;
+ for (int i=0; i<NL && !diff; i++) {
+ diff |= mPartialTimers.get(i) != mLastPartialTimers.get(i);
+ }
+ if (!diff) {
+ for (int i=0; i<NL; i++) {
+ mPartialTimers.get(i).mInList = true;
+ }
+ return;
+ }
+
+ for (int i=0; i<NL; i++) {
+ mLastPartialTimers.get(i).mInList = false;
+ }
+ mLastPartialTimers.clear();
+ for (int i=0; i<N; i++) {
+ StopwatchTimer st = mPartialTimers.get(i);
+ st.mInList = true;
+ mLastPartialTimers.add(st);
+ }
+ }
+
+ public void noteProcessDiedLocked(int uid, int pid) {
+ Uid u = mUidStats.get(uid);
+ if (u != null) {
+ u.mPids.remove(pid);
+ }
+ }
+
+ public long getProcessWakeTime(int uid, int pid, long realtime) {
+ Uid u = mUidStats.get(uid);
+ if (u != null) {
+ Uid.Pid p = u.mPids.get(pid);
+ if (p != null) {
+ return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0);
+ }
+ }
+ return 0;
+ }
+
+ public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) {
+ Uid u = mUidStats.get(uid);
+ if (u != null) {
+ u.reportExcessiveWakeLocked(proc, overTime, usedTime);
+ }
+ }
+
+ int mSensorNesting;
+
+ public void noteStartSensorLocked(int uid, int sensor) {
+ if (mSensorNesting == 0) {
+ mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ mSensorNesting++;
+ getUidStatsLocked(uid).noteStartSensor(sensor);
+ }
+
+ public void noteStopSensorLocked(int uid, int sensor) {
+ mSensorNesting--;
+ if (mSensorNesting == 0) {
+ mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ getUidStatsLocked(uid).noteStopSensor(sensor);
+ }
+
+ int mGpsNesting;
+
+ public void noteStartGpsLocked(int uid) {
+ if (mGpsNesting == 0) {
+ mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ mGpsNesting++;
getUidStatsLocked(uid).noteStartGps();
}
- public void noteStopGps(int uid) {
+ public void noteStopGpsLocked(int uid) {
+ mGpsNesting--;
+ if (mGpsNesting == 0) {
+ mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
getUidStatsLocked(uid).noteStopGps();
}
public void noteScreenOnLocked() {
if (!mScreenOn) {
+ mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mScreenOn = true;
mScreenOnTimer.startRunningLocked(this);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this);
}
+
+ // Fake a wake lock, so we consider the device waked as long
+ // as the screen is on.
+ noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
}
}
public void noteScreenOffLocked() {
if (mScreenOn) {
+ mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mScreenOn = false;
mScreenOnTimer.stopRunningLocked(this);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this);
}
+
+ noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL);
}
}
@@ -1128,6 +1557,11 @@ public final class BatteryStatsImpl extends BatteryStats {
if (bin < 0) bin = 0;
else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1;
if (mScreenBrightnessBin != bin) {
+ mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK)
+ | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+ if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
if (mScreenOn) {
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this);
@@ -1148,6 +1582,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void notePhoneOnLocked() {
if (!mPhoneOn) {
+ mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mPhoneOn = true;
mPhoneOnTimer.startRunningLocked(this);
}
@@ -1155,47 +1593,83 @@ public final class BatteryStatsImpl extends BatteryStats {
public void notePhoneOffLocked() {
if (mPhoneOn) {
+ mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mPhoneOn = false;
mPhoneOnTimer.stopRunningLocked(this);
}
}
+ void stopAllSignalStrengthTimersLocked(int except) {
+ for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) {
+ if (i == except) {
+ continue;
+ }
+ while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) {
+ mPhoneSignalStrengthsTimer[i].stopRunningLocked(this);
+ }
+ }
+ }
+
/**
* Telephony stack updates the phone state.
* @param state phone state from ServiceState.getState()
*/
public void notePhoneStateLocked(int state) {
+ boolean scanning = false;
+
int bin = mPhoneSignalStrengthBin;
- boolean isAirplaneMode = state == ServiceState.STATE_POWER_OFF;
- // Stop all timers
- if (isAirplaneMode || state == ServiceState.STATE_OUT_OF_SERVICE) {
- for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) {
- while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) {
- mPhoneSignalStrengthsTimer[i].stopRunningLocked(this);
- }
- }
- }
- // Stop Signal Scanning timer, in case we're going into service
- while (mPhoneSignalScanningTimer.isRunningLocked()) {
- mPhoneSignalScanningTimer.stopRunningLocked(this);
- }
+
+ // If the phone is powered off, stop all timers.
+ if (state == ServiceState.STATE_POWER_OFF) {
+ stopAllSignalStrengthTimersLocked(-1);
// If we're back in service or continuing in service, restart the old timer.
- if (state == ServiceState.STATE_IN_SERVICE) {
+ } if (state == ServiceState.STATE_IN_SERVICE) {
if (bin == -1) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) {
mPhoneSignalStrengthsTimer[bin].startRunningLocked(this);
}
+
+ // If we're out of service, we are in the lowest signal strength
+ // bin and have the scanning bit set.
} else if (state == ServiceState.STATE_OUT_OF_SERVICE) {
+ scanning = true;
mPhoneSignalStrengthBin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ stopAllSignalStrengthTimersLocked(mPhoneSignalStrengthBin);
if (!mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].isRunningLocked()) {
mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].startRunningLocked(this);
}
if (!mPhoneSignalScanningTimer.isRunningLocked()) {
+ mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mPhoneSignalScanningTimer.startRunningLocked(this);
}
}
- mPhoneServiceState = state;
+
+ if (!scanning) {
+ // If we are no longer scanning, then stop the scanning timer.
+ if (mPhoneSignalScanningTimer.isRunningLocked()) {
+ mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ mPhoneSignalScanningTimer.stopRunningLocked(this);
+ }
+ }
+
+ if (mPhoneServiceState != state) {
+ mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK)
+ | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
+ if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + bin + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ mPhoneServiceState = state;
+ }
}
public void notePhoneSignalStrengthLocked(SignalStrength signalStrength) {
@@ -1222,6 +1696,11 @@ public final class BatteryStatsImpl extends BatteryStats {
else bin = SIGNAL_STRENGTH_POOR;
}
if (mPhoneSignalStrengthBin != bin) {
+ mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK)
+ | (bin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT);
+ if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + bin + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
if (mPhoneSignalStrengthBin >= 0) {
mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this);
}
@@ -1243,6 +1722,33 @@ public final class BatteryStatsImpl extends BatteryStats {
case TelephonyManager.NETWORK_TYPE_UMTS:
bin = DATA_CONNECTION_UMTS;
break;
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ bin = DATA_CONNECTION_CDMA;
+ break;
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ bin = DATA_CONNECTION_EVDO_0;
+ break;
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ bin = DATA_CONNECTION_EVDO_A;
+ break;
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ bin = DATA_CONNECTION_1xRTT;
+ break;
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ bin = DATA_CONNECTION_HSDPA;
+ break;
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ bin = DATA_CONNECTION_HSUPA;
+ break;
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ bin = DATA_CONNECTION_HSPA;
+ break;
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ bin = DATA_CONNECTION_IDEN;
+ break;
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ bin = DATA_CONNECTION_EVDO_B;
+ break;
default:
bin = DATA_CONNECTION_OTHER;
break;
@@ -1250,6 +1756,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
+ mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
+ | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
+ if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
if (mPhoneDataConnectionType >= 0) {
mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this);
}
@@ -1260,6 +1771,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiOnLocked(int uid) {
if (!mWifiOn) {
+ mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mWifiOn = true;
mWifiOnTimer.startRunningLocked(this);
}
@@ -1274,6 +1789,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiOffLocked(int uid) {
if (mWifiOn) {
+ mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mWifiOn = false;
mWifiOnTimer.stopRunningLocked(this);
}
@@ -1285,6 +1804,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteAudioOnLocked(int uid) {
if (!mAudioOn) {
+ mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mAudioOn = true;
mAudioOnTimer.startRunningLocked(this);
}
@@ -1293,6 +1816,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteAudioOffLocked(int uid) {
if (mAudioOn) {
+ mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mAudioOn = false;
mAudioOnTimer.stopRunningLocked(this);
}
@@ -1301,6 +1828,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteVideoOnLocked(int uid) {
if (!mVideoOn) {
+ mHistoryCur.states |= HistoryItem.STATE_VIDEO_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mVideoOn = true;
mVideoOnTimer.startRunningLocked(this);
}
@@ -1309,6 +1840,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteVideoOffLocked(int uid) {
if (mVideoOn) {
+ mHistoryCur.states &= ~HistoryItem.STATE_VIDEO_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mVideoOn = false;
mVideoOnTimer.stopRunningLocked(this);
}
@@ -1317,6 +1852,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiRunningLocked() {
if (!mWifiRunning) {
+ mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mWifiRunning = true;
mWifiRunningTimer.startRunningLocked(this);
}
@@ -1324,6 +1863,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiStoppedLocked() {
if (mWifiRunning) {
+ mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mWifiRunning = false;
mWifiRunningTimer.stopRunningLocked(this);
}
@@ -1331,6 +1874,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteBluetoothOnLocked() {
if (!mBluetoothOn) {
+ mHistoryCur.states |= HistoryItem.STATE_BLUETOOTH_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mBluetoothOn = true;
mBluetoothOnTimer.startRunningLocked(this);
}
@@ -1338,35 +1885,129 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteBluetoothOffLocked() {
if (mBluetoothOn) {
+ mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
mBluetoothOn = false;
mBluetoothOnTimer.stopRunningLocked(this);
}
}
+ int mWifiFullLockNesting = 0;
+
public void noteFullWifiLockAcquiredLocked(int uid) {
+ if (mWifiFullLockNesting == 0) {
+ mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ mWifiFullLockNesting++;
getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked();
}
public void noteFullWifiLockReleasedLocked(int uid) {
+ mWifiFullLockNesting--;
+ if (mWifiFullLockNesting == 0) {
+ mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
getUidStatsLocked(uid).noteFullWifiLockReleasedLocked();
}
+ int mWifiScanLockNesting = 0;
+
public void noteScanWifiLockAcquiredLocked(int uid) {
+ if (mWifiScanLockNesting == 0) {
+ mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ mWifiScanLockNesting++;
getUidStatsLocked(uid).noteScanWifiLockAcquiredLocked();
}
public void noteScanWifiLockReleasedLocked(int uid) {
+ mWifiScanLockNesting--;
+ if (mWifiScanLockNesting == 0) {
+ mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
getUidStatsLocked(uid).noteScanWifiLockReleasedLocked();
}
+ int mWifiMulticastNesting = 0;
+
public void noteWifiMulticastEnabledLocked(int uid) {
+ if (mWifiMulticastNesting == 0) {
+ mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ mWifiMulticastNesting++;
getUidStatsLocked(uid).noteWifiMulticastEnabledLocked();
}
public void noteWifiMulticastDisabledLocked(int uid) {
+ mWifiMulticastNesting--;
+ if (mWifiMulticastNesting == 0) {
+ mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
getUidStatsLocked(uid).noteWifiMulticastDisabledLocked();
}
+ public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteFullWifiLockAcquiredLocked(ws.get(i));
+ }
+ }
+
+ public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteFullWifiLockReleasedLocked(ws.get(i));
+ }
+ }
+
+ public void noteScanWifiLockAcquiredFromSourceLocked(WorkSource ws) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteScanWifiLockAcquiredLocked(ws.get(i));
+ }
+ }
+
+ public void noteScanWifiLockReleasedFromSourceLocked(WorkSource ws) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteScanWifiLockReleasedLocked(ws.get(i));
+ }
+ }
+
+ public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteWifiMulticastEnabledLocked(ws.get(i));
+ }
+ }
+
+ public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) {
+ int N = ws.size();
+ for (int i=0; i<N; i++) {
+ noteWifiMulticastDisabledLocked(ws.get(i));
+ }
+ }
+
@Override public long getScreenOnTime(long batteryRealtime, int which) {
return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which);
}
@@ -1489,15 +2130,25 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>();
+ /**
+ * The transient wake stats we have collected for this uid's pids.
+ */
+ final SparseArray<Pid> mPids = new SparseArray<Pid>();
+
public Uid(int uid) {
mUid = uid;
- mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables);
- mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK, null, mUnpluggables);
- mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables);
- mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
+ mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
+ null, mUnpluggables);
+ mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
+ null, mUnpluggables);
+ mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
+ null, mUnpluggables);
+ mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
+ null, mUnpluggables);
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+ null, mUnpluggables);
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
null, mUnpluggables);
- mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON, null, mUnpluggables);
- mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON, null, mUnpluggables);
}
@Override
@@ -1531,9 +2182,9 @@ public final class BatteryStatsImpl extends BatteryStats {
return mLoadedTcpBytesReceived;
} else {
long current = computeCurrentTcpBytesReceived();
- if (which == STATS_UNPLUGGED) {
+ if (which == STATS_SINCE_UNPLUGGED) {
current -= mTcpBytesReceivedAtLastUnplug;
- } else if (which == STATS_TOTAL) {
+ } else if (which == STATS_SINCE_CHARGED) {
current += mLoadedTcpBytesReceived;
}
return current;
@@ -1551,9 +2202,9 @@ public final class BatteryStatsImpl extends BatteryStats {
return mLoadedTcpBytesSent;
} else {
long current = computeCurrentTcpBytesSent();
- if (which == STATS_UNPLUGGED) {
+ if (which == STATS_SINCE_UNPLUGGED) {
current -= mTcpBytesSentAtLastUnplug;
- } else if (which == STATS_TOTAL) {
+ } else if (which == STATS_SINCE_CHARGED) {
current += mLoadedTcpBytesSent;
}
return current;
@@ -1564,6 +2215,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiTurnedOnLocked() {
if (!mWifiTurnedOn) {
mWifiTurnedOn = true;
+ if (mWifiTurnedOnTimer == null) {
+ mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
+ null, mUnpluggables);
+ }
mWifiTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
}
}
@@ -1580,43 +2235,15 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteFullWifiLockAcquiredLocked() {
if (!mFullWifiLockOut) {
mFullWifiLockOut = true;
+ if (mFullWifiLockTimer == null) {
+ mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
+ null, mUnpluggables);
+ }
mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
}
}
@Override
- public void noteVideoTurnedOnLocked() {
- if (!mVideoTurnedOn) {
- mVideoTurnedOn = true;
- mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
- }
- }
-
- @Override
- public void noteVideoTurnedOffLocked() {
- if (mVideoTurnedOn) {
- mVideoTurnedOn = false;
- mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
- }
- }
-
- @Override
- public void noteAudioTurnedOnLocked() {
- if (!mAudioTurnedOn) {
- mAudioTurnedOn = true;
- mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
- }
- }
-
- @Override
- public void noteAudioTurnedOffLocked() {
- if (mAudioTurnedOn) {
- mAudioTurnedOn = false;
- mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
- }
- }
-
- @Override
public void noteFullWifiLockReleasedLocked() {
if (mFullWifiLockOut) {
mFullWifiLockOut = false;
@@ -1628,6 +2255,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteScanWifiLockAcquiredLocked() {
if (!mScanWifiLockOut) {
mScanWifiLockOut = true;
+ if (mScanWifiLockTimer == null) {
+ mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
+ null, mUnpluggables);
+ }
mScanWifiLockTimer.startRunningLocked(BatteryStatsImpl.this);
}
}
@@ -1644,6 +2275,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteWifiMulticastEnabledLocked() {
if (!mWifiMulticastEnabled) {
mWifiMulticastEnabled = true;
+ if (mWifiMulticastTimer == null) {
+ mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
+ null, mUnpluggables);
+ }
mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this);
}
}
@@ -1656,37 +2291,95 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- @Override
- public long getWifiTurnedOnTime(long batteryRealtime, int which) {
- return mWifiTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override
+ public void noteAudioTurnedOnLocked() {
+ if (!mAudioTurnedOn) {
+ mAudioTurnedOn = true;
+ if (mAudioTurnedOnTimer == null) {
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+ null, mUnpluggables);
+ }
+ mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+ }
}
- @Override
- public long getAudioTurnedOnTime(long batteryRealtime, int which) {
- return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ @Override
+ public void noteAudioTurnedOffLocked() {
+ if (mAudioTurnedOn) {
+ mAudioTurnedOn = false;
+ mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
+ @Override
+ public void noteVideoTurnedOnLocked() {
+ if (!mVideoTurnedOn) {
+ mVideoTurnedOn = true;
+ if (mVideoTurnedOnTimer == null) {
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
+ null, mUnpluggables);
+ }
+ mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+
+ @Override
+ public void noteVideoTurnedOffLocked() {
+ if (mVideoTurnedOn) {
+ mVideoTurnedOn = false;
+ mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ }
}
@Override
- public long getVideoTurnedOnTime(long batteryRealtime, int which) {
- return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ public long getWifiTurnedOnTime(long batteryRealtime, int which) {
+ if (mWifiTurnedOnTimer == null) {
+ return 0;
+ }
+ return mWifiTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
}
@Override
public long getFullWifiLockTime(long batteryRealtime, int which) {
+ if (mFullWifiLockTimer == null) {
+ return 0;
+ }
return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which);
}
@Override
public long getScanWifiLockTime(long batteryRealtime, int which) {
+ if (mScanWifiLockTimer == null) {
+ return 0;
+ }
return mScanWifiLockTimer.getTotalTimeLocked(batteryRealtime, which);
}
@Override
public long getWifiMulticastTime(long batteryRealtime, int which) {
+ if (mWifiMulticastTimer == null) {
+ return 0;
+ }
return mWifiMulticastTimer.getTotalTimeLocked(batteryRealtime,
which);
}
+ @Override
+ public long getAudioTurnedOnTime(long batteryRealtime, int which) {
+ if (mAudioTurnedOnTimer == null) {
+ return 0;
+ }
+ return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ }
+
+ @Override
+ public long getVideoTurnedOnTime(long batteryRealtime, int which) {
+ if (mVideoTurnedOnTimer == null) {
+ return 0;
+ }
+ return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ }
+
@Override
public void noteUserActivityLocked(int type) {
if (mUserActivityCounters == null) {
@@ -1722,6 +2415,136 @@ public final class BatteryStatsImpl extends BatteryStats {
? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
}
+ /**
+ * Clear all stats for this uid. Returns true if the uid is completely
+ * inactive so can be dropped.
+ */
+ boolean reset() {
+ boolean active = false;
+
+ if (mWifiTurnedOnTimer != null) {
+ active |= !mWifiTurnedOnTimer.reset(BatteryStatsImpl.this, false);
+ active |= mWifiTurnedOn;
+ }
+ if (mFullWifiLockTimer != null) {
+ active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false);
+ active |= mFullWifiLockOut;
+ }
+ if (mScanWifiLockTimer != null) {
+ active |= !mScanWifiLockTimer.reset(BatteryStatsImpl.this, false);
+ active |= mScanWifiLockOut;
+ }
+ if (mWifiMulticastTimer != null) {
+ active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false);
+ active |= mWifiMulticastEnabled;
+ }
+ if (mAudioTurnedOnTimer != null) {
+ active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false);
+ active |= mAudioTurnedOn;
+ }
+ if (mVideoTurnedOnTimer != null) {
+ active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
+ active |= mVideoTurnedOn;
+ }
+
+ mLoadedTcpBytesReceived = mLoadedTcpBytesSent = 0;
+ mCurrentTcpBytesReceived = mCurrentTcpBytesSent = 0;
+
+ if (mUserActivityCounters != null) {
+ for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
+ mUserActivityCounters[i].reset(false);
+ }
+ }
+
+ if (mWakelockStats.size() > 0) {
+ Iterator<Map.Entry<String, Wakelock>> it = mWakelockStats.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Wakelock> wakelockEntry = it.next();
+ Wakelock wl = wakelockEntry.getValue();
+ if (wl.reset()) {
+ it.remove();
+ } else {
+ active = true;
+ }
+ }
+ }
+ if (mSensorStats.size() > 0) {
+ Iterator<Map.Entry<Integer, Sensor>> it = mSensorStats.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Integer, Sensor> sensorEntry = it.next();
+ Sensor s = sensorEntry.getValue();
+ if (s.reset()) {
+ it.remove();
+ } else {
+ active = true;
+ }
+ }
+ }
+ if (mProcessStats.size() > 0) {
+ Iterator<Map.Entry<String, Proc>> it = mProcessStats.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Proc> procEntry = it.next();
+ procEntry.getValue().detach();
+ }
+ mProcessStats.clear();
+ }
+ if (mPids.size() > 0) {
+ for (int i=0; !active && i<mPids.size(); i++) {
+ Pid pid = mPids.valueAt(i);
+ if (pid.mWakeStart != 0) {
+ active = true;
+ }
+ }
+ }
+ if (mPackageStats.size() > 0) {
+ Iterator<Map.Entry<String, Pkg>> it = mPackageStats.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Pkg> pkgEntry = it.next();
+ Pkg p = pkgEntry.getValue();
+ p.detach();
+ if (p.mServiceStats.size() > 0) {
+ Iterator<Map.Entry<String, Pkg.Serv>> it2
+ = p.mServiceStats.entrySet().iterator();
+ while (it2.hasNext()) {
+ Map.Entry<String, Pkg.Serv> servEntry = it2.next();
+ servEntry.getValue().detach();
+ }
+ }
+ }
+ mPackageStats.clear();
+ }
+
+ mPids.clear();
+
+ if (!active) {
+ if (mWifiTurnedOnTimer != null) {
+ mWifiTurnedOnTimer.detach();
+ }
+ if (mFullWifiLockTimer != null) {
+ mFullWifiLockTimer.detach();
+ }
+ if (mScanWifiLockTimer != null) {
+ mScanWifiLockTimer.detach();
+ }
+ if (mWifiMulticastTimer != null) {
+ mWifiMulticastTimer.detach();
+ }
+ if (mAudioTurnedOnTimer != null) {
+ mAudioTurnedOnTimer.detach();
+ }
+ if (mVideoTurnedOnTimer != null) {
+ mVideoTurnedOnTimer.detach();
+ }
+ if (mUserActivityCounters != null) {
+ for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
+ mUserActivityCounters[i].detach();
+ }
+ }
+ }
+
+ return !active;
+ }
+
void writeToParcelLocked(Parcel out, long batteryRealtime) {
out.writeInt(mWakelockStats.size());
for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) {
@@ -1757,19 +2580,49 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(computeCurrentTcpBytesSent());
out.writeLong(mTcpBytesReceivedAtLastUnplug);
out.writeLong(mTcpBytesSentAtLastUnplug);
- mWifiTurnedOnTimer.writeToParcel(out, batteryRealtime);
- mFullWifiLockTimer.writeToParcel(out, batteryRealtime);
- mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime);
- mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime);
- mScanWifiLockTimer.writeToParcel(out, batteryRealtime);
- mWifiMulticastTimer.writeToParcel(out, batteryRealtime);
- if (mUserActivityCounters == null) {
+ if (mWifiTurnedOnTimer != null) {
+ out.writeInt(1);
+ mWifiTurnedOnTimer.writeToParcel(out, batteryRealtime);
+ } else {
out.writeInt(0);
+ }
+ if (mFullWifiLockTimer != null) {
+ out.writeInt(1);
+ mFullWifiLockTimer.writeToParcel(out, batteryRealtime);
} else {
+ out.writeInt(0);
+ }
+ if (mScanWifiLockTimer != null) {
+ out.writeInt(1);
+ mScanWifiLockTimer.writeToParcel(out, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
+ if (mWifiMulticastTimer != null) {
+ out.writeInt(1);
+ mWifiMulticastTimer.writeToParcel(out, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
+ if (mAudioTurnedOnTimer != null) {
+ out.writeInt(1);
+ mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
+ if (mVideoTurnedOnTimer != null) {
+ out.writeInt(1);
+ mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
+ if (mUserActivityCounters != null) {
out.writeInt(1);
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
mUserActivityCounters[i].writeToParcel(out);
}
+ } else {
+ out.writeInt(0);
}
}
@@ -1822,25 +2675,54 @@ public final class BatteryStatsImpl extends BatteryStats {
mTcpBytesReceivedAtLastUnplug = in.readLong();
mTcpBytesSentAtLastUnplug = in.readLong();
mWifiTurnedOn = false;
- mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables, in);
+ if (in.readInt() != 0) {
+ mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON,
+ null, mUnpluggables, in);
+ } else {
+ mWifiTurnedOnTimer = null;
+ }
mFullWifiLockOut = false;
- mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK, null, mUnpluggables, in);
- mAudioTurnedOn = false;
- mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON, null, mUnpluggables, in);
- mVideoTurnedOn = false;
- mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON, null, mUnpluggables, in);
+ if (in.readInt() != 0) {
+ mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK,
+ null, mUnpluggables, in);
+ } else {
+ mFullWifiLockTimer = null;
+ }
mScanWifiLockOut = false;
- mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables, in);
+ if (in.readInt() != 0) {
+ mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK,
+ null, mUnpluggables, in);
+ } else {
+ mScanWifiLockTimer = null;
+ }
mWifiMulticastEnabled = false;
- mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
- null, mUnpluggables, in);
- if (in.readInt() == 0) {
- mUserActivityCounters = null;
+ if (in.readInt() != 0) {
+ mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
+ null, mUnpluggables, in);
+ } else {
+ mWifiMulticastTimer = null;
+ }
+ mAudioTurnedOn = false;
+ if (in.readInt() != 0) {
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+ null, mUnpluggables, in);
} else {
+ mAudioTurnedOnTimer = null;
+ }
+ mVideoTurnedOn = false;
+ if (in.readInt() != 0) {
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
+ null, mUnpluggables, in);
+ } else {
+ mVideoTurnedOnTimer = null;
+ }
+ if (in.readInt() != 0) {
mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
mUserActivityCounters[i] = new Counter(mUnpluggables, in);
}
+ } else {
+ mUserActivityCounters = null;
}
}
@@ -1876,9 +2758,37 @@ public final class BatteryStatsImpl extends BatteryStats {
return null;
}
- return new StopwatchTimer(type, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, type, pool, unpluggables, in);
}
+ boolean reset() {
+ boolean wlactive = false;
+ if (mTimerFull != null) {
+ wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false);
+ }
+ if (mTimerPartial != null) {
+ wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false);
+ }
+ if (mTimerWindow != null) {
+ wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false);
+ }
+ if (!wlactive) {
+ if (mTimerFull != null) {
+ mTimerFull.detach();
+ mTimerFull = null;
+ }
+ if (mTimerPartial != null) {
+ mTimerPartial.detach();
+ mTimerPartial = null;
+ }
+ if (mTimerWindow != null) {
+ mTimerWindow.detach();
+ mTimerWindow = null;
+ }
+ }
+ return !wlactive;
+ }
+
void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL,
mPartialTimers, unpluggables, in);
@@ -1924,9 +2834,17 @@ public final class BatteryStatsImpl extends BatteryStats {
pool = new ArrayList<StopwatchTimer>();
mSensorTimers.put(mHandle, pool);
}
- return new StopwatchTimer(0, pool, unpluggables, in);
+ return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in);
}
+ boolean reset() {
+ if (mTimer.reset(BatteryStatsImpl.this, true)) {
+ mTimer = null;
+ return true;
+ }
+ return false;
+ }
+
void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) {
mTimer = readTimerFromParcel(unpluggables, in);
}
@@ -2032,12 +2950,11 @@ public final class BatteryStatsImpl extends BatteryStats {
SamplingCounter[] mSpeedBins;
+ ArrayList<ExcessiveWake> mExcessiveWake;
+
Proc() {
mUnpluggables.add(this);
mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
- for (int i = 0; i < mSpeedBins.length; i++) {
- mSpeedBins[i] = new SamplingCounter(mUnpluggables);
- }
}
public void unplug(long batteryUptime, long batteryRealtime) {
@@ -2050,10 +2967,70 @@ public final class BatteryStatsImpl extends BatteryStats {
public void plug(long batteryUptime, long batteryRealtime) {
}
+ void detach() {
+ mUnpluggables.remove(this);
+ for (int i = 0; i < mSpeedBins.length; i++) {
+ SamplingCounter c = mSpeedBins[i];
+ if (c != null) {
+ mUnpluggables.remove(c);
+ mSpeedBins[i] = null;
+ }
+ }
+ }
+
+ public int countExcessiveWakes() {
+ return mExcessiveWake != null ? mExcessiveWake.size() : 0;
+ }
+
+ public ExcessiveWake getExcessiveWake(int i) {
+ if (mExcessiveWake != null) {
+ return mExcessiveWake.get(i);
+ }
+ return null;
+ }
+
+ public void addExcessiveWake(long overTime, long usedTime) {
+ if (mExcessiveWake == null) {
+ mExcessiveWake = new ArrayList<ExcessiveWake>();
+ }
+ ExcessiveWake ew = new ExcessiveWake();
+ ew.overTime = overTime;
+ ew.usedTime = usedTime;
+ mExcessiveWake.add(ew);
+ }
+
+ void writeExcessiveWakeToParcelLocked(Parcel out) {
+ if (mExcessiveWake == null) {
+ out.writeInt(0);
+ return;
+ }
+
+ final int N = mExcessiveWake.size();
+ out.writeInt(N);
+ for (int i=0; i<N; i++) {
+ ExcessiveWake ew = mExcessiveWake.get(i);
+ out.writeLong(ew.overTime);
+ out.writeLong(ew.usedTime);
+ }
+ }
+
+ void readExcessiveWakeFromParcelLocked(Parcel in) {
+ final int N = in.readInt();
+ if (N == 0) {
+ mExcessiveWake = null;
+ return;
+ }
+
+ mExcessiveWake = new ArrayList<ExcessiveWake>();
+ for (int i=0; i<N; i++) {
+ ExcessiveWake ew = new ExcessiveWake();
+ ew.overTime = in.readLong();
+ ew.usedTime = in.readLong();
+ mExcessiveWake.add(ew);
+ }
+ }
+
void writeToParcelLocked(Parcel out) {
- final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
- final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime);
-
out.writeLong(mUserTime);
out.writeLong(mSystemTime);
out.writeLong(mForegroundTime);
@@ -2062,10 +3039,6 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mLoadedSystemTime);
out.writeLong(mLoadedForegroundTime);
out.writeInt(mLoadedStarts);
- out.writeLong(mLastUserTime);
- out.writeLong(mLastSystemTime);
- out.writeLong(mLastForegroundTime);
- out.writeInt(mLastStarts);
out.writeLong(mUnpluggedUserTime);
out.writeLong(mUnpluggedSystemTime);
out.writeLong(mUnpluggedForegroundTime);
@@ -2073,8 +3046,16 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mSpeedBins.length);
for (int i = 0; i < mSpeedBins.length; i++) {
- mSpeedBins[i].writeToParcel(out);
+ SamplingCounter c = mSpeedBins[i];
+ if (c != null) {
+ out.writeInt(1);
+ c.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
}
+
+ writeExcessiveWakeToParcelLocked(out);
}
void readFromParcelLocked(Parcel in) {
@@ -2086,20 +3067,25 @@ public final class BatteryStatsImpl extends BatteryStats {
mLoadedSystemTime = in.readLong();
mLoadedForegroundTime = in.readLong();
mLoadedStarts = in.readInt();
- mLastUserTime = in.readLong();
- mLastSystemTime = in.readLong();
- mLastForegroundTime = in.readLong();
- mLastStarts = in.readInt();
+ mLastUserTime = 0;
+ mLastSystemTime = 0;
+ mLastForegroundTime = 0;
+ mLastStarts = 0;
mUnpluggedUserTime = in.readLong();
mUnpluggedSystemTime = in.readLong();
mUnpluggedForegroundTime = in.readLong();
mUnpluggedStarts = in.readInt();
int bins = in.readInt();
- mSpeedBins = new SamplingCounter[bins];
+ int steps = getCpuSpeedSteps();
+ mSpeedBins = new SamplingCounter[bins >= steps ? bins : steps];
for (int i = 0; i < bins; i++) {
- mSpeedBins[i] = new SamplingCounter(mUnpluggables, in);
+ if (in.readInt() != 0) {
+ mSpeedBins[i] = new SamplingCounter(mUnpluggables, in);
+ }
}
+
+ readExcessiveWakeFromParcelLocked(in);
}
public BatteryStatsImpl getBatteryStats() {
@@ -2128,7 +3114,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mUserTime;
if (which == STATS_CURRENT) {
val -= mLoadedUserTime;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedUserTime;
}
}
@@ -2144,7 +3130,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mSystemTime;
if (which == STATS_CURRENT) {
val -= mLoadedSystemTime;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedSystemTime;
}
}
@@ -2160,7 +3146,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mForegroundTime;
if (which == STATS_CURRENT) {
val -= mLoadedForegroundTime;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedForegroundTime;
}
}
@@ -2176,7 +3162,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mStarts;
if (which == STATS_CURRENT) {
val -= mLoadedStarts;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedStarts;
}
}
@@ -2186,14 +3172,22 @@ public final class BatteryStatsImpl extends BatteryStats {
/* Called by ActivityManagerService when CPU times are updated. */
public void addSpeedStepTimes(long[] values) {
for (int i = 0; i < mSpeedBins.length && i < values.length; i++) {
- mSpeedBins[i].addCountAtomic(values[i]);
+ long amt = values[i];
+ if (amt != 0) {
+ SamplingCounter c = mSpeedBins[i];
+ if (c == null) {
+ mSpeedBins[i] = c = new SamplingCounter(mUnpluggables);
+ }
+ c.addCountAtomic(values[i]);
+ }
}
}
@Override
public long getTimeAtCpuSpeedStep(int speedStep, int which) {
if (speedStep < mSpeedBins.length) {
- return mSpeedBins[speedStep].getCountLocked(which);
+ SamplingCounter c = mSpeedBins[speedStep];
+ return c != null ? c.getCountLocked(which) : 0;
} else {
return 0;
}
@@ -2244,10 +3238,14 @@ public final class BatteryStatsImpl extends BatteryStats {
public void plug(long batteryUptime, long batteryRealtime) {
}
+ void detach() {
+ mUnpluggables.remove(this);
+ }
+
void readFromParcelLocked(Parcel in) {
mWakeups = in.readInt();
mLoadedWakeups = in.readInt();
- mLastWakeups = in.readInt();
+ mLastWakeups = 0;
mUnpluggedWakeups = in.readInt();
int numServs = in.readInt();
@@ -2264,7 +3262,6 @@ public final class BatteryStatsImpl extends BatteryStats {
void writeToParcelLocked(Parcel out) {
out.writeInt(mWakeups);
out.writeInt(mLoadedWakeups);
- out.writeInt(mLastWakeups);
out.writeInt(mUnpluggedWakeups);
out.writeInt(mServiceStats.size());
@@ -2290,7 +3287,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mWakeups;
if (which == STATS_CURRENT) {
val -= mLoadedWakeups;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedWakeups;
}
}
@@ -2405,6 +3402,10 @@ public final class BatteryStatsImpl extends BatteryStats {
public void plug(long batteryUptime, long batteryRealtime) {
}
+ void detach() {
+ mUnpluggables.remove(this);
+ }
+
void readFromParcelLocked(Parcel in) {
mStartTime = in.readLong();
mRunningSince = in.readLong();
@@ -2417,9 +3418,9 @@ public final class BatteryStatsImpl extends BatteryStats {
mLoadedStartTime = in.readLong();
mLoadedStarts = in.readInt();
mLoadedLaunches = in.readInt();
- mLastStartTime = in.readLong();
- mLastStarts = in.readInt();
- mLastLaunches = in.readInt();
+ mLastStartTime = 0;
+ mLastStarts = 0;
+ mLastLaunches = 0;
mUnpluggedStartTime = in.readLong();
mUnpluggedStarts = in.readInt();
mUnpluggedLaunches = in.readInt();
@@ -2437,9 +3438,6 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mLoadedStartTime);
out.writeInt(mLoadedStarts);
out.writeInt(mLoadedLaunches);
- out.writeLong(mLastStartTime);
- out.writeInt(mLastStarts);
- out.writeInt(mLastLaunches);
out.writeLong(mUnpluggedStartTime);
out.writeInt(mUnpluggedStarts);
out.writeInt(mUnpluggedLaunches);
@@ -2509,7 +3507,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mLaunches;
if (which == STATS_CURRENT) {
val -= mLoadedLaunches;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedLaunches;
}
}
@@ -2526,7 +3524,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = getStartTimeToNowLocked(now);
if (which == STATS_CURRENT) {
val -= mLoadedStartTime;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedStartTime;
}
}
@@ -2543,7 +3541,7 @@ public final class BatteryStatsImpl extends BatteryStats {
val = mStarts;
if (which == STATS_CURRENT) {
val -= mLoadedStarts;
- } else if (which == STATS_UNPLUGGED) {
+ } else if (which == STATS_SINCE_UNPLUGGED) {
val -= mUnpluggedStarts;
}
}
@@ -2579,6 +3577,19 @@ public final class BatteryStatsImpl extends BatteryStats {
return ps;
}
+ public SparseArray<? extends Pid> getPidStats() {
+ return mPids;
+ }
+
+ public Pid getPidStatsLocked(int pid) {
+ Pid p = mPids.get(pid);
+ if (p == null) {
+ p = new Pid();
+ mPids.put(pid, p);
+ }
+ return p;
+ }
+
/**
* Retrieve the statistics object for a particular service, creating
* if needed.
@@ -2625,21 +3636,24 @@ public final class BatteryStatsImpl extends BatteryStats {
case WAKE_TYPE_PARTIAL:
t = wl.mTimerPartial;
if (t == null) {
- t = new StopwatchTimer(WAKE_TYPE_PARTIAL, mPartialTimers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL,
+ mPartialTimers, mUnpluggables);
wl.mTimerPartial = t;
}
return t;
case WAKE_TYPE_FULL:
t = wl.mTimerFull;
if (t == null) {
- t = new StopwatchTimer(WAKE_TYPE_FULL, mFullTimers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL,
+ mFullTimers, mUnpluggables);
wl.mTimerFull = t;
}
return t;
case WAKE_TYPE_WINDOW:
t = wl.mTimerWindow;
if (t == null) {
- t = new StopwatchTimer(WAKE_TYPE_WINDOW, mWindowTimers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW,
+ mWindowTimers, mUnpluggables);
wl.mTimerWindow = t;
}
return t;
@@ -2666,23 +3680,43 @@ public final class BatteryStatsImpl extends BatteryStats {
timers = new ArrayList<StopwatchTimer>();
mSensorTimers.put(sensor, timers);
}
- t = new StopwatchTimer(BatteryStats.SENSOR, timers, mUnpluggables);
+ t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables);
se.mTimer = t;
return t;
}
- public void noteStartWakeLocked(String name, int type) {
+ public void noteStartWakeLocked(int pid, String name, int type) {
StopwatchTimer t = getWakeTimerLocked(name, type);
if (t != null) {
t.startRunningLocked(BatteryStatsImpl.this);
}
+ if (pid >= 0 && type == WAKE_TYPE_PARTIAL) {
+ Pid p = getPidStatsLocked(pid);
+ if (p.mWakeStart == 0) {
+ p.mWakeStart = SystemClock.elapsedRealtime();
+ }
+ }
}
- public void noteStopWakeLocked(String name, int type) {
+ public void noteStopWakeLocked(int pid, String name, int type) {
StopwatchTimer t = getWakeTimerLocked(name, type);
if (t != null) {
t.stopRunningLocked(BatteryStatsImpl.this);
}
+ if (pid >= 0 && type == WAKE_TYPE_PARTIAL) {
+ Pid p = mPids.get(pid);
+ if (p != null && p.mWakeStart != 0) {
+ p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart;
+ p.mWakeStart = 0;
+ }
+ }
+ }
+
+ public void reportExcessiveWakeLocked(String proc, long overTime, long usedTime) {
+ Proc p = getProcessStatsLocked(proc);
+ if (p != null) {
+ p.addExcessiveWake(overTime, usedTime);
+ }
}
public void noteStartSensor(int sensor) {
@@ -2721,25 +3755,28 @@ public final class BatteryStatsImpl extends BatteryStats {
public BatteryStatsImpl(String filename) {
mFile = new JournaledFile(new File(filename), new File(filename + ".tmp"));
+ mHandler = new MyHandler();
mStartCount++;
- mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables);
}
mInputEventCounter = new Counter(mUnpluggables);
- mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables);
for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables);
+ mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables);
+ mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables);
}
- mWifiOnTimer = new StopwatchTimer(-3, null, mUnpluggables);
- mWifiRunningTimer = new StopwatchTimer(-4, null, mUnpluggables);
- mBluetoothOnTimer = new StopwatchTimer(-5, null, mUnpluggables);
- mAudioOnTimer = new StopwatchTimer(-6, null, mUnpluggables);
+ mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables);
+ mWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables);
+ mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables);
+ mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables);
+ mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables);
mOnBattery = mOnBatteryInternal = false;
+ initTimes();
mTrackBatteryPastUptime = 0;
mTrackBatteryPastRealtime = 0;
mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
@@ -2747,14 +3784,22 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
mDischargeStartLevel = 0;
+ mDischargeUnplugLevel = 0;
mDischargeCurrentLevel = 0;
+ mLowDischargeAmountSinceCharge = 0;
+ mHighDischargeAmountSinceCharge = 0;
}
public BatteryStatsImpl(Parcel p) {
mFile = null;
+ mHandler = null;
readFromParcel(p);
}
+ public void setCallback(BatteryCallback cb) {
+ mCallback = cb;
+ }
+
public void setNumSpeedSteps(int steps) {
if (sNumSpeedSteps == 0) sNumSpeedSteps = steps;
}
@@ -2766,6 +3811,16 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public HistoryItem getHistory() {
+ return mHistory;
+ }
+
+ @Override
+ public long getHistoryBaseTime() {
+ return mHistoryBaseTime;
+ }
+
+ @Override
public int getStartCount() {
return mStartCount;
}
@@ -2774,39 +3829,188 @@ public final class BatteryStatsImpl extends BatteryStats {
return mOnBattery;
}
- public void setOnBattery(boolean onBattery, int level) {
+ public boolean isScreenOn() {
+ return mScreenOn;
+ }
+
+ void initTimes() {
+ mBatteryRealtime = mTrackBatteryPastUptime = 0;
+ mBatteryUptime = mTrackBatteryPastRealtime = 0;
+ mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000;
+ mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000;
+ mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart);
+ mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart);
+ }
+
+ public void resetAllStatsLocked() {
+ mStartCount = 0;
+ initTimes();
+ mScreenOnTimer.reset(this, false);
+ for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
+ mScreenBrightnessTimer[i].reset(this, false);
+ }
+ mInputEventCounter.reset(false);
+ mPhoneOnTimer.reset(this, false);
+ mAudioOnTimer.reset(this, false);
+ mVideoOnTimer.reset(this, false);
+ for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
+ mPhoneSignalStrengthsTimer[i].reset(this, false);
+ }
+ mPhoneSignalScanningTimer.reset(this, false);
+ for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
+ mPhoneDataConnectionsTimer[i].reset(this, false);
+ }
+ mWifiOnTimer.reset(this, false);
+ mWifiRunningTimer.reset(this, false);
+ mBluetoothOnTimer.reset(this, false);
+
+ for (int i=0; i<mUidStats.size(); i++) {
+ if (mUidStats.valueAt(i).reset()) {
+ mUidStats.remove(mUidStats.keyAt(i));
+ i--;
+ }
+ }
+
+ if (mKernelWakelockStats.size() > 0) {
+ for (SamplingTimer timer : mKernelWakelockStats.values()) {
+ mUnpluggables.remove(timer);
+ }
+ mKernelWakelockStats.clear();
+ }
+
+ clearHistoryLocked();
+ }
+
+ void setOnBattery(boolean onBattery, int oldStatus, int level) {
synchronized(this) {
- updateKernelWakelocksLocked();
- if (mOnBattery != onBattery) {
- mOnBattery = mOnBatteryInternal = onBattery;
-
- long uptime = SystemClock.uptimeMillis() * 1000;
- long mSecRealtime = SystemClock.elapsedRealtime();
- long realtime = mSecRealtime * 1000;
- if (onBattery) {
- mTrackBatteryUptimeStart = uptime;
- mTrackBatteryRealtimeStart = realtime;
- mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime);
- mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime);
- mDischargeCurrentLevel = mDischargeStartLevel = level;
- doUnplug(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
- } else {
- mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
- mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
- mDischargeCurrentLevel = level;
- doPlug(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
- }
- if ((mLastWriteTime + (60 * 1000)) < mSecRealtime) {
- if (mFile != null) {
- writeLocked();
- }
+ boolean doWrite = false;
+ Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
+ m.arg1 = onBattery ? 1 : 0;
+ mHandler.sendMessage(m);
+ mOnBattery = mOnBatteryInternal = onBattery;
+
+ long uptime = SystemClock.uptimeMillis() * 1000;
+ long mSecRealtime = SystemClock.elapsedRealtime();
+ long realtime = mSecRealtime * 1000;
+ if (onBattery) {
+ // We will reset our status if we are unplugging after the
+ // battery was last full, or the level is at 100, or
+ // we have gone through a significant charge (from a very low
+ // level to a now very high level).
+ if (oldStatus == BatteryManager.BATTERY_STATUS_FULL
+ || level >= 100
+ || (mDischargeCurrentLevel < 20 && level > 90)) {
+ doWrite = true;
+ resetAllStatsLocked();
+ mDischargeStartLevel = level;
+ mLowDischargeAmountSinceCharge = 0;
+ mHighDischargeAmountSinceCharge = 0;
+ }
+ updateKernelWakelocksLocked();
+ mHistoryCur.batteryLevel = (byte)level;
+ mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(mSecRealtime);
+ mTrackBatteryUptimeStart = uptime;
+ mTrackBatteryRealtimeStart = realtime;
+ mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime);
+ mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime);
+ mDischargeCurrentLevel = mDischargeUnplugLevel = level;
+ doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+ } else {
+ updateKernelWakelocksLocked();
+ mHistoryCur.batteryLevel = (byte)level;
+ mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
+ + Integer.toHexString(mHistoryCur.states));
+ addHistoryRecordLocked(mSecRealtime);
+ mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart;
+ mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart;
+ mDischargeCurrentLevel = level;
+ if (level < mDischargeUnplugLevel) {
+ mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
+ mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
+ }
+ doPlugLocked(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
+ }
+ if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
+ if (mFile != null) {
+ writeLocked();
}
}
}
}
- public void recordCurrentLevel(int level) {
- mDischargeCurrentLevel = level;
+ // This should probably be exposed in the API, though it's not critical
+ private static final int BATTERY_PLUGGED_NONE = 0;
+
+ public void setBatteryState(int status, int health, int plugType, int level,
+ int temp, int volt) {
+ boolean onBattery = plugType == BATTERY_PLUGGED_NONE;
+ int oldStatus = mHistoryCur.batteryStatus;
+ if (!mHaveBatteryLevel) {
+ mHaveBatteryLevel = true;
+ // We start out assuming that the device is plugged in (not
+ // on battery). If our first report is now that we are indeed
+ // plugged in, then twiddle our state to correctly reflect that
+ // since we won't be going through the full setOnBattery().
+ if (onBattery == mOnBattery) {
+ if (onBattery) {
+ mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ } else {
+ mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ }
+ }
+ oldStatus = status;
+ }
+ if (onBattery) {
+ mDischargeCurrentLevel = level;
+ mRecordingHistory = true;
+ }
+ if (onBattery != mOnBattery) {
+ mHistoryCur.batteryLevel = (byte)level;
+ mHistoryCur.batteryStatus = (byte)status;
+ mHistoryCur.batteryHealth = (byte)health;
+ mHistoryCur.batteryPlugType = (byte)plugType;
+ mHistoryCur.batteryTemperature = (char)temp;
+ mHistoryCur.batteryVoltage = (char)volt;
+ setOnBattery(onBattery, oldStatus, level);
+ } else {
+ boolean changed = false;
+ if (mHistoryCur.batteryLevel != level) {
+ mHistoryCur.batteryLevel = (byte)level;
+ changed = true;
+ }
+ if (mHistoryCur.batteryStatus != status) {
+ mHistoryCur.batteryStatus = (byte)status;
+ changed = true;
+ }
+ if (mHistoryCur.batteryHealth != health) {
+ mHistoryCur.batteryHealth = (byte)health;
+ changed = true;
+ }
+ if (mHistoryCur.batteryPlugType != plugType) {
+ mHistoryCur.batteryPlugType = (byte)plugType;
+ changed = true;
+ }
+ if (mHistoryCur.batteryTemperature != temp) {
+ mHistoryCur.batteryTemperature = (char)temp;
+ changed = true;
+ }
+ if (mHistoryCur.batteryVoltage != volt) {
+ mHistoryCur.batteryVoltage = (char)volt;
+ changed = true;
+ }
+ if (changed) {
+ addHistoryRecordLocked(SystemClock.elapsedRealtime());
+ }
+ }
+ if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) {
+ // We don't record history while we are plugged in and fully charged.
+ // The next time we are unplugged, history will be cleared.
+ mRecordingHistory = false;
+ }
}
public void updateKernelWakelocksLocked() {
@@ -2855,10 +4059,10 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public long computeUptime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL: return mUptime + (curTime-mUptimeStart);
+ case STATS_SINCE_CHARGED: return mUptime + (curTime-mUptimeStart);
case STATS_LAST: return mLastUptime;
case STATS_CURRENT: return (curTime-mUptimeStart);
- case STATS_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
+ case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart);
}
return 0;
}
@@ -2866,10 +4070,10 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public long computeRealtime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL: return mRealtime + (curTime-mRealtimeStart);
+ case STATS_SINCE_CHARGED: return mRealtime + (curTime-mRealtimeStart);
case STATS_LAST: return mLastRealtime;
case STATS_CURRENT: return (curTime-mRealtimeStart);
- case STATS_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
+ case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart);
}
return 0;
}
@@ -2877,13 +4081,13 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public long computeBatteryUptime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL:
+ case STATS_SINCE_CHARGED:
return mBatteryUptime + getBatteryUptime(curTime);
case STATS_LAST:
return mBatteryLastUptime;
case STATS_CURRENT:
return getBatteryUptime(curTime);
- case STATS_UNPLUGGED:
+ case STATS_SINCE_UNPLUGGED:
return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime;
}
return 0;
@@ -2892,13 +4096,13 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override
public long computeBatteryRealtime(long curTime, int which) {
switch (which) {
- case STATS_TOTAL:
+ case STATS_SINCE_CHARGED:
return mBatteryRealtime + getBatteryRealtimeLocked(curTime);
case STATS_LAST:
return mBatteryLastRealtime;
case STATS_CURRENT:
return getBatteryRealtimeLocked(curTime);
- case STATS_UNPLUGGED:
+ case STATS_SINCE_UNPLUGGED:
return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime;
}
return 0;
@@ -2938,14 +4142,14 @@ public final class BatteryStatsImpl extends BatteryStats {
if (which == STATS_LAST) {
return dataBytes[STATS_LAST];
} else {
- if (which == STATS_UNPLUGGED) {
- if (dataBytes[STATS_UNPLUGGED] < 0) {
+ if (which == STATS_SINCE_UNPLUGGED) {
+ if (dataBytes[STATS_SINCE_UNPLUGGED] < 0) {
return dataBytes[STATS_LAST];
} else {
- return current - dataBytes[STATS_UNPLUGGED];
+ return current - dataBytes[STATS_SINCE_UNPLUGGED];
}
- } else if (which == STATS_TOTAL) {
- return (current - dataBytes[STATS_CURRENT]) + dataBytes[STATS_TOTAL];
+ } else if (which == STATS_SINCE_CHARGED) {
+ return (current - dataBytes[STATS_CURRENT]) + dataBytes[STATS_SINCE_CHARGED];
}
return current - dataBytes[STATS_CURRENT];
}
@@ -2979,7 +4183,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public int getDischargeStartLevelLocked() {
- return mDischargeStartLevel;
+ return mDischargeUnplugLevel;
}
@Override
@@ -2994,6 +4198,20 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public int getLowDischargeAmountSinceCharge() {
+ synchronized(this) {
+ return mLowDischargeAmountSinceCharge;
+ }
+ }
+
+ @Override
+ public int getHighDischargeAmountSinceCharge() {
+ synchronized(this) {
+ return mHighDischargeAmountSinceCharge;
+ }
+ }
+
+ @Override
public int getCpuSpeedSteps() {
return sNumSpeedSteps;
}
@@ -3062,17 +4280,21 @@ public final class BatteryStatsImpl extends BatteryStats {
return u.getServiceStatsLocked(pkg, name);
}
- private static JournaledFile makeJournaledFile() {
- final String base = "/data/system/device_policies.xml";
- return new JournaledFile(new File(base), new File(base + ".tmp"));
+ public void shutdownLocked() {
+ writeLocked();
+ mShuttingDown = true;
}
-
+
public void writeLocked() {
if (mFile == null) {
Slog.w("BatteryStats", "writeLocked: no file associated with this instance");
return;
}
+ if (mShuttingDown) {
+ return;
+ }
+
try {
FileOutputStream stream = new FileOutputStream(mFile.chooseForWrite());
Parcel out = Parcel.obtain();
@@ -3140,12 +4362,47 @@ public final class BatteryStatsImpl extends BatteryStats {
} catch(java.io.IOException e) {
Slog.e("BatteryStats", "Error reading battery statistics", e);
}
+
+ addHistoryRecordLocked(SystemClock.elapsedRealtime(), HistoryItem.CMD_START);
}
public int describeContents() {
return 0;
}
+ void readHistory(Parcel in) {
+ mHistory = mHistoryEnd = mHistoryCache = null;
+ mHistoryBaseTime = 0;
+ long time;
+ while ((time=in.readLong()) >= 0) {
+ HistoryItem rec = new HistoryItem(time, in);
+ addHistoryRecordLocked(rec);
+ if (rec.time > mHistoryBaseTime) {
+ mHistoryBaseTime = rec.time;
+ }
+ }
+
+ long oldnow = SystemClock.elapsedRealtime() - (5*60*100);
+ if (oldnow > 0) {
+ // If the system process has restarted, but not the entire
+ // system, then the mHistoryBaseTime already accounts for
+ // much of the elapsed time. We thus want to adjust it back,
+ // to avoid large gaps in the data. We determine we are
+ // in this case by arbitrarily saying it is so if at this
+ // point in boot the elapsed time is already more than 5 seconds.
+ mHistoryBaseTime -= oldnow;
+ }
+ }
+
+ void writeHistory(Parcel out) {
+ HistoryItem rec = mHistory;
+ while (rec != null) {
+ if (rec.time >= 0) rec.writeToParcel(out, 0);
+ rec = rec.next;
+ }
+ out.writeLong(-1);
+ }
+
private void readSummaryFromParcel(Parcel in) {
final int version = in.readInt();
if (version != VERSION) {
@@ -3154,17 +4411,17 @@ public final class BatteryStatsImpl extends BatteryStats {
return;
}
+ readHistory(in);
+
mStartCount = in.readInt();
mBatteryUptime = in.readLong();
- mBatteryLastUptime = in.readLong();
mBatteryRealtime = in.readLong();
- mBatteryLastRealtime = in.readLong();
mUptime = in.readLong();
- mLastUptime = in.readLong();
mRealtime = in.readLong();
- mLastRealtime = in.readLong();
- mDischargeStartLevel = in.readInt();
+ mDischargeUnplugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
+ mLowDischargeAmountSinceCharge = in.readInt();
+ mHighDischargeAmountSinceCharge = in.readInt();
mStartCount++;
@@ -3215,17 +4472,29 @@ public final class BatteryStatsImpl extends BatteryStats {
mUidStats.put(uid, u);
u.mWifiTurnedOn = false;
- u.mWifiTurnedOnTimer.readSummaryFromParcelLocked(in);
+ if (in.readInt() != 0) {
+ u.mWifiTurnedOnTimer.readSummaryFromParcelLocked(in);
+ }
u.mFullWifiLockOut = false;
- u.mFullWifiLockTimer.readSummaryFromParcelLocked(in);
- u.mAudioTurnedOn = false;
- u.mAudioTurnedOnTimer.readSummaryFromParcelLocked(in);
- u.mVideoTurnedOn = false;
- u.mVideoTurnedOnTimer.readSummaryFromParcelLocked(in);
+ if (in.readInt() != 0) {
+ u.mFullWifiLockTimer.readSummaryFromParcelLocked(in);
+ }
u.mScanWifiLockOut = false;
- u.mScanWifiLockTimer.readSummaryFromParcelLocked(in);
+ if (in.readInt() != 0) {
+ u.mScanWifiLockTimer.readSummaryFromParcelLocked(in);
+ }
u.mWifiMulticastEnabled = false;
- u.mWifiMulticastTimer.readSummaryFromParcelLocked(in);
+ if (in.readInt() != 0) {
+ u.mWifiMulticastTimer.readSummaryFromParcelLocked(in);
+ }
+ u.mAudioTurnedOn = false;
+ if (in.readInt() != 0) {
+ u.mAudioTurnedOnTimer.readSummaryFromParcelLocked(in);
+ }
+ u.mVideoTurnedOn = false;
+ if (in.readInt() != 0) {
+ u.mVideoTurnedOnTimer.readSummaryFromParcelLocked(in);
+ }
if (in.readInt() != 0) {
if (u.mUserActivityCounters == null) {
@@ -3276,11 +4545,9 @@ public final class BatteryStatsImpl extends BatteryStats {
String procName = in.readString();
Uid.Proc p = u.getProcessStatsLocked(procName);
p.mUserTime = p.mLoadedUserTime = in.readLong();
- p.mLastUserTime = in.readLong();
p.mSystemTime = p.mLoadedSystemTime = in.readLong();
- p.mLastSystemTime = in.readLong();
p.mStarts = p.mLoadedStarts = in.readInt();
- p.mLastStarts = in.readInt();
+ p.readExcessiveWakeFromParcelLocked(in);
}
NP = in.readInt();
@@ -3292,17 +4559,13 @@ public final class BatteryStatsImpl extends BatteryStats {
String pkgName = in.readString();
Uid.Pkg p = u.getPackageStatsLocked(pkgName);
p.mWakeups = p.mLoadedWakeups = in.readInt();
- p.mLastWakeups = in.readInt();
final int NS = in.readInt();
for (int is = 0; is < NS; is++) {
String servName = in.readString();
Uid.Pkg.Serv s = u.getServiceStatsLocked(pkgName, servName);
s.mStartTime = s.mLoadedStartTime = in.readLong();
- s.mLastStartTime = in.readLong();
s.mStarts = s.mLoadedStarts = in.readInt();
- s.mLastStarts = in.readInt();
s.mLaunches = s.mLoadedLaunches = in.readInt();
- s.mLastLaunches = in.readInt();
}
}
@@ -3325,18 +4588,17 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(VERSION);
+ writeHistory(out);
+
out.writeInt(mStartCount);
- out.writeLong(computeBatteryUptime(NOW_SYS, STATS_TOTAL));
- out.writeLong(computeBatteryUptime(NOW_SYS, STATS_CURRENT));
- out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_TOTAL));
- out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_CURRENT));
- out.writeLong(computeUptime(NOW_SYS, STATS_TOTAL));
- out.writeLong(computeUptime(NOW_SYS, STATS_CURRENT));
- out.writeLong(computeRealtime(NOWREAL_SYS, STATS_TOTAL));
- out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT));
- out.writeInt(mDischargeStartLevel);
+ out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED));
+ out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
+ out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED));
+ out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED));
+ out.writeInt(mDischargeUnplugLevel);
out.writeInt(mDischargeCurrentLevel);
-
+ out.writeInt(mLowDischargeAmountSinceCharge);
+ out.writeInt(mHighDischargeAmountSinceCharge);
mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
@@ -3374,12 +4636,42 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mUidStats.keyAt(iu));
Uid u = mUidStats.valueAt(iu);
- u.mWifiTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- u.mScanWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL);
- u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ if (u.mWifiTurnedOnTimer != null) {
+ out.writeInt(1);
+ u.mWifiTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mFullWifiLockTimer != null) {
+ out.writeInt(1);
+ u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mScanWifiLockTimer != null) {
+ out.writeInt(1);
+ u.mScanWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mWifiMulticastTimer != null) {
+ out.writeInt(1);
+ u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mAudioTurnedOnTimer != null) {
+ out.writeInt(1);
+ u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
+ if (u.mVideoTurnedOnTimer != null) {
+ out.writeInt(1);
+ u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
if (u.mUserActivityCounters == null) {
out.writeInt(0);
@@ -3442,11 +4734,9 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeString(ent.getKey());
Uid.Proc ps = ent.getValue();
out.writeLong(ps.mUserTime);
- out.writeLong(ps.mUserTime - ps.mLoadedUserTime);
out.writeLong(ps.mSystemTime);
- out.writeLong(ps.mSystemTime - ps.mLoadedSystemTime);
out.writeInt(ps.mStarts);
- out.writeInt(ps.mStarts - ps.mLoadedStarts);
+ ps.writeExcessiveWakeToParcelLocked(out);
}
}
@@ -3458,7 +4748,6 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeString(ent.getKey());
Uid.Pkg ps = ent.getValue();
out.writeInt(ps.mWakeups);
- out.writeInt(ps.mWakeups - ps.mLoadedWakeups);
final int NS = ps.mServiceStats.size();
out.writeInt(NS);
if (NS > 0) {
@@ -3468,18 +4757,15 @@ public final class BatteryStatsImpl extends BatteryStats {
BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue();
long time = ss.getStartTimeToNowLocked(NOW);
out.writeLong(time);
- out.writeLong(time - ss.mLoadedStartTime);
out.writeInt(ss.mStarts);
- out.writeInt(ss.mStarts - ss.mLoadedStarts);
out.writeInt(ss.mLaunches);
- out.writeInt(ss.mLaunches - ss.mLoadedLaunches);
}
}
}
}
- out.writeLong(u.getTcpBytesReceived(STATS_TOTAL));
- out.writeLong(u.getTcpBytesSent(STATS_TOTAL));
+ out.writeLong(u.getTcpBytesReceived(STATS_SINCE_CHARGED));
+ out.writeLong(u.getTcpBytesSent(STATS_SINCE_CHARGED));
}
}
@@ -3493,38 +4779,43 @@ public final class BatteryStatsImpl extends BatteryStats {
throw new ParcelFormatException("Bad magic number");
}
+ readHistory(in);
+
mStartCount = in.readInt();
mBatteryUptime = in.readLong();
- mBatteryLastUptime = in.readLong();
+ mBatteryLastUptime = 0;
mBatteryRealtime = in.readLong();
- mBatteryLastRealtime = in.readLong();
+ mBatteryLastRealtime = 0;
mScreenOn = false;
- mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables, in);
+ mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
- mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables, in);
+ mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i,
+ null, mUnpluggables, in);
}
mInputEventCounter = new Counter(mUnpluggables, in);
mPhoneOn = false;
- mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) {
- mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables, in);
+ mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i,
+ null, mUnpluggables, in);
}
- mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables, in);
+ mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in);
for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) {
- mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables, in);
+ mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i,
+ null, mUnpluggables, in);
}
mWifiOn = false;
- mWifiOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mWifiRunning = false;
- mWifiRunningTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mBluetoothOn = false;
- mBluetoothOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in);
+ mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in);
mUptime = in.readLong();
mUptimeStart = in.readLong();
- mLastUptime = in.readLong();
+ mLastUptime = 0;
mRealtime = in.readLong();
mRealtimeStart = in.readLong();
- mLastRealtime = in.readLong();
+ mLastRealtime = 0;
mOnBattery = in.readInt() != 0;
mOnBatteryInternal = false; // we are no longer really running.
mTrackBatteryPastUptime = in.readLong();
@@ -3533,18 +4824,20 @@ public final class BatteryStatsImpl extends BatteryStats {
mTrackBatteryRealtimeStart = in.readLong();
mUnpluggedBatteryUptime = in.readLong();
mUnpluggedBatteryRealtime = in.readLong();
- mDischargeStartLevel = in.readInt();
+ mDischargeUnplugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
+ mLowDischargeAmountSinceCharge = in.readInt();
+ mHighDischargeAmountSinceCharge = in.readInt();
mLastWriteTime = in.readLong();
mMobileDataRx[STATS_LAST] = in.readLong();
- mMobileDataRx[STATS_UNPLUGGED] = -1;
+ mMobileDataRx[STATS_SINCE_UNPLUGGED] = -1;
mMobileDataTx[STATS_LAST] = in.readLong();
- mMobileDataTx[STATS_UNPLUGGED] = -1;
+ mMobileDataTx[STATS_SINCE_UNPLUGGED] = -1;
mTotalDataRx[STATS_LAST] = in.readLong();
- mTotalDataRx[STATS_UNPLUGGED] = -1;
+ mTotalDataRx[STATS_SINCE_UNPLUGGED] = -1;
mTotalDataTx[STATS_LAST] = in.readLong();
- mTotalDataTx[STATS_UNPLUGGED] = -1;
+ mTotalDataTx[STATS_SINCE_UNPLUGGED] = -1;
mRadioDataUptime = in.readLong();
mRadioDataStart = -1;
@@ -3580,22 +4873,27 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void writeToParcel(Parcel out, int flags) {
- writeToParcelLocked(out, flags);
+ writeToParcelLocked(out, true, flags);
+ }
+
+ public void writeToParcelWithoutUids(Parcel out, int flags) {
+ writeToParcelLocked(out, false, flags);
}
@SuppressWarnings("unused")
- void writeToParcelLocked(Parcel out, int flags) {
+ void writeToParcelLocked(Parcel out, boolean inclUids, int flags) {
final long uSecUptime = SystemClock.uptimeMillis() * 1000;
final long uSecRealtime = SystemClock.elapsedRealtime() * 1000;
final long batteryUptime = getBatteryUptimeLocked(uSecUptime);
final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime);
out.writeInt(MAGIC);
+
+ writeHistory(out);
+
out.writeInt(mStartCount);
out.writeLong(mBatteryUptime);
- out.writeLong(mBatteryLastUptime);
out.writeLong(mBatteryRealtime);
- out.writeLong(mBatteryLastRealtime);
mScreenOnTimer.writeToParcel(out, batteryRealtime);
for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
mScreenBrightnessTimer[i].writeToParcel(out, batteryRealtime);
@@ -3614,10 +4912,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothOnTimer.writeToParcel(out, batteryRealtime);
out.writeLong(mUptime);
out.writeLong(mUptimeStart);
- out.writeLong(mLastUptime);
out.writeLong(mRealtime);
out.writeLong(mRealtimeStart);
- out.writeLong(mLastRealtime);
out.writeInt(mOnBattery ? 1 : 0);
out.writeLong(batteryUptime);
out.writeLong(mTrackBatteryUptimeStart);
@@ -3625,41 +4921,51 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mTrackBatteryRealtimeStart);
out.writeLong(mUnpluggedBatteryUptime);
out.writeLong(mUnpluggedBatteryRealtime);
- out.writeInt(mDischargeStartLevel);
+ out.writeInt(mDischargeUnplugLevel);
out.writeInt(mDischargeCurrentLevel);
+ out.writeInt(mLowDischargeAmountSinceCharge);
+ out.writeInt(mHighDischargeAmountSinceCharge);
out.writeLong(mLastWriteTime);
- out.writeLong(getMobileTcpBytesReceived(STATS_UNPLUGGED));
- out.writeLong(getMobileTcpBytesSent(STATS_UNPLUGGED));
- out.writeLong(getTotalTcpBytesReceived(STATS_UNPLUGGED));
- out.writeLong(getTotalTcpBytesSent(STATS_UNPLUGGED));
+ out.writeLong(getMobileTcpBytesReceived(STATS_SINCE_UNPLUGGED));
+ out.writeLong(getMobileTcpBytesSent(STATS_SINCE_UNPLUGGED));
+ out.writeLong(getTotalTcpBytesReceived(STATS_SINCE_UNPLUGGED));
+ out.writeLong(getTotalTcpBytesSent(STATS_SINCE_UNPLUGGED));
// Write radio uptime for data
out.writeLong(getRadioDataUptime());
out.writeInt(getBluetoothPingCount());
- out.writeInt(mKernelWakelockStats.size());
- for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
- SamplingTimer kwlt = ent.getValue();
- if (kwlt != null) {
- out.writeInt(1);
- out.writeString(ent.getKey());
- Timer.writeTimerToParcel(out, kwlt, batteryRealtime);
- } else {
- out.writeInt(0);
+ if (inclUids) {
+ out.writeInt(mKernelWakelockStats.size());
+ for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
+ SamplingTimer kwlt = ent.getValue();
+ if (kwlt != null) {
+ out.writeInt(1);
+ out.writeString(ent.getKey());
+ Timer.writeTimerToParcel(out, kwlt, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
}
+ } else {
+ out.writeInt(0);
}
out.writeInt(sNumSpeedSteps);
- int size = mUidStats.size();
- out.writeInt(size);
- for (int i = 0; i < size; i++) {
- out.writeInt(mUidStats.keyAt(i));
- Uid uid = mUidStats.valueAt(i);
+ if (inclUids) {
+ int size = mUidStats.size();
+ out.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ out.writeInt(mUidStats.keyAt(i));
+ Uid uid = mUidStats.valueAt(i);
- uid.writeToParcelLocked(out, batteryRealtime);
+ uid.writeToParcelLocked(out, batteryRealtime);
+ }
+ } else {
+ out.writeInt(0);
}
}
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
index 2369d25bd472..127ed68dff98 100644
--- a/core/java/com/android/internal/os/PowerProfile.java
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -126,6 +126,11 @@ public class PowerProfile {
public static final String POWER_CPU_SPEEDS = "cpu.speeds";
+ /**
+ * Battery capacity in milliAmpHour (mAh).
+ */
+ public static final String POWER_BATTERY_CAPACITY = "battery.capacity";
+
static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>();
private static final String TAG_DEVICE = "device";
@@ -243,6 +248,19 @@ public class PowerProfile {
}
}
+ /**
+ * Returns the battery capacity, if available, in milli Amp Hours. If not available,
+ * it returns zero.
+ * @return the battery capacity in mAh
+ */
+ public double getBatteryCapacity() {
+ return getAveragePower(POWER_BATTERY_CAPACITY);
+ }
+
+ /**
+ * Returns the number of speeds that the CPU can be run at.
+ * @return
+ */
public int getNumSpeedSteps() {
Object value = sPowerMap.get(POWER_CPU_SPEEDS);
if (value != null && value instanceof Double[]) {
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 599a7fe4b681..59600dcdc455 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -342,6 +342,10 @@ public class RuntimeInit {
mApplicationObject = app;
}
+ public static final IBinder getApplicationObject() {
+ return mApplicationObject;
+ }
+
/**
* Enable debugging features.
*/
diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
index 5f5c7a47e219..a3efbc87317b 100644
--- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java
+++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java
@@ -18,10 +18,12 @@ package com.android.internal.os;
import dalvik.system.SamplingProfiler;
+import java.io.BufferedOutputStream;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.FileNotFoundException;
+import java.io.PrintStream;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -48,6 +50,8 @@ public class SamplingProfilerIntegration {
}
}
+ private static SamplingProfiler INSTANCE;
+
/**
* Is profiling enabled?
*/
@@ -59,8 +63,13 @@ public class SamplingProfilerIntegration {
* Starts the profiler if profiling is enabled.
*/
public static void start() {
- if (!enabled) return;
- SamplingProfiler.getInstance().start(10);
+ if (!enabled) {
+ return;
+ }
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupTheadSet(group);
+ INSTANCE = new SamplingProfiler(4, threadSet);
+ INSTANCE.start(10);
}
/** Whether or not we've created the snapshots dir. */
@@ -73,7 +82,9 @@ public class SamplingProfilerIntegration {
* Writes a snapshot to the SD card if profiling is enabled.
*/
public static void writeSnapshot(final String name) {
- if (!enabled) return;
+ if (!enabled) {
+ return;
+ }
/*
* If we're already writing a snapshot, don't bother enqueing another
@@ -109,18 +120,22 @@ public class SamplingProfilerIntegration {
* Writes the zygote's snapshot to internal storage if profiling is enabled.
*/
public static void writeZygoteSnapshot() {
- if (!enabled) return;
+ if (!enabled) {
+ return;
+ }
String dir = "/data/zygote/snapshots";
new File(dir).mkdirs();
writeSnapshot(dir, "zygote");
+ INSTANCE.shutdown();
+ INSTANCE = null;
}
private static void writeSnapshot(String dir, String name) {
- byte[] snapshot = SamplingProfiler.getInstance().snapshot();
- if (snapshot == null) {
+ if (!enabled) {
return;
}
+ INSTANCE.stop();
/*
* We use the current time as a unique ID. We can't use a counter
@@ -128,39 +143,40 @@ public class SamplingProfilerIntegration {
* we capture two snapshots in rapid succession.
*/
long start = System.currentTimeMillis();
- String path = dir + "/" + name.replace(':', '.') + "-" +
+ String path = dir + "/" + name.replace(':', '.') + "-"
+ System.currentTimeMillis() + ".snapshot";
- try {
- // Try to open the file a few times. The SD card may not be mounted.
- FileOutputStream out;
- int count = 0;
- while (true) {
- try {
- out = new FileOutputStream(path);
- break;
- } catch (FileNotFoundException e) {
- if (++count > 3) {
- Log.e(TAG, "Could not open " + path + ".");
- return;
- }
- // Sleep for a bit and then try again.
- try {
- Thread.sleep(2500);
- } catch (InterruptedException e1) { /* ignore */ }
+ // Try to open the file a few times. The SD card may not be mounted.
+ PrintStream out;
+ int count = 0;
+ while (true) {
+ try {
+ out = new PrintStream(new BufferedOutputStream(new FileOutputStream(path)));
+ break;
+ } catch (FileNotFoundException e) {
+ if (++count > 3) {
+ Log.e(TAG, "Could not open " + path + ".");
+ return;
}
- }
- try {
- out.write(snapshot);
- } finally {
- out.close();
+ // Sleep for a bit and then try again.
+ try {
+ Thread.sleep(2500);
+ } catch (InterruptedException e1) { /* ignore */ }
}
+ }
+
+ try {
+ INSTANCE.writeHprofData(out);
+ } finally {
+ out.close();
+ }
+ if (out.checkError()) {
+ Log.e(TAG, "Error writing snapshot.");
+ } else {
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "Wrote snapshot for " + name
- + " in " + elapsed + "ms.");
- } catch (IOException e) {
- Log.e(TAG, "Error writing snapshot.", e);
+ + " in " + elapsed + "ms.");
}
}
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b677b1e60489..a409ec844248 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -66,10 +66,6 @@ public class ZygoteInit {
/** when preloading, GC after allocating this many bytes */
private static final int PRELOAD_GC_THRESHOLD = 50000;
- /** throw on missing preload, only if this looks like a developer */
- private static final boolean THROW_ON_MISSING_PRELOAD =
- "1".equals(SystemProperties.get("persist.service.adb.enable"));
-
public static final String USAGE_STRING =
" <\"true\"|\"false\" for startSystemServer>";
@@ -287,7 +283,6 @@ public class ZygoteInit {
int count = 0;
String line;
- String missingClasses = null;
while ((line = br.readLine()) != null) {
// Skip comments and blank lines.
line = line.trim();
@@ -311,12 +306,7 @@ public class ZygoteInit {
}
count++;
} catch (ClassNotFoundException e) {
- Log.e(TAG, "Class not found for preloading: " + line);
- if (missingClasses == null) {
- missingClasses = line;
- } else {
- missingClasses += " " + line;
- }
+ Log.w(TAG, "Class not found for preloading: " + line);
} catch (Throwable t) {
Log.e(TAG, "Error preloading " + line + ".", t);
if (t instanceof Error) {
@@ -329,13 +319,6 @@ public class ZygoteInit {
}
}
- if (THROW_ON_MISSING_PRELOAD &&
- missingClasses != null) {
- throw new IllegalStateException(
- "Missing class(es) for preloading, update preloaded-classes ["
- + missingClasses + "]");
- }
-
Log.i(TAG, "...preloaded " + count + " classes in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
} catch (IOException e) {
@@ -592,12 +575,8 @@ public class ZygoteInit {
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
SystemClock.uptimeMillis());
- if (SamplingProfilerIntegration.isEnabled()) {
- SamplingProfiler sp = SamplingProfiler.getInstance();
- sp.pause();
- SamplingProfilerIntegration.writeZygoteSnapshot();
- sp.shutDown();
- }
+ // Finish profiling the zygote initialization.
+ SamplingProfilerIntegration.writeZygoteSnapshot();
// Do an initial gc to clean up after startup
gc();
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
new file mode 100644
index 000000000000..4501bd769496
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarNotification;
+
+/** @hide */
+oneway interface IStatusBar
+{
+ void setIcon(int index, in StatusBarIcon icon);
+ void removeIcon(int index);
+ void addNotification(IBinder key, in StatusBarNotification notification);
+ void updateNotification(IBinder key, in StatusBarNotification notification);
+ void removeNotification(IBinder key);
+ void disable(int state);
+ void animateExpand();
+ void animateCollapse();
+}
+
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
new file mode 100644
index 000000000000..852630dfa443
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import com.android.internal.statusbar.IStatusBar;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarIconList;
+import com.android.internal.statusbar.StatusBarNotification;
+
+/** @hide */
+interface IStatusBarService
+{
+ void expand();
+ void collapse();
+ void disable(int what, IBinder token, String pkg);
+ void setIcon(String slot, String iconPackage, int iconId, int iconLevel);
+ void setIconVisibility(String slot, boolean visible);
+ void removeIcon(String slot);
+
+ // ---- Methods below are for use by the status bar policy services ----
+ // You need the STATUS_BAR_SERVICE permission
+ void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList,
+ out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications);
+ void onPanelRevealed();
+ void onNotificationClick(String pkg, String tag, int id);
+ void onNotificationError(String pkg, String tag, int id,
+ int uid, int initialPid, String message);
+ void onClearAllNotifications();
+}
diff --git a/core/java/android/app/IStatusBar.aidl b/core/java/com/android/internal/statusbar/StatusBarIcon.aidl
index c64fa50f1dcd..311a0770ad36 100644
--- a/core/java/android/app/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2007, The Android Open Source Project
+/*
+ * Copyright (c) 2010, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,17 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package android.app;
-/** @hide */
-interface IStatusBar
-{
- void activate();
- void deactivate();
- void toggle();
- void disable(int what, IBinder token, String pkg);
- IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel);
- void updateIcon(IBinder key, String slot, String iconPackage, int iconId, int iconLevel);
- void removeIcon(IBinder key);
-}
+package com.android.internal.statusbar;
+
+parcelable StatusBarIcon;
+
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
new file mode 100644
index 000000000000..ae2cac2a68f7
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class StatusBarIcon implements Parcelable {
+ public String iconPackage;
+ public int iconId;
+ public int iconLevel;
+ public boolean visible = true;
+ public int number;
+
+ private StatusBarIcon() {
+ }
+
+ public StatusBarIcon(String iconPackage, int iconId, int iconLevel) {
+ this.iconPackage = iconPackage;
+ this.iconId = iconId;
+ this.iconLevel = iconLevel;
+ }
+
+ public StatusBarIcon(String iconPackage, int iconId, int iconLevel, int number) {
+ this.iconPackage = iconPackage;
+ this.iconId = iconId;
+ this.iconLevel = iconLevel;
+ this.number = number;
+ }
+
+ public String toString() {
+ return "StatusBarIcon(pkg=" + this.iconPackage + " id=0x" + Integer.toHexString(this.iconId)
+ + " level=" + this.iconLevel + " visible=" + visible
+ + " num=" + this.number + " )";
+ }
+
+ public StatusBarIcon clone() {
+ StatusBarIcon that = new StatusBarIcon(this.iconPackage, this.iconId, this.iconLevel);
+ that.visible = this.visible;
+ that.number = this.number;
+ return that;
+ }
+
+ /**
+ * Unflatten the StatusBarIcon from a parcel.
+ */
+ public StatusBarIcon(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void readFromParcel(Parcel in) {
+ this.iconPackage = in.readString();
+ this.iconId = in.readInt();
+ this.iconLevel = in.readInt();
+ this.visible = in.readInt() != 0;
+ this.number = in.readInt();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(this.iconPackage);
+ out.writeInt(this.iconId);
+ out.writeInt(this.iconLevel);
+ out.writeInt(this.visible ? 1 : 0);
+ out.writeInt(this.number);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable.Creator that instantiates StatusBarIcon objects
+ */
+ public static final Parcelable.Creator<StatusBarIcon> CREATOR
+ = new Parcelable.Creator<StatusBarIcon>()
+ {
+ public StatusBarIcon createFromParcel(Parcel parcel)
+ {
+ return new StatusBarIcon(parcel);
+ }
+
+ public StatusBarIcon[] newArray(int size)
+ {
+ return new StatusBarIcon[size];
+ }
+ };
+}
+
diff --git a/core/java/com/android/internal/statusbar/StatusBarIconList.aidl b/core/java/com/android/internal/statusbar/StatusBarIconList.aidl
new file mode 100644
index 000000000000..c74512050092
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/StatusBarIconList.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+parcelable StatusBarIconList;
+
diff --git a/core/java/com/android/internal/statusbar/StatusBarIconList.java b/core/java/com/android/internal/statusbar/StatusBarIconList.java
new file mode 100644
index 000000000000..478d245eb8cd
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/StatusBarIconList.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.PrintWriter;
+
+public class StatusBarIconList implements Parcelable {
+ private String[] mSlots;
+ private StatusBarIcon[] mIcons;
+
+ public StatusBarIconList() {
+ }
+
+ public StatusBarIconList(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void readFromParcel(Parcel in) {
+ this.mSlots = in.readStringArray();
+ final int N = in.readInt();
+ if (N < 0) {
+ mIcons = null;
+ } else {
+ mIcons = new StatusBarIcon[N];
+ for (int i=0; i<N; i++) {
+ if (in.readInt() != 0) {
+ mIcons[i] = new StatusBarIcon(in);
+ }
+ }
+ }
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStringArray(mSlots);
+ if (mIcons == null) {
+ out.writeInt(-1);
+ } else {
+ final int N = mIcons.length;
+ out.writeInt(N);
+ for (int i=0; i<N; i++) {
+ StatusBarIcon ic = mIcons[i];
+ if (ic == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(1);
+ ic.writeToParcel(out, flags);
+ }
+ }
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Parcelable.Creator that instantiates StatusBarIconList objects
+ */
+ public static final Parcelable.Creator<StatusBarIconList> CREATOR
+ = new Parcelable.Creator<StatusBarIconList>()
+ {
+ public StatusBarIconList createFromParcel(Parcel parcel)
+ {
+ return new StatusBarIconList(parcel);
+ }
+
+ public StatusBarIconList[] newArray(int size)
+ {
+ return new StatusBarIconList[size];
+ }
+ };
+
+ public void defineSlots(String[] slots) {
+ final int N = slots.length;
+ String[] s = mSlots = new String[N];
+ for (int i=0; i<N; i++) {
+ s[i] = slots[i];
+ }
+ mIcons = new StatusBarIcon[N];
+ }
+
+ public int getSlotIndex(String slot) {
+ final int N = mSlots.length;
+ for (int i=0; i<N; i++) {
+ if (slot.equals(mSlots[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int size() {
+ return mSlots.length;
+ }
+
+ public void setIcon(int index, StatusBarIcon icon) {
+ mIcons[index] = icon.clone();
+ }
+
+ public void removeIcon(int index) {
+ mIcons[index] = null;
+ }
+
+ public String getSlot(int index) {
+ return mSlots[index];
+ }
+
+ public StatusBarIcon getIcon(int index) {
+ return mIcons[index];
+ }
+
+ public int getViewIndex(int index) {
+ int count = 0;
+ for (int i=0; i<index; i++) {
+ if (mIcons[i] != null) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public void copyFrom(StatusBarIconList that) {
+ if (that.mSlots == null) {
+ this.mSlots = null;
+ this.mIcons = null;
+ } else {
+ final int N = that.mSlots.length;
+ this.mSlots = new String[N];
+ this.mIcons = new StatusBarIcon[N];
+ for (int i=0; i<N; i++) {
+ this.mSlots[i] = that.mSlots[i];
+ this.mIcons[i] = that.mIcons[i] != null ? that.mIcons[i].clone() : null;
+ }
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ final int N = mSlots.length;
+ pw.println("Icon list:");
+ for (int i=0; i<N; i++) {
+ pw.printf(" %2d: (%s) %s\n", i, mSlots[i], mIcons[i]);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl b/core/java/com/android/internal/statusbar/StatusBarNotification.aidl
new file mode 100644
index 000000000000..bd9e89ce8a42
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/StatusBarNotification.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+parcelable StatusBarNotification;
+
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java
new file mode 100644
index 000000000000..aa340fba4ef9
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of 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.statusbar;
+
+import android.app.Notification;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.widget.RemoteViews;
+
+
+/*
+boolean clearable = !n.ongoingEvent && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
+
+
+// TODO: make this restriction do something smarter like never fill
+// more than two screens. "Why would anyone need more than 80 characters." :-/
+final int maxTickerLen = 80;
+if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
+ truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
+}
+*/
+
+public class StatusBarNotification implements Parcelable {
+ public String pkg;
+ public int id;
+ public String tag;
+ public int uid;
+ public int initialPid;
+ public Notification notification;
+
+ public StatusBarNotification() {
+ }
+
+ public StatusBarNotification(String pkg, int id, String tag,
+ int uid, int initialPid, Notification notification) {
+ if (pkg == null) throw new NullPointerException();
+ if (notification == null) throw new NullPointerException();
+
+ this.pkg = pkg;
+ this.id = id;
+ this.tag = tag;
+ this.uid = uid;
+ this.initialPid = initialPid;
+ this.notification = notification;
+ }
+
+ public StatusBarNotification(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void readFromParcel(Parcel in) {
+ this.pkg = in.readString();
+ this.id = in.readInt();
+ if (in.readInt() != 0) {
+ this.tag = in.readString();
+ } else {
+ this.tag = null;
+ }
+ this.uid = in.readInt();
+ this.initialPid = in.readInt();
+ this.notification = new Notification(in);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(this.pkg);
+ out.writeInt(this.id);
+ if (this.tag != null) {
+ out.writeInt(1);
+ out.writeString(this.tag);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeInt(this.uid);
+ out.writeInt(this.initialPid);
+ this.notification.writeToParcel(out, flags);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<StatusBarNotification> CREATOR
+ = new Parcelable.Creator<StatusBarNotification>()
+ {
+ public StatusBarNotification createFromParcel(Parcel parcel)
+ {
+ return new StatusBarNotification(parcel);
+ }
+
+ public StatusBarNotification[] newArray(int size)
+ {
+ return new StatusBarNotification[size];
+ }
+ };
+
+ public StatusBarNotification clone() {
+ return new StatusBarNotification(this.pkg, this.id, this.tag,
+ this.uid, this.initialPid, this.notification.clone());
+ }
+
+ public String toString() {
+ return "StatusBarNotification(package=" + pkg + " id=" + id + " tag=" + tag
+ + " notification=" + notification + ")";
+ }
+
+ public boolean isOngoing() {
+ return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0;
+ }
+
+}
+
+
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl b/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl
new file mode 100644
index 000000000000..10abeee94e15
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+parcelable StatusBarNotificationList;
+
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java
index 9911f486d5ac..c599d68e7a3c 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java
@@ -51,7 +51,7 @@ import java.util.HashMap;
mS2 mS1 ----> initial state
</code>
* After the state machine is created and started, messages are sent to a state
- * machine using <code>sendMessage</code and the messages are created using
+ * machine using <code>sendMessage</code> and the messages are created using
* <code>obtainMessage</code>. When the state machine receives a message the
* current state's <code>processMessage</code> is invoked. In the above example
* mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
@@ -59,9 +59,9 @@ import java.util.HashMap;
*
* Each state in the state machine may have a zero or one parent states and if
* a child state is unable to handle a message it may have the message processed
- * by its parent by returning false. If a message is never processed <code>unhandledMessage</code>
- * will be invoked to give one last chance for the state machine to process
- * the message.
+ * by its parent by returning false or NOT_HANDLED. If a message is never processed
+ * <code>unhandledMessage</code> will be invoked to give one last chance for the state machine
+ * to process the message.
*
* When all processing is completed a state machine may choose to call
* <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
@@ -95,7 +95,7 @@ import java.util.HashMap;
* any other messages that are on the queue or might be added later. Both of
* these are protected and may only be invoked from within a state machine.
*
- * To illustrate some of these properties we'll use state machine with 8
+ * To illustrate some of these properties we'll use state machine with an 8
* state hierarchy:
<code>
mP0
@@ -109,44 +109,19 @@ import java.util.HashMap;
*
* After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
* So the order of calling processMessage when a message is received is mS5,
- * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
- * message by returning false.
+ * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
+ * message by returning false or NOT_HANDLED.
*
* Now assume mS5.processMessage receives a message it can handle, and during
- * the handling determines the machine should changes states. It would call
- * transitionTo(mS4) and return true. Immediately after returning from
+ * the handling determines the machine should change states. It could call
+ * transitionTo(mS4) and return true or HANDLED. Immediately after returning from
* processMessage the state machine runtime will find the common parent,
* which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
* mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
* when the next message is received mS4.processMessage will be invoked.
*
- * To assist in describing an HSM a simple grammar has been created which
- * is informally defined here and a formal EBNF description is at the end
- * of the class comment.
- *
- * An HSM starts with the name and includes a set of hierarchical states.
- * A state is preceeded by one or more plus signs (+), to indicate its
- * depth and a hash (#) if its the initial state. Child states follow their
- * parents and have one more plus sign then their parent. Inside a state
- * are a series of messages, the actions they perform and if the processing
- * is complete ends with a period (.). If processing isn't complete and
- * the parent should process the message it ends with a caret (^). The
- * actions include send a message ($MESSAGE), defer a message (%MESSAGE),
- * transition to a new state (>MESSAGE) and an if statement
- * (if ( expression ) { list of actions }.)
- *
- * The Hsm HelloWorld could documented as:
- *
- * HelloWorld {
- * + # mState1.
- * }
- *
- * and interpreted as HSM HelloWorld:
- *
- * mState1 a root state (single +) and initial state (#) which
- * processes all messages completely, the period (.).
- *
- * The implementation is:
+ * Now for some concrete examples, here is the canonical HelloWorld as an HSM.
+ * It responds with "Hello World" being printed to the log for every message.
<code>
class HelloWorld extends HierarchicalStateMachine {
Hsm1(String name) {
@@ -164,7 +139,7 @@ class HelloWorld extends HierarchicalStateMachine {
class State1 extends HierarchicalState {
@Override public boolean processMessage(Message message) {
Log.d(TAG, "Hello World");
- return true;
+ return HANDLED;
}
}
State1 mState1 = new State1();
@@ -176,7 +151,7 @@ void testHelloWorld() {
}
</code>
*
- * A more interesting state machine is one of four states
+ * A more interesting state machine is one with four states
* with two independent parent states.
<code>
mP1 mP2
@@ -184,45 +159,68 @@ void testHelloWorld() {
mS2 mS1
</code>
*
- * documented as:
+ * Here is a description of this state machine using pseudo code.
*
- * Hsm1 {
- * + mP1 {
- * CMD_2 {
- * $CMD_3
- * %CMD_2
- * >mS2
- * }.
- * }
- * ++ # mS1 { CMD_1{ >mS1 }^ }
- * ++ mS2 {
- * CMD_2{$CMD_4}.
- * CMD_3{%CMD_3 ; >mP2}.
- * }
*
- * + mP2 e($CMD_5) {
- * CMD_3, CMD_4.
- * CMD_5{>HALT}.
- * }
+ * state mP1 {
+ * enter { log("mP1.enter"); }
+ * exit { log("mP1.exit"); }
+ * on msg {
+ * CMD_2 {
+ * send(CMD_3);
+ * defer(msg);
+ * transitonTo(mS2);
+ * return HANDLED;
+ * }
+ * return NOT_HANDLED;
+ * }
* }
*
- * and interpreted as HierarchicalStateMachine Hsm1:
- *
- * mP1 a root state.
- * processes message CMD_2 which sends CMD_3, defers CMD_2, and transitions to mS2
- *
- * mS1 a child of mP1 is the initial state:
- * processes message CMD_1 which transitions to itself and returns false to let mP1 handle it.
+ * INITIAL
+ * state mS1 parent mP1 {
+ * enter { log("mS1.enter"); }
+ * exit { log("mS1.exit"); }
+ * on msg {
+ * CMD_1 {
+ * transitionTo(mS1);
+ * return HANDLED;
+ * }
+ * return NOT_HANDLED;
+ * }
+ * }
*
- * mS2 a child of mP1:
- * processes message CMD_2 which send CMD_4
- * processes message CMD_3 which defers CMD_3 and transitions to mP2
+ * state mS2 parent mP1 {
+ * enter { log("mS2.enter"); }
+ * exit { log("mS2.exit"); }
+ * on msg {
+ * CMD_2 {
+ * send(CMD_4);
+ * return HANDLED;
+ * }
+ * CMD_3 {
+ * defer(msg);
+ * transitionTo(mP2);
+ * return HANDLED;
+ * }
+ * return NOT_HANDLED;
+ * }
+ * }
*
- * mP2 a root state.
- * on enter it sends CMD_5
- * processes message CMD_3
- * processes message CMD_4
- * processes message CMD_5 which transitions to halt state
+ * state mP2 {
+ * enter {
+ * log("mP2.enter");
+ * send(CMD_5);
+ * }
+ * exit { log("mP2.exit"); }
+ * on msg {
+ * CMD_3, CMD_4 { return HANDLED; }
+ * CMD_5 {
+ * transitionTo(HaltingState);
+ * return HANDLED;
+ * }
+ * return NOT_HANDLED;
+ * }
+ * }
*
* The implementation is below and also in HierarchicalStateMachineTest:
<code>
@@ -271,11 +269,11 @@ class Hsm1 extends HierarchicalStateMachine {
sendMessage(obtainMessage(CMD_3));
deferMessage(message);
transitionTo(mS2);
- retVal = true;
+ retVal = HANDLED;
break;
default:
// Any message we don't understand in this state invokes unhandledMessage
- retVal = false;
+ retVal = NOT_HANDLED;
break;
}
return retVal;
@@ -294,10 +292,10 @@ class Hsm1 extends HierarchicalStateMachine {
if (message.what == CMD_1) {
// Transition to ourself to show that enter/exit is called
transitionTo(mS1);
- return true;
+ return HANDLED;
} else {
// Let parent process all other messages
- return false;
+ return NOT_HANDLED;
}
}
@Override public void exit() {
@@ -315,15 +313,15 @@ class Hsm1 extends HierarchicalStateMachine {
switch(message.what) {
case(CMD_2):
sendMessage(obtainMessage(CMD_4));
- retVal = true;
+ retVal = HANDLED;
break;
case(CMD_3):
deferMessage(message);
transitionTo(mP2);
- retVal = true;
+ retVal = HANDLED;
break;
default:
- retVal = false;
+ retVal = NOT_HANDLED;
break;
}
return retVal;
@@ -349,7 +347,7 @@ class Hsm1 extends HierarchicalStateMachine {
transitionToHaltingState();
break;
}
- return true;
+ return HANDLED;
}
@Override public void exit() {
Log.d(TAG, "mP2.exit");
@@ -357,7 +355,7 @@ class Hsm1 extends HierarchicalStateMachine {
}
@Override
- protected void halting() {
+ void halting() {
Log.d(TAG, "halting");
synchronized (this) {
this.notifyAll();
@@ -413,53 +411,32 @@ class Hsm1 extends HierarchicalStateMachine {
* D/hsm1 ( 1999): mP2.exit
* D/hsm1 ( 1999): halting
*
- * Here is the HSM a BNF grammar, this is a first stab at creating an
- * HSM description language, suggestions corrections or alternatives
- * would be much appreciated.
- *
- * Legend:
- * {} ::= zero or more
- * {}+ ::= one or more
- * [] ::= zero or one
- * () ::= define a group with "or" semantics.
- *
- * HSM EBNF:
- * HSM = HSM_NAME "{" { STATE }+ "}" ;
- * HSM_NAME = alpha_numeric_name ;
- * STATE = INTRODUCE_STATE [ ENTER | [ ENTER EXIT ] "{" [ MESSAGES ] "}" [ EXIT ] ;
- * INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ;
- * STATE_DEPTH = "+" ;
- * INITIAL_STATE_INDICATOR = "#"
- * ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
- * MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ;
- * MSG_LIST = { MSG_NAME { "," MSG_NAME } };
- * EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
- * PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ;
- * SEND_ACTION = "$" MSG_NAME ;
- * DEFER_ACTION = "%" MSG_NAME ;
- * TRANSITION_ACTION = ">" STATE_NAME ;
- * HALT_ACTION = ">" HALT ;
- * MESSAGE_ACTIONS = { "{" ACTION_LIST "}" } [ PROCESS_COMPLETION ] ;
- * ACTION_LIST = ACTION { (";" | "\n") ACTION } ;
- * ACTION = IF_ACTION | SEND_ACTION | DEFER_ACTION | TRANSITION_ACTION | HALT_ACTION ;
- * IF_ACTION = "if(" boolean_expression ")" "{" ACTION_LIST "}"
- * PROCESS_IN_PARENT_OR_COMPLETE = "^" ;
- * PROCESS_COMPLETE = "." ;
- * STATE_NAME = alpha_numeric_name ;
- * MSG_NAME = alpha_numeric_name | ALL_OTHER_MESSAGES ;
- * ALL_OTHER_MESSAGES = "*" ;
- * EXP = boolean_expression ;
- *
- * Idioms:
- * * { %* }. ::= All other messages will be deferred.
*/
public class HierarchicalStateMachine {
private static final String TAG = "HierarchicalStateMachine";
private String mName;
+ /** Message.what value when quitting */
public static final int HSM_QUIT_CMD = -1;
+ /** Message.what value when initializing */
+ public static final int HSM_INIT_CMD = -1;
+
+ /**
+ * Convenience constant that maybe returned by processMessage
+ * to indicate the the message was processed and is not to be
+ * processed by parent states
+ */
+ public static final boolean HANDLED = true;
+
+ /**
+ * Convenience constant that maybe returned by processMessage
+ * to indicate the the message was NOT processed and is to be
+ * processed by parent states
+ */
+ public static final boolean NOT_HANDLED = false;
+
private static class HsmHandler extends Handler {
/** The debug flag */
@@ -468,6 +445,12 @@ public class HierarchicalStateMachine {
/** The quit object */
private static final Object mQuitObj = new Object();
+ /** The initialization message */
+ private static final Message mInitMsg = null;
+
+ /** The current message */
+ private Message mMsg;
+
/** A list of messages that this state machine has processed */
private ProcessedMessages mProcessedMessages = new ProcessedMessages();
@@ -550,8 +533,7 @@ public class HierarchicalStateMachine {
private class QuittingState extends HierarchicalState {
@Override
public boolean processMessage(Message msg) {
- // Ignore
- return false;
+ return NOT_HANDLED;
}
}
@@ -565,6 +547,9 @@ public class HierarchicalStateMachine {
public final void handleMessage(Message msg) {
if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
+ /** Save the current message */
+ mMsg = msg;
+
/**
* Check that construction was completed
*/
@@ -679,6 +664,7 @@ public class HierarchicalStateMachine {
* starting at the first entry.
*/
mIsConstructionCompleted = true;
+ mMsg = obtainMessage(HSM_INIT_CMD);
invokeEnterMethods(0);
/**
@@ -855,6 +841,13 @@ public class HierarchicalStateMachine {
}
/**
+ * @return current message
+ */
+ private final Message getCurrentMessage() {
+ return mMsg;
+ }
+
+ /**
* @return current state
*/
private final HierarchicalState getCurrentState() {
@@ -1025,6 +1018,14 @@ public class HierarchicalStateMachine {
protected final void addState(HierarchicalState state, HierarchicalState parent) {
mHsmHandler.addState(state, parent);
}
+
+ /**
+ * @return current message
+ */
+ protected final Message getCurrentMessage() {
+ return mHsmHandler.getCurrentMessage();
+ }
+
/**
* @return current state
*/
@@ -1032,7 +1033,6 @@ public class HierarchicalStateMachine {
return mHsmHandler.getCurrentState();
}
-
/**
* Add a new state to the state machine, parent will be null
* @param state to add
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index b13d656796db..4da74e6e31ca 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -43,59 +43,6 @@ public class BaseIWindow extends IWindow.Stub {
}
}
- public void dispatchKey(KeyEvent event) {
- try {
- mSession.finishKey(this);
- } catch (RemoteException ex) {
- }
- }
-
- public boolean onDispatchPointer(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- event.recycle();
- return false;
- }
-
- public void dispatchPointer(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- try {
- if (event == null) {
- event = mSession.getPendingPointerMove(this);
- onDispatchPointer(event, eventTime, false);
- } else if (callWhenDone) {
- if (!onDispatchPointer(event, eventTime, true)) {
- mSession.finishKey(this);
- }
- } else {
- onDispatchPointer(event, eventTime, false);
- }
- } catch (RemoteException ex) {
- }
- }
-
- public boolean onDispatchTrackball(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- event.recycle();
- return false;
- }
-
- public void dispatchTrackball(MotionEvent event, long eventTime,
- boolean callWhenDone) {
- try {
- if (event == null) {
- event = mSession.getPendingTrackballMove(this);
- onDispatchTrackball(event, eventTime, false);
- } else if (callWhenDone) {
- if (!onDispatchTrackball(event, eventTime, true)) {
- mSession.finishKey(this);
- }
- } else {
- onDispatchTrackball(event, eventTime, false);
- }
- } catch (RemoteException ex) {
- }
- }
-
public void dispatchAppVisibility(boolean visible) {
}
diff --git a/core/java/com/android/internal/view/BaseInputHandler.java b/core/java/com/android/internal/view/BaseInputHandler.java
new file mode 100644
index 000000000000..e943a7dd3c0d
--- /dev/null
+++ b/core/java/com/android/internal/view/BaseInputHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.view.InputHandler;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Base do-nothing implementation of an input handler.
+ * @hide
+ */
+public abstract class BaseInputHandler implements InputHandler {
+ public void handleKey(KeyEvent event, Runnable finishedCallback) {
+ finishedCallback.run();
+ }
+
+ public void handleMotion(MotionEvent event, Runnable finishedCallback) {
+ finishedCallback.run();
+ }
+}
diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java
index e0d3a5f87b2e..1e97cd687f40 100644
--- a/core/java/com/android/internal/view/BaseSurfaceHolder.java
+++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java
@@ -33,13 +33,16 @@ public abstract class BaseSurfaceHolder implements SurfaceHolder {
public final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
-
+ SurfaceHolder.Callback[] mGottenCallbacks;
+ boolean mHaveGottenCallbacks;
+
public final ReentrantLock mSurfaceLock = new ReentrantLock();
- public final Surface mSurface = new Surface();
+ public Surface mSurface = new Surface();
int mRequestedWidth = -1;
int mRequestedHeight = -1;
- int mRequestedFormat = PixelFormat.OPAQUE;
+ /** @hide */
+ protected int mRequestedFormat = PixelFormat.OPAQUE;
int mRequestedType = -1;
long mLastLockTime = 0;
@@ -83,6 +86,31 @@ public abstract class BaseSurfaceHolder implements SurfaceHolder {
}
}
+ public SurfaceHolder.Callback[] getCallbacks() {
+ if (mHaveGottenCallbacks) {
+ return mGottenCallbacks;
+ }
+
+ synchronized (mCallbacks) {
+ final int N = mCallbacks.size();
+ if (N > 0) {
+ if (mGottenCallbacks == null || mGottenCallbacks.length != N) {
+ mGottenCallbacks = new SurfaceHolder.Callback[N];
+ }
+ mCallbacks.toArray(mGottenCallbacks);
+ } else {
+ mGottenCallbacks = null;
+ }
+ mHaveGottenCallbacks = true;
+ }
+
+ return mGottenCallbacks;
+ }
+
+ public void ungetCallbacks() {
+ mHaveGottenCallbacks = false;
+ }
+
public void setFixedSize(int width, int height) {
if (mRequestedWidth != width || mRequestedHeight != height) {
mRequestedWidth = width;
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index a765e384aa5e..986ba3815eb6 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -31,9 +31,10 @@ import java.lang.ref.WeakReference;
public class IInputConnectionWrapper extends IInputContext.Stub {
static final String TAG = "IInputConnectionWrapper";
-
+
private static final int DO_GET_TEXT_AFTER_CURSOR = 10;
private static final int DO_GET_TEXT_BEFORE_CURSOR = 20;
+ private static final int DO_GET_SELECTED_TEXT = 25;
private static final int DO_GET_CURSOR_CAPS_MODE = 30;
private static final int DO_GET_EXTRACTED_TEXT = 40;
private static final int DO_COMMIT_TEXT = 50;
@@ -42,6 +43,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
private static final int DO_PERFORM_EDITOR_ACTION = 58;
private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59;
private static final int DO_SET_COMPOSING_TEXT = 60;
+ private static final int DO_SET_COMPOSING_REGION = 63;
private static final int DO_FINISH_COMPOSING_TEXT = 65;
private static final int DO_SEND_KEY_EVENT = 70;
private static final int DO_DELETE_SURROUNDING_TEXT = 80;
@@ -50,7 +52,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
private static final int DO_REPORT_FULLSCREEN_MODE = 100;
private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
private static final int DO_CLEAR_META_KEY_STATES = 130;
-
+
private WeakReference<InputConnection> mInputConnection;
private Looper mMainLooper;
@@ -92,6 +94,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
}
+ public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
+ dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
+ }
+
public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
}
@@ -122,6 +128,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0));
}
+ public void setComposingRegion(int start, int end) {
+ dispatchMessage(obtainMessageII(DO_SET_COMPOSING_REGION, start, end));
+ }
+
public void setComposingText(CharSequence text, int newCursorPosition) {
dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text));
}
@@ -206,6 +216,22 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
}
return;
}
+ case DO_GET_SELECTED_TEXT: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ try {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getSelectedText on inactive InputConnection");
+ args.callback.setSelectedText(null, args.seq);
+ return;
+ }
+ args.callback.setSelectedText(ic.getSelectedText(
+ msg.arg1), args.seq);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Got RemoteException calling setSelectedText", e);
+ }
+ return;
+ }
case DO_GET_CURSOR_CAPS_MODE: {
SomeArgs args = (SomeArgs)msg.obj;
try {
@@ -292,6 +318,15 @@ public class IInputConnectionWrapper extends IInputContext.Stub {
ic.setComposingText((CharSequence)msg.obj, msg.arg1);
return;
}
+ case DO_SET_COMPOSING_REGION: {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "setComposingRegion on inactive InputConnection");
+ return;
+ }
+ ic.setComposingRegion(msg.arg1, msg.arg2);
+ return;
+ }
case DO_FINISH_COMPOSING_TEXT: {
InputConnection ic = mInputConnection.get();
// Note we do NOT check isActive() here, because this is safe
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index 02cb9e4df5e3..333fc82c964a 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -65,4 +65,8 @@ import com.android.internal.view.IInputContextCallback;
void clearMetaKeyStates(int states);
void performPrivateCommand(String action, in Bundle data);
+
+ void setComposingRegion(int start, int end);
+
+ void getSelectedText(int flags, int seq, IInputContextCallback callback);
}
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
index 9b8c43c6ff72..661066baa1d9 100644
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ b/core/java/com/android/internal/view/IInputContextCallback.aidl
@@ -26,4 +26,5 @@ oneway interface IInputContextCallback {
void setTextAfterCursor(CharSequence textAfterCursor, int seq);
void setCursorCapsMode(int capsMode, int seq);
void setExtractedText(in ExtractedText extractedText, int seq);
+ void setSelectedText(CharSequence selectedText, int seq);
}
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index a05ff14bcccd..338dcaa7db96 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -48,4 +48,6 @@ oneway interface IInputMethodSession {
void appPrivateCommand(String action, in Bundle data);
void toggleSoftInput(int showFlags, int hideFlags);
+
+ void finishSession();
}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 3c44e58a9467..08c302636a21 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -16,8 +16,6 @@
package com.android.internal.view;
-import com.android.internal.view.IInputContext;
-
import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -38,6 +36,7 @@ public class InputConnectionWrapper implements InputConnection {
public boolean mHaveValue;
public CharSequence mTextBeforeCursor;
public CharSequence mTextAfterCursor;
+ public CharSequence mSelectedText;
public ExtractedText mExtractedText;
public int mCursorCapsMode;
@@ -114,6 +113,19 @@ public class InputConnectionWrapper implements InputConnection {
}
}
+ public void setSelectedText(CharSequence selectedText, int seq) {
+ synchronized (this) {
+ if (seq == mSeq) {
+ mSelectedText = selectedText;
+ mHaveValue = true;
+ notifyAll();
+ } else {
+ Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ + ") in setSelectedText, ignoring.");
+ }
+ }
+ }
+
public void setCursorCapsMode(int capsMode, int seq) {
synchronized (this) {
if (seq == mSeq) {
@@ -203,6 +215,24 @@ public class InputConnectionWrapper implements InputConnection {
return value;
}
+ public CharSequence getSelectedText(int flags) {
+ CharSequence value = null;
+ try {
+ InputContextCallback callback = InputContextCallback.getInstance();
+ mIInputContext.getSelectedText(flags, callback.mSeq, callback);
+ synchronized (callback) {
+ callback.waitForResultLocked();
+ if (callback.mHaveValue) {
+ value = callback.mSelectedText;
+ }
+ }
+ callback.dispose();
+ } catch (RemoteException e) {
+ return null;
+ }
+ return value;
+ }
+
public int getCursorCapsMode(int reqModes) {
int value = 0;
try {
@@ -283,7 +313,16 @@ public class InputConnectionWrapper implements InputConnection {
return false;
}
}
-
+
+ public boolean setComposingRegion(int start, int end) {
+ try {
+ mIInputContext.setComposingRegion(start, end);
+ return true;
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
public boolean setComposingText(CharSequence text, int newCursorPosition) {
try {
mIInputContext.setComposingText(text, newCursorPosition);
diff --git a/core/java/com/android/internal/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java
new file mode 100644
index 000000000000..9c1b558c740b
--- /dev/null
+++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java
@@ -0,0 +1,13 @@
+package com.android.internal.view;
+
+import android.view.InputQueue;
+import android.view.SurfaceHolder;
+
+/** hahahah */
+public interface RootViewSurfaceTaker {
+ SurfaceHolder.Callback2 willYouTakeTheSurface();
+ void setSurfaceType(int type);
+ void setSurfaceFormat(int format);
+ void setSurfaceKeepScreenOn(boolean keepOn);
+ InputQueue.Callback willYouTakeTheInputQueue();
+}
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index 23e2277379a9..fa47ff638cd5 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -30,7 +30,7 @@ import android.provider.Settings;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
import android.widget.TextView;
import java.text.DateFormatSymbols;
@@ -39,7 +39,7 @@ import java.util.Calendar;
/**
* Displays the time
*/
-public class DigitalClock extends RelativeLayout {
+public class DigitalClock extends LinearLayout {
private final static String M12 = "h:mm";
private final static String M24 = "kk:mm";
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index dbbd2860a5c0..c7886055444d 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -19,6 +19,7 @@ package com.android.internal.widget;
import android.app.admin.DevicePolicyManager;
import android.content.ContentResolver;
import android.content.Context;
+import android.os.FileObserver;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -33,6 +34,7 @@ import com.android.internal.R;
import com.android.internal.telephony.ITelephony;
import com.google.android.collect.Lists;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
@@ -40,6 +42,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Utilities for the lock patten and its settings.
@@ -48,8 +51,9 @@ public class LockPatternUtils {
private static final String TAG = "LockPatternUtils";
- private static final String LOCK_PATTERN_FILE = "/system/gesture.key";
- private static final String LOCK_PASSWORD_FILE = "/system/password.key";
+ private static final String SYSTEM_DIRECTORY = "/system/";
+ private static final String LOCK_PATTERN_FILE = "gesture.key";
+ private static final String LOCK_PASSWORD_FILE = "password.key";
/**
* The maximum number of incorrect attempts before the user is prevented
@@ -98,6 +102,10 @@ public class LockPatternUtils {
private static String sLockPatternFilename;
private static String sLockPasswordFilename;
+ private static final AtomicBoolean sHaveNonZeroPatternFile = new AtomicBoolean(false);
+ private static final AtomicBoolean sHaveNonZeroPasswordFile = new AtomicBoolean(false);
+ private static FileObserver sPasswordObserver;
+
public DevicePolicyManager getDevicePolicyManager() {
if (mDevicePolicyManager == null) {
mDevicePolicyManager =
@@ -115,14 +123,31 @@ public class LockPatternUtils {
public LockPatternUtils(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
- // Initialize the location of gesture lock file
+
+ // Initialize the location of gesture & PIN lock files
if (sLockPatternFilename == null) {
- sLockPatternFilename = android.os.Environment.getDataDirectory()
- .getAbsolutePath() + LOCK_PATTERN_FILE;
- sLockPasswordFilename = android.os.Environment.getDataDirectory()
- .getAbsolutePath() + LOCK_PASSWORD_FILE;
+ String dataSystemDirectory =
+ android.os.Environment.getDataDirectory().getAbsolutePath() +
+ SYSTEM_DIRECTORY;
+ sLockPatternFilename = dataSystemDirectory + LOCK_PATTERN_FILE;
+ sLockPasswordFilename = dataSystemDirectory + LOCK_PASSWORD_FILE;
+ sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0);
+ sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0);
+ int fileObserverMask = FileObserver.CLOSE_WRITE | FileObserver.DELETE |
+ FileObserver.MOVED_TO | FileObserver.CREATE;
+ sPasswordObserver = new FileObserver(dataSystemDirectory, fileObserverMask) {
+ public void onEvent(int event, String path) {
+ if (LOCK_PATTERN_FILE.equals(path)) {
+ Log.d(TAG, "lock pattern file changed");
+ sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0);
+ } else if (LOCK_PASSWORD_FILE.equals(path)) {
+ Log.d(TAG, "lock password file changed");
+ sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0);
+ }
+ }
+ };
+ sPasswordObserver.startWatching();
}
-
}
public int getRequestedMinimumPasswordLength() {
@@ -202,31 +227,11 @@ public class LockPatternUtils {
}
/**
- * Checks to see if the given file exists and contains any data. Returns true if it does,
- * false otherwise.
- * @param filename
- * @return true if file exists and is non-empty.
- */
- private boolean nonEmptyFileExists(String filename) {
- try {
- // Check if we can read a byte from the file
- RandomAccessFile raf = new RandomAccessFile(filename, "r");
- raf.readByte();
- raf.close();
- return true;
- } catch (FileNotFoundException fnfe) {
- return false;
- } catch (IOException ioe) {
- return false;
- }
- }
-
- /**
* Check to see if the user has stored a lock pattern.
* @return Whether a saved pattern exists.
*/
public boolean savedPatternExists() {
- return nonEmptyFileExists(sLockPatternFilename);
+ return sHaveNonZeroPatternFile.get();
}
/**
@@ -234,7 +239,7 @@ public class LockPatternUtils {
* @return Whether a saved pattern exists.
*/
public boolean savedPasswordExists() {
- return nonEmptyFileExists(sLockPasswordFilename);
+ return sHaveNonZeroPasswordFile.get();
}
/**
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index f487a1637dbc..12cf8536f52d 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -19,8 +19,10 @@ package com.android.internal.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.RectF;
import android.graphics.Paint.FontMetricsInt;
import android.util.Log;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -29,16 +31,45 @@ import android.view.ViewConfiguration;
import java.util.ArrayList;
public class PointerLocationView extends View {
+ private static final String TAG = "Pointer";
+
public static class PointerState {
- private final ArrayList<Float> mXs = new ArrayList<Float>();
- private final ArrayList<Float> mYs = new ArrayList<Float>();
+ // Trace of previous points.
+ private float[] mTraceX = new float[32];
+ private float[] mTraceY = new float[32];
+ private int mTraceCount;
+
+ // True if the pointer is down.
private boolean mCurDown;
- private int mCurX;
- private int mCurY;
- private float mCurPressure;
- private float mCurSize;
- private int mCurWidth;
- private VelocityTracker mVelocity;
+
+ // Most recent coordinates.
+ private MotionEvent.PointerCoords mCoords = new MotionEvent.PointerCoords();
+
+ // Most recent velocity.
+ private float mXVelocity;
+ private float mYVelocity;
+
+ public void clearTrace() {
+ mTraceCount = 0;
+ }
+
+ public void addTrace(float x, float y) {
+ int traceCapacity = mTraceX.length;
+ if (mTraceCount == traceCapacity) {
+ traceCapacity *= 2;
+ float[] newTraceX = new float[traceCapacity];
+ System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
+ mTraceX = newTraceX;
+
+ float[] newTraceY = new float[traceCapacity];
+ System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
+ mTraceY = newTraceY;
+ }
+
+ mTraceX[mTraceCount] = x;
+ mTraceY[mTraceCount] = y;
+ mTraceCount += 1;
+ }
}
private final ViewConfiguration mVC;
@@ -53,8 +84,12 @@ public class PointerLocationView extends View {
private boolean mCurDown;
private int mCurNumPointers;
private int mMaxNumPointers;
- private final ArrayList<PointerState> mPointers
- = new ArrayList<PointerState>();
+ private int mActivePointerId;
+ private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
+
+ private final VelocityTracker mVelocity;
+
+ private final FasterStringBuilder mText = new FasterStringBuilder();
private boolean mPrintCoords = true;
@@ -88,8 +123,22 @@ public class PointerLocationView extends View {
mPaint.setStrokeWidth(1);
PointerState ps = new PointerState();
- ps.mVelocity = VelocityTracker.obtain();
mPointers.add(ps);
+ mActivePointerId = 0;
+
+ mVelocity = VelocityTracker.obtain();
+
+ logInputDeviceCapabilities();
+ }
+
+ private void logInputDeviceCapabilities() {
+ int[] deviceIds = InputDevice.getDeviceIds();
+ for (int i = 0; i < deviceIds.length; i++) {
+ InputDevice device = InputDevice.getDevice(deviceIds[i]);
+ if (device != null) {
+ Log.i(TAG, device.toString());
+ }
+ }
}
public void setPrintCoords(boolean state) {
@@ -109,6 +158,21 @@ public class PointerLocationView extends View {
+ " bottom=" + mTextMetrics.bottom);
}
}
+
+ // Draw an oval. When angle is 0 radians, orients the major axis vertically,
+ // angles less than or greater than 0 radians rotate the major axis left or right.
+ private RectF mReusableOvalRect = new RectF();
+ private void drawOval(Canvas canvas, float x, float y, float major, float minor,
+ float angle, Paint paint) {
+ canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ canvas.rotate((float) (angle * 180 / Math.PI), x, y);
+ mReusableOvalRect.left = x - minor / 2;
+ mReusableOvalRect.right = x + minor / 2;
+ mReusableOvalRect.top = y - major / 2;
+ mReusableOvalRect.bottom = y + major / 2;
+ canvas.drawOval(mReusableOvalRect, paint);
+ canvas.restore();
+ }
@Override
protected void onDraw(Canvas canvas) {
@@ -120,76 +184,81 @@ public class PointerLocationView extends View {
final int NP = mPointers.size();
- if (NP > 0) {
- final PointerState ps = mPointers.get(0);
- canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
- canvas.drawText("P: " + mCurNumPointers + " / " + mMaxNumPointers,
- 1, base, mTextPaint);
+ // Labels
+ if (mActivePointerId >= 0) {
+ final PointerState ps = mPointers.get(mActivePointerId);
- final int N = ps.mXs.size();
+ canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
+ canvas.drawText(mText.clear()
+ .append("P: ").append(mCurNumPointers)
+ .append(" / ").append(mMaxNumPointers)
+ .toString(), 1, base, mTextPaint);
+
+ final int N = ps.mTraceCount;
if ((mCurDown && ps.mCurDown) || N == 0) {
canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
- canvas.drawText("X: " + ps.mCurX, 1 + itemW, base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("X: ").append(ps.mCoords.x, 1)
+ .toString(), 1 + itemW, base, mTextPaint);
canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
- canvas.drawText("Y: " + ps.mCurY, 1 + itemW * 2, base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("Y: ").append(ps.mCoords.y, 1)
+ .toString(), 1 + itemW * 2, base, mTextPaint);
} else {
- float dx = ps.mXs.get(N-1) - ps.mXs.get(0);
- float dy = ps.mYs.get(N-1) - ps.mYs.get(0);
+ float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
+ float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
Math.abs(dx) < mVC.getScaledTouchSlop()
? mTextBackgroundPaint : mTextLevelPaint);
- canvas.drawText("dX: " + String.format("%.1f", dx), 1 + itemW, base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("dX: ").append(dx, 1)
+ .toString(), 1 + itemW, base, mTextPaint);
canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
Math.abs(dy) < mVC.getScaledTouchSlop()
? mTextBackgroundPaint : mTextLevelPaint);
- canvas.drawText("dY: " + String.format("%.1f", dy), 1 + itemW * 2, base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("dY: ").append(dy, 1)
+ .toString(), 1 + itemW * 2, base, mTextPaint);
}
canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
- int velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getXVelocity() * 1000);
- canvas.drawText("Xv: " + velocity, 1 + itemW * 3, base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("Xv: ").append(ps.mXVelocity, 3)
+ .toString(), 1 + itemW * 3, base, mTextPaint);
canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
- velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getYVelocity() * 1000);
- canvas.drawText("Yv: " + velocity, 1 + itemW * 4, base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("Yv: ").append(ps.mYVelocity, 3)
+ .toString(), 1 + itemW * 4, base, mTextPaint);
canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
- canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCurPressure * itemW) - 1,
+ canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1,
bottom, mTextLevelPaint);
- canvas.drawText("Prs: " + String.format("%.2f", ps.mCurPressure), 1 + itemW * 5,
- base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("Prs: ").append(ps.mCoords.pressure, 2)
+ .toString(), 1 + itemW * 5, base, mTextPaint);
canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
- canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCurSize * itemW) - 1,
+ canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1,
bottom, mTextLevelPaint);
- canvas.drawText("Size: " + String.format("%.2f", ps.mCurSize), 1 + itemW * 6,
- base, mTextPaint);
+ canvas.drawText(mText.clear()
+ .append("Size: ").append(ps.mCoords.size, 2)
+ .toString(), 1 + itemW * 6, base, mTextPaint);
}
- for (int p=0; p<NP; p++) {
+ // Pointer trace.
+ for (int p = 0; p < NP; p++) {
final PointerState ps = mPointers.get(p);
- if (mCurDown && ps.mCurDown) {
- canvas.drawLine(0, (int)ps.mCurY, getWidth(), (int)ps.mCurY, mTargetPaint);
- canvas.drawLine((int)ps.mCurX, 0, (int)ps.mCurX, getHeight(), mTargetPaint);
- int pressureLevel = (int)(ps.mCurPressure*255);
- mPaint.setARGB(255, pressureLevel, 128, 255-pressureLevel);
- canvas.drawPoint(ps.mCurX, ps.mCurY, mPaint);
- canvas.drawCircle(ps.mCurX, ps.mCurY, ps.mCurWidth, mPaint);
- }
- }
-
- for (int p=0; p<NP; p++) {
- final PointerState ps = mPointers.get(p);
-
- final int N = ps.mXs.size();
- float lastX=0, lastY=0;
+ // Draw path.
+ final int N = ps.mTraceCount;
+ float lastX = 0, lastY = 0;
boolean haveLast = false;
boolean drawn = false;
mPaint.setARGB(255, 128, 255, 255);
- for (int i=0; i<N; i++) {
- float x = ps.mXs.get(i);
- float y = ps.mYs.get(i);
+ for (int i=0; i < N; i++) {
+ float x = ps.mTraceX[i];
+ float y = ps.mTraceY[i];
if (Float.isNaN(x)) {
haveLast = false;
continue;
@@ -204,25 +273,57 @@ public class PointerLocationView extends View {
haveLast = true;
}
+ // Draw velocity vector.
if (drawn) {
- if (ps.mVelocity != null) {
- mPaint.setARGB(255, 255, 64, 128);
- float xVel = ps.mVelocity.getXVelocity() * (1000/60);
- float yVel = ps.mVelocity.getYVelocity() * (1000/60);
- canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint);
- } else {
- canvas.drawPoint(lastX, lastY, mPaint);
- }
+ mPaint.setARGB(255, 255, 64, 128);
+ float xVel = ps.mXVelocity * (1000 / 60);
+ float yVel = ps.mYVelocity * (1000 / 60);
+ canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
+ }
+
+ if (mCurDown && ps.mCurDown) {
+ // Draw crosshairs.
+ canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
+ canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint);
+
+ // Draw current point.
+ int pressureLevel = (int)(ps.mCoords.pressure * 255);
+ mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
+ canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
+
+ // Draw current touch ellipse.
+ mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
+ drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
+ ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
+
+ // Draw current tool ellipse.
+ mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
+ drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
+ ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
}
}
}
}
+
+ private void logPointerCoords(MotionEvent.PointerCoords coords, int id) {
+ Log.i(TAG, mText.clear()
+ .append("Pointer ").append(id + 1)
+ .append(": (").append(coords.x, 3).append(", ").append(coords.y, 3)
+ .append(") Pressure=").append(coords.pressure, 3)
+ .append(" Size=").append(coords.size, 3)
+ .append(" TouchMajor=").append(coords.touchMajor, 3)
+ .append(" TouchMinor=").append(coords.touchMinor, 3)
+ .append(" ToolMajor=").append(coords.toolMajor, 3)
+ .append(" ToolMinor=").append(coords.toolMinor, 3)
+ .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
+ .append("deg").toString());
+ }
public void addTouchEvent(MotionEvent event) {
synchronized (mPointers) {
int action = event.getAction();
- //Log.i("Pointer", "Motion: action=0x" + Integer.toHexString(action)
+ //Log.i(TAG, "Motion: action=0x" + Integer.toHexString(action)
// + " pointers=" + event.getPointerCount());
int NP = mPointers.size();
@@ -235,36 +336,38 @@ public class PointerLocationView extends View {
//} else {
// mRect.setEmpty();
//}
- if (action == MotionEvent.ACTION_DOWN) {
- for (int p=0; p<NP; p++) {
- final PointerState ps = mPointers.get(p);
- ps.mXs.clear();
- ps.mYs.clear();
- ps.mVelocity = VelocityTracker.obtain();
- ps.mCurDown = false;
- }
- mPointers.get(0).mCurDown = true;
- mMaxNumPointers = 0;
- if (mPrintCoords) {
- Log.i("Pointer", "Pointer 1: DOWN");
+ if (action == MotionEvent.ACTION_DOWN
+ || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
+ final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
+ if (action == MotionEvent.ACTION_DOWN) {
+ for (int p=0; p<NP; p++) {
+ final PointerState ps = mPointers.get(p);
+ ps.clearTrace();
+ ps.mCurDown = false;
+ }
+ mCurDown = true;
+ mMaxNumPointers = 0;
+ mVelocity.clear();
}
- }
-
- if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
- final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+
final int id = event.getPointerId(index);
while (NP <= id) {
PointerState ps = new PointerState();
- ps.mVelocity = VelocityTracker.obtain();
mPointers.add(ps);
NP++;
}
+
+ if (mActivePointerId < 0 ||
+ ! mPointers.get(mActivePointerId).mCurDown) {
+ mActivePointerId = id;
+ }
+
final PointerState ps = mPointers.get(id);
- ps.mVelocity = VelocityTracker.obtain();
ps.mCurDown = true;
if (mPrintCoords) {
- Log.i("Pointer", "Pointer " + (id+1) + ": DOWN");
+ Log.i(TAG, mText.clear().append("Pointer ")
+ .append(id + 1).append(": DOWN").toString());
}
}
@@ -276,64 +379,52 @@ public class PointerLocationView extends View {
if (mMaxNumPointers < mCurNumPointers) {
mMaxNumPointers = mCurNumPointers;
}
+
+ mVelocity.addMovement(event);
+ mVelocity.computeCurrentVelocity(1);
for (int i=0; i<NI; i++) {
final int id = event.getPointerId(i);
final PointerState ps = mPointers.get(id);
- ps.mVelocity.addMovement(event);
- ps.mVelocity.computeCurrentVelocity(1);
final int N = event.getHistorySize();
for (int j=0; j<N; j++) {
+ event.getHistoricalPointerCoords(i, j, ps.mCoords);
if (mPrintCoords) {
- Log.i("Pointer", "Pointer " + (id+1) + ": ("
- + event.getHistoricalX(i, j)
- + ", " + event.getHistoricalY(i, j) + ")"
- + " Prs=" + event.getHistoricalPressure(i, j)
- + " Size=" + event.getHistoricalSize(i, j));
+ logPointerCoords(ps.mCoords, id);
}
- ps.mXs.add(event.getHistoricalX(i, j));
- ps.mYs.add(event.getHistoricalY(i, j));
+ ps.addTrace(event.getHistoricalX(i, j), event.getHistoricalY(i, j));
}
+ event.getPointerCoords(i, ps.mCoords);
if (mPrintCoords) {
- Log.i("Pointer", "Pointer " + (id+1) + ": ("
- + event.getX(i) + ", " + event.getY(i) + ")"
- + " Prs=" + event.getPressure(i)
- + " Size=" + event.getSize(i));
+ logPointerCoords(ps.mCoords, id);
}
- ps.mXs.add(event.getX(i));
- ps.mYs.add(event.getY(i));
- ps.mCurX = (int)event.getX(i);
- ps.mCurY = (int)event.getY(i);
- //Log.i("Pointer", "Pointer #" + p + ": (" + ps.mCurX
- // + "," + ps.mCurY + ")");
- ps.mCurPressure = event.getPressure(i);
- ps.mCurSize = event.getSize(i);
- ps.mCurWidth = (int)(ps.mCurSize*(getWidth()/3));
+ ps.addTrace(ps.mCoords.x, ps.mCoords.y);
+ ps.mXVelocity = mVelocity.getXVelocity(id);
+ ps.mYVelocity = mVelocity.getYVelocity(id);
}
- if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
- final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
- >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+ if (action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL
+ || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
+ final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+ >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
+
final int id = event.getPointerId(index);
final PointerState ps = mPointers.get(id);
- ps.mXs.add(Float.NaN);
- ps.mYs.add(Float.NaN);
ps.mCurDown = false;
if (mPrintCoords) {
- Log.i("Pointer", "Pointer " + (id+1) + ": UP");
+ Log.i(TAG, mText.clear().append("Pointer ")
+ .append(id + 1).append(": UP").toString());
}
- }
-
- if (action == MotionEvent.ACTION_UP) {
- for (int i=0; i<NI; i++) {
- final int id = event.getPointerId(i);
- final PointerState ps = mPointers.get(id);
- if (ps.mCurDown) {
- ps.mCurDown = false;
- if (mPrintCoords) {
- Log.i("Pointer", "Pointer " + (id+1) + ": UP");
- }
+
+ if (action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL) {
+ mCurDown = false;
+ } else {
+ if (mActivePointerId == id) {
+ mActivePointerId = event.getPointerId(index == 0 ? 1 : 0);
}
+ ps.addTrace(Float.NaN, Float.NaN);
}
}
@@ -354,8 +445,120 @@ public class PointerLocationView extends View {
@Override
public boolean onTrackballEvent(MotionEvent event) {
- Log.i("Pointer", "Trackball: " + event);
+ Log.i(TAG, "Trackball: " + event);
return super.onTrackballEvent(event);
}
+ // HACK
+ // A quick and dirty string builder implementation optimized for GC.
+ // Using String.format causes the application grind to a halt when
+ // more than a couple of pointers are down due to the number of
+ // temporary objects allocated while formatting strings for drawing or logging.
+ private static final class FasterStringBuilder {
+ private char[] mChars;
+ private int mLength;
+
+ public FasterStringBuilder() {
+ mChars = new char[64];
+ }
+
+ public FasterStringBuilder clear() {
+ mLength = 0;
+ return this;
+ }
+
+ public FasterStringBuilder append(String value) {
+ final int valueLength = value.length();
+ final int index = reserve(valueLength);
+ value.getChars(0, valueLength, mChars, index);
+ mLength += valueLength;
+ return this;
+ }
+
+ public FasterStringBuilder append(int value) {
+ return append(value, 0);
+ }
+
+ public FasterStringBuilder append(int value, int zeroPadWidth) {
+ final boolean negative = value < 0;
+ if (negative) {
+ value = - value;
+ if (value < 0) {
+ append("-2147483648");
+ return this;
+ }
+ }
+
+ int index = reserve(11);
+ final char[] chars = mChars;
+
+ if (value == 0) {
+ chars[index++] = '0';
+ mLength += 1;
+ return this;
+ }
+
+ if (negative) {
+ chars[index++] = '-';
+ }
+
+ int divisor = 1000000000;
+ int numberWidth = 10;
+ while (value < divisor) {
+ divisor /= 10;
+ numberWidth -= 1;
+ if (numberWidth < zeroPadWidth) {
+ chars[index++] = '0';
+ }
+ }
+
+ do {
+ int digit = value / divisor;
+ value -= digit * divisor;
+ divisor /= 10;
+ chars[index++] = (char) (digit + '0');
+ } while (divisor != 0);
+
+ mLength = index;
+ return this;
+ }
+
+ public FasterStringBuilder append(float value, int precision) {
+ int scale = 1;
+ for (int i = 0; i < precision; i++) {
+ scale *= 10;
+ }
+ value = (float) (Math.rint(value * scale) / scale);
+
+ append((int) value);
+
+ if (precision != 0) {
+ append(".");
+ value = Math.abs(value);
+ value -= Math.floor(value);
+ append((int) (value * scale), precision);
+ }
+
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return new String(mChars, 0, mLength);
+ }
+
+ private int reserve(int length) {
+ final int oldLength = mLength;
+ final int newLength = mLength + length;
+ final char[] oldChars = mChars;
+ final int oldCapacity = oldChars.length;
+ if (newLength > oldCapacity) {
+ final int newCapacity = oldCapacity * 2;
+ final char[] newChars = new char[newCapacity];
+ System.arraycopy(oldChars, 0, newChars, 0, oldLength);
+ mChars = newChars;
+ }
+ return oldLength;
+ }
+ }
}
diff --git a/core/java/com/google/android/mms/pdu/PduParser.java b/core/java/com/google/android/mms/pdu/PduParser.java
index 131ac51707bb..21f0c93df3dc 100644
--- a/core/java/com/google/android/mms/pdu/PduParser.java
+++ b/core/java/com/google/android/mms/pdu/PduParser.java
@@ -202,7 +202,18 @@ public class PduParser {
PduHeaders headers = new PduHeaders();
while (keepParsing && (pduDataStream.available() > 0)) {
+ pduDataStream.mark(1);
int headerField = extractByteValue(pduDataStream);
+ /* parse custom text header */
+ if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) {
+ pduDataStream.reset();
+ byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING);
+ if (LOCAL_LOGV) {
+ Log.v(LOG_TAG, "TextHeader: " + new String(bVal));
+ }
+ /* we should ignore it at the moment */
+ continue;
+ }
switch (headerField) {
case PduHeaders.MESSAGE_TYPE:
{
@@ -780,26 +791,34 @@ public class PduParser {
/* get part's data */
if (dataLength > 0) {
byte[] partData = new byte[dataLength];
+ String partContentType = new String(part.getContentType());
pduDataStream.read(partData, 0, dataLength);
- // Check Content-Transfer-Encoding.
- byte[] partDataEncoding = part.getContentTransferEncoding();
- if (null != partDataEncoding) {
- String encoding = new String(partDataEncoding);
- if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
- // Decode "base64" into "binary".
- partData = Base64.decodeBase64(partData);
- } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
- // Decode "quoted-printable" into "binary".
- partData = QuotedPrintable.decodeQuotedPrintable(partData);
- } else {
- // "binary" is the default encoding.
+ if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) {
+ // parse "multipart/vnd.wap.multipart.alternative".
+ PduBody childBody = parseParts(new ByteArrayInputStream(partData));
+ // take the first part of children.
+ part = childBody.getPart(0);
+ } else {
+ // Check Content-Transfer-Encoding.
+ byte[] partDataEncoding = part.getContentTransferEncoding();
+ if (null != partDataEncoding) {
+ String encoding = new String(partDataEncoding);
+ if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) {
+ // Decode "base64" into "binary".
+ partData = Base64.decodeBase64(partData);
+ } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) {
+ // Decode "quoted-printable" into "binary".
+ partData = QuotedPrintable.decodeQuotedPrintable(partData);
+ } else {
+ // "binary" is the default encoding.
+ }
}
+ if (null == partData) {
+ log("Decode part data error!");
+ return null;
+ }
+ part.setData(partData);
}
- if (null == partData) {
- log("Decode part data error!");
- return null;
- }
- part.setData(partData);
}
/* add this part to body */