summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java225
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl19
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java145
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl30
-rw-r--r--core/java/android/accessibilityservice/IEventListener.aidl34
-rw-r--r--core/java/android/app/Activity.java105
-rw-r--r--core/java/android/app/ActivityManagerNative.java114
-rw-r--r--core/java/android/app/ActivityThread.java304
-rw-r--r--core/java/android/app/ApplicationContext.java153
-rw-r--r--core/java/android/app/ApplicationErrorReport.java308
-rw-r--r--core/java/android/app/ApplicationThreadNative.java75
-rw-r--r--core/java/android/app/BackupAgent.java (renamed from core/java/android/backup/BackupService.java)101
-rw-r--r--core/java/android/app/DatePickerDialog.java2
-rw-r--r--core/java/android/app/Dialog.java34
-rw-r--r--core/java/android/app/FullBackupAgent.java58
-rw-r--r--core/java/android/app/IActivityManager.java39
-rw-r--r--core/java/android/app/IApplicationThread.java20
-rw-r--r--core/java/android/app/IBackupAgent.aidl (renamed from core/java/android/backup/IBackupService.aidl)13
-rwxr-xr-xcore/java/android/app/IIntentReceiver.aidl33
-rw-r--r--core/java/android/app/IIntentSender.aidl27
-rw-r--r--core/java/android/app/ISearchManager.aidl17
-rw-r--r--core/java/android/app/ISearchManagerCallback.aidl23
-rw-r--r--core/java/android/app/Instrumentation.java10
-rw-r--r--core/java/android/app/LauncherActivity.java28
-rw-r--r--core/java/android/app/PendingIntent.java20
-rw-r--r--core/java/android/app/SearchDialog.java444
-rw-r--r--core/java/android/app/SearchManager.java502
-rw-r--r--core/java/android/app/SuggestionsAdapter.java373
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java12
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java7
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java15
-rw-r--r--core/java/android/backup/AbsoluteFileBackupHelper.java66
-rw-r--r--core/java/android/backup/BackupDataInput.java117
-rw-r--r--core/java/android/backup/BackupDataInputStream.java63
-rw-r--r--core/java/android/backup/BackupDataOutput.java54
-rw-r--r--core/java/android/backup/BackupHelper.java46
-rw-r--r--core/java/android/backup/BackupHelperAgent.java56
-rw-r--r--core/java/android/backup/BackupHelperDispatcher.java151
-rw-r--r--core/java/android/backup/BackupManager.java70
-rw-r--r--core/java/android/backup/FileBackupHelper.java69
-rw-r--r--core/java/android/backup/FileBackupHelperBase.java130
-rw-r--r--core/java/android/backup/IBackupManager.aidl100
-rw-r--r--core/java/android/backup/IRestoreObserver.aidl50
-rw-r--r--core/java/android/backup/IRestoreSession.aidl55
-rw-r--r--core/java/android/backup/RestoreSet.aidl19
-rw-r--r--core/java/android/backup/RestoreSet.java87
-rw-r--r--core/java/android/backup/SharedPreferencesBackupHelper.java41
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java25
-rw-r--r--core/java/android/bluetooth/HeadsetBase.java16
-rw-r--r--core/java/android/bluetooth/IBluetoothHeadset.aidl1
-rw-r--r--core/java/android/content/AbstractSyncableContentProvider.java170
-rw-r--r--core/java/android/content/AbstractTableMerger.java50
-rw-r--r--core/java/android/content/Context.java51
-rw-r--r--core/java/android/content/ContextWrapper.java18
-rwxr-xr-xcore/java/android/content/IIntentReceiver.aidl33
-rw-r--r--core/java/android/content/IIntentSender.aidl26
-rw-r--r--core/java/android/content/Intent.java435
-rw-r--r--core/java/android/content/IntentFilter.java1
-rw-r--r--core/java/android/content/IntentSender.aidl19
-rw-r--r--core/java/android/content/IntentSender.java255
-rw-r--r--core/java/android/content/SyncStorageEngine.java14
-rw-r--r--core/java/android/content/pm/ActivityInfo.java10
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java87
-rwxr-xr-xcore/java/android/content/pm/ConfigurationInfo.java30
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl18
-rw-r--r--core/java/android/content/pm/PackageManager.java43
-rw-r--r--core/java/android/content/pm/PackageParser.java188
-rw-r--r--core/java/android/content/res/AssetFileDescriptor.java88
-rw-r--r--core/java/android/content/res/AssetManager.java2
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java491
-rw-r--r--core/java/android/content/res/Configuration.java37
-rw-r--r--core/java/android/content/res/Resources.java98
-rw-r--r--core/java/android/database/BulkCursorToCursorAdaptor.java8
-rw-r--r--core/java/android/database/sqlite/SQLiteContentHelper.java92
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java12
-rwxr-xr-xcore/java/android/gesture/Gesture.java341
-rw-r--r--core/java/android/gesture/GestureConstants.java26
-rw-r--r--core/java/android/gesture/GestureLibraries.java143
-rw-r--r--core/java/android/gesture/GestureLibrary.java81
-rwxr-xr-xcore/java/android/gesture/GestureOverlayView.java793
-rw-r--r--core/java/android/gesture/GesturePoint.java46
-rw-r--r--core/java/android/gesture/GestureStore.java330
-rw-r--r--core/java/android/gesture/GestureStroke.java229
-rwxr-xr-xcore/java/android/gesture/GestureUtilities.java475
-rwxr-xr-xcore/java/android/gesture/Instance.java113
-rw-r--r--core/java/android/gesture/InstanceLearner.java88
-rwxr-xr-xcore/java/android/gesture/Learner.java83
-rw-r--r--core/java/android/gesture/OrientedBoundingBox.java85
-rwxr-xr-xcore/java/android/gesture/Prediction.java33
-rw-r--r--core/java/android/gesture/package.html5
-rw-r--r--core/java/android/hardware/Camera.java38
-rw-r--r--core/java/android/hardware/ISensorService.aidl4
-rw-r--r--core/java/android/hardware/SensorManager.java72
-rw-r--r--core/java/android/net/http/RequestHandle.java6
-rw-r--r--core/java/android/os/AsyncTask.java6
-rw-r--r--core/java/android/os/BatteryStats.java45
-rw-r--r--core/java/android/os/Build.java25
-rw-r--r--core/java/android/os/Bundle.java68
-rw-r--r--core/java/android/os/Debug.java15
-rw-r--r--core/java/android/os/MemoryFile.java192
-rw-r--r--core/java/android/os/Parcel.java68
-rw-r--r--core/java/android/os/Process.java30
-rw-r--r--core/java/android/pim/EventRecurrence.java14
-rw-r--r--core/java/android/preference/CheckBoxPreference.java34
-rw-r--r--core/java/android/preference/PreferenceScreen.java12
-rw-r--r--core/java/android/provider/Browser.java65
-rw-r--r--core/java/android/provider/CallLog.java20
-rw-r--r--core/java/android/provider/Checkin.java5
-rw-r--r--core/java/android/provider/Contacts.java43
-rw-r--r--core/java/android/provider/MediaStore.java23
-rw-r--r--core/java/android/provider/Settings.java446
-rw-r--r--core/java/android/provider/Telephony.java31
-rw-r--r--core/java/android/server/BluetoothDeviceService.java6
-rw-r--r--core/java/android/server/search/SearchManagerService.java289
-rw-r--r--core/java/android/server/search/SearchableInfo.java54
-rw-r--r--core/java/android/server/search/Searchables.java254
-rw-r--r--core/java/android/speech/IRecognitionListener.aidl17
-rw-r--r--core/java/android/speech/IRecognitionService.aidl3
-rw-r--r--core/java/android/speech/RecognitionResult.aidl19
-rw-r--r--core/java/android/speech/RecognitionResult.java162
-rw-r--r--core/java/android/speech/RecognitionServiceUtil.java12
-rwxr-xr-xcore/java/android/speech/tts/ITts.aidl63
-rwxr-xr-xcore/java/android/speech/tts/ITtsCallback.aidl27
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java719
-rw-r--r--core/java/android/syncml/pim/PropertyNode.java194
-rw-r--r--core/java/android/syncml/pim/VBuilderCollection.java100
-rw-r--r--core/java/android/syncml/pim/VDataBuilder.java293
-rw-r--r--core/java/android/syncml/pim/VParser.java17
-rw-r--r--core/java/android/syncml/pim/vcard/ContactStruct.java943
-rw-r--r--core/java/android/syncml/pim/vcard/VCardComposer.java35
-rw-r--r--core/java/android/syncml/pim/vcard/VCardDataBuilder.java442
-rw-r--r--core/java/android/syncml/pim/vcard/VCardEntryCounter.java63
-rw-r--r--core/java/android/syncml/pim/vcard/VCardNestedException.java27
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser_V21.java638
-rw-r--r--core/java/android/syncml/pim/vcard/VCardParser_V30.java111
-rw-r--r--core/java/android/syncml/pim/vcard/VCardSourceDetector.java140
-rw-r--r--core/java/android/test/AndroidTestCase.java72
-rw-r--r--core/java/android/test/InstrumentationTestCase.java24
-rw-r--r--core/java/android/text/LoginFilter.java41
-rw-r--r--core/java/android/text/TextUtils.java11
-rw-r--r--core/java/android/text/format/DateFormat.java88
-rw-r--r--core/java/android/text/format/DateUtils.java130
-rw-r--r--core/java/android/text/format/Formatter.java10
-rw-r--r--core/java/android/text/format/Time.java16
-rw-r--r--core/java/android/text/method/DialerKeyListener.java2
-rw-r--r--core/java/android/text/method/Touch.java6
-rw-r--r--core/java/android/util/CharsetUtils.java13
-rw-r--r--core/java/android/util/DisplayMetrics.java86
-rw-r--r--core/java/android/util/LongSparseArray.java342
-rw-r--r--core/java/android/view/GestureDetector.java5
-rw-r--r--core/java/android/view/MotionEvent.java186
-rw-r--r--core/java/android/view/SurfaceView.java75
-rw-r--r--core/java/android/view/VelocityTracker.java21
-rw-r--r--core/java/android/view/View.java484
-rw-r--r--core/java/android/view/ViewConfiguration.java25
-rw-r--r--core/java/android/view/ViewDebug.java92
-rw-r--r--core/java/android/view/ViewGroup.java114
-rw-r--r--core/java/android/view/ViewRoot.java381
-rw-r--r--core/java/android/view/Window.java19
-rw-r--r--core/java/android/view/WindowManager.java88
-rw-r--r--core/java/android/view/WindowManagerImpl.java1
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.aidl19
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java734
-rw-r--r--core/java/android/view/accessibility/AccessibilityEventSource.java52
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java198
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl39
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManagerClient.aidl29
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java29
-rw-r--r--core/java/android/webkit/BrowserFrame.java13
-rw-r--r--core/java/android/webkit/ByteArrayBuilder.java15
-rw-r--r--core/java/android/webkit/FrameLoader.java2
-rw-r--r--core/java/android/webkit/JWebCoreJavaBridge.java10
-rw-r--r--core/java/android/webkit/LoadListener.java36
-rw-r--r--core/java/android/webkit/TextDialog.java3
-rw-r--r--core/java/android/webkit/WebSettings.java54
-rw-r--r--core/java/android/webkit/WebView.java99
-rw-r--r--core/java/android/webkit/WebViewCore.java44
-rw-r--r--core/java/android/webkit/gears/AndroidRadioDataProvider.java10
-rw-r--r--core/java/android/widget/AbsListView.java152
-rw-r--r--core/java/android/widget/AdapterView.java43
-rw-r--r--core/java/android/widget/AlphabetIndexer.java2
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java31
-rw-r--r--core/java/android/widget/ArrayAdapter.java7
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java221
-rw-r--r--core/java/android/widget/CheckedTextView.java15
-rw-r--r--core/java/android/widget/CompoundButton.java22
-rw-r--r--core/java/android/widget/ExpandableListView.java5
-rw-r--r--core/java/android/widget/FastScroller.java40
-rw-r--r--core/java/android/widget/FrameLayout.java29
-rw-r--r--core/java/android/widget/HorizontalScrollView.java13
-rw-r--r--core/java/android/widget/ImageButton.java32
-rw-r--r--core/java/android/widget/ImageView.java6
-rw-r--r--core/java/android/widget/ListView.java113
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java2
-rw-r--r--core/java/android/widget/PopupWindow.java46
-rw-r--r--core/java/android/widget/ProgressBar.java11
-rw-r--r--core/java/android/widget/RelativeLayout.java598
-rw-r--r--core/java/android/widget/RemoteViews.java69
-rw-r--r--core/java/android/widget/ScrollView.java13
-rw-r--r--core/java/android/widget/SlidingDrawer.java26
-rw-r--r--core/java/android/widget/TabHost.java49
-rw-r--r--core/java/android/widget/TabWidget.java110
-rw-r--r--core/java/android/widget/TextView.java281
-rw-r--r--core/java/android/widget/Toast.java5
-rw-r--r--core/java/android/widget/VideoView.java11
-rw-r--r--core/java/android/widget/ViewSwitcher.java2
-rw-r--r--core/java/android/widget/ZoomButtonsController.java27
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl5
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl116
-rw-r--r--core/java/com/android/internal/backup/LocalTransport.java200
-rw-r--r--core/java/com/android/internal/backup/SystemBackupAgent.java35
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java446
-rw-r--r--core/java/com/android/internal/os/PowerProfile.java238
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java2
-rw-r--r--core/java/com/android/internal/util/BitwiseInputStream.java13
-rw-r--r--core/java/com/android/internal/util/BitwiseOutputStream.java7
-rw-r--r--core/java/com/google/android/net/GoogleHttpClient.java120
-rw-r--r--core/java/com/google/android/util/GoogleWebContentHelper.java13
218 files changed, 19494 insertions, 2943 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
new file mode 100644
index 0000000..a3456c7
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -0,0 +1,225 @@
+/*
+ * 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.accessibilityservice;
+
+import com.android.internal.os.HandlerCaller;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * An accessibility service runs in the background and receives callbacks by the system
+ * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
+ * in the user interface, for example, the focus has changed, a button has been clicked,
+ * etc.
+ * <p>
+ * An accessibility service extends this class and implements its abstract methods. Such
+ * a service is declared as any other service in an AndroidManifest.xml but it must also
+ * specify that it handles the "android.accessibilityservice.AccessibilityService"
+ * {@link android.content.Intent}. Following is an example of such a declaration:
+ * <p>
+ * <code>
+ * &lt;service android:name=".MyAccessibilityService"&gt;<br>
+ * &lt;intent-filter&gt;<br>
+ * &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;<br>
+ * &lt;/intent-filter&gt;<br>
+ * &lt;/service&gt;<br>
+ * </code>
+ * <p>
+ * The lifecycle of an accessibility service is managed exclusively by the system. Starting
+ * or stopping an accessibility service is triggered by an explicit user action through
+ * enabling or disabling it in the device settings. After the system binds to a service it
+ * calls {@link AccessibilityService#onServiceConnected()}. This method can be
+ * overriden by clients that want to perform post binding setup. An accessibility service
+ * is configured though setting an {@link AccessibilityServiceInfo} by calling
+ * {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. You can call this
+ * method any time to change the service configuration but it is good practice to do that
+ * in the overriden {@link AccessibilityService#onServiceConnected()}.
+ * <p>
+ * An accessibility service can be registered for events in specific packages to provide a
+ * specific type of feedback and is notified with a certain timeout after the last event
+ * of interest has been fired.
+ * <p>
+ * <b>Notification strategy</b>
+ * <p>
+ * For each feedback type only one accessibility service is notified. Services are notified
+ * in the order of registration. Hence, if two services are registered for the same
+ * feedback type in the same package the first one wins. It is possible however, to
+ * register a service as the default one for a given feedback type. In such a case this
+ * service is invoked if no other service was interested in the event. In other words, default
+ * services do not compete with other services and are notified last regardless of the
+ * registration order. This enables "generic" accessibility services that work reasonably
+ * well with most applications to coexist with "polished" ones that are targeted for
+ * specific applications.
+ * <p>
+ * <b>Event types</b>
+ * <p>
+ * {@link AccessibilityEvent#TYPE_VIEW_CLICKED}
+ * {@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}
+ * {@link AccessibilityEvent#TYPE_VIEW_FOCUSED}
+ * {@link AccessibilityEvent#TYPE_VIEW_SELECTED}
+ * {@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}
+ * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}
+ * {@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}
+ * <p>
+ * <b>Feedback types</b>
+ * <p>
+ * {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
+ * {@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}
+ * {@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}
+ * {@link AccessibilityServiceInfo#FEEDBACK_VISUAL}
+ * {@link AccessibilityServiceInfo#FEEDBACK_GENERIC}
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityServiceInfo
+ * @see android.view.accessibility.AccessibilityManager
+ *
+ * Note: The event notification timeout is useful to avoid propagating events to the client
+ * too frequently since this is accomplished via an expensive interprocess call.
+ * One can think of the timeout as a criteria to determine when event generation has
+ * settled down.
+ */
+public abstract class AccessibilityService extends Service {
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ */
+ public static final String SERVICE_INTERFACE =
+ "android.accessibilityservice.AccessibilityService";
+
+ private static final String LOG_TAG = "AccessibilityService";
+
+ private AccessibilityServiceInfo mInfo;
+
+ IAccessibilityServiceConnection mConnection;
+
+ /**
+ * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
+ *
+ * @param event An event.
+ */
+ public abstract void onAccessibilityEvent(AccessibilityEvent event);
+
+ /**
+ * Callback for interrupting the accessibility feedback.
+ */
+ public abstract void onInterrupt();
+
+ /**
+ * This method is a part of the {@link AccessibilityService} lifecycle and is
+ * called after the system has successfully bound to the service. If is
+ * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
+ *
+ * @see AccessibilityServiceInfo
+ * @see #setServiceInfo(AccessibilityServiceInfo)
+ */
+ protected void onServiceConnected() {
+
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} that describes this service.
+ * <p>
+ * Note: You can call this method any time but the info will be picked up after
+ * the system has bound to this service and when this method is called thereafter.
+ *
+ * @param info The info.
+ */
+ public final void setServiceInfo(AccessibilityServiceInfo info) {
+ mInfo = info;
+ sendServiceInfo();
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
+ * properly set and there is an {@link IAccessibilityServiceConnection} to the
+ * AccessibilityManagerService.
+ */
+ private void sendServiceInfo() {
+ if (mInfo != null && mConnection != null) {
+ try {
+ mConnection.setServiceInfo(mInfo);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
+ }
+ }
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IEventListenerWrapper(this);
+ }
+
+ /**
+ * Implements the internal {@link IEventListener} interface to convert
+ * incoming calls to it back to calls on an {@link AccessibilityService}.
+ */
+ class IEventListenerWrapper extends IEventListener.Stub
+ implements HandlerCaller.Callback {
+
+ private static final int DO_SET_SET_CONNECTION = 10;
+ private static final int DO_ON_INTERRUPT = 20;
+ private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
+
+ private final HandlerCaller mCaller;
+
+ private AccessibilityService mTarget;
+
+ public IEventListenerWrapper(AccessibilityService context) {
+ mTarget = context;
+ mCaller = new HandlerCaller(context, this);
+ }
+
+ public void setConnection(IAccessibilityServiceConnection connection) {
+ Message message = mCaller.obtainMessageO(DO_SET_SET_CONNECTION, connection);
+ mCaller.sendMessage(message);
+ }
+
+ public void onInterrupt() {
+ Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);
+ mCaller.sendMessage(message);
+ }
+
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event);
+ mCaller.sendMessage(message);
+ }
+
+ public void executeMessage(Message message) {
+ switch (message.what) {
+ case DO_ON_ACCESSIBILITY_EVENT :
+ AccessibilityEvent event = (AccessibilityEvent) message.obj;
+ mTarget.onAccessibilityEvent(event);
+ event.recycle();
+ return;
+ case DO_ON_INTERRUPT :
+ mTarget.onInterrupt();
+ return;
+ case DO_SET_SET_CONNECTION :
+ mConnection = ((IAccessibilityServiceConnection) message.obj);
+ mTarget.onServiceConnected();
+ return;
+ default :
+ Log.w(LOG_TAG, "Unknown message type " + message.what);
+ }
+ }
+ }
+}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl b/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl
new file mode 100644
index 0000000..1f5d385
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+parcelable AccessibilityServiceInfo;
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
new file mode 100644
index 0000000..4761f98
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -0,0 +1,145 @@
+/*
+ * 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.accessibilityservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class describes an {@link AccessibilityService}. The system
+ * notifies an {@link AccessibilityService} for
+ * {@link android.view.accessibility.AccessibilityEvent}s
+ * according to the information encapsulated in this class.
+ *
+ * @see AccessibilityService
+ * @see android.view.accessibility.AccessibilityEvent
+ */
+public class AccessibilityServiceInfo implements Parcelable {
+
+ /**
+ * Denotes spoken feedback.
+ */
+ public static final int FEEDBACK_SPOKEN = 0x0000001;
+
+ /**
+ * Denotes haptic feedback.
+ */
+ public static final int FEEDBACK_HAPTIC = 0x0000002;
+
+ /**
+ * Denotes audible (not spoken) feedback.
+ */
+ public static final int FEEDBACK_AUDIBLE = 0x0000004;
+
+ /**
+ * Denotes visual feedback.
+ */
+ public static final int FEEDBACK_VISUAL = 0x0000008;
+
+ /**
+ * Denotes generic feedback.
+ */
+ public static final int FEEDBACK_GENERIC = 0x0000010;
+
+ /**
+ * If an {@link AccessibilityService} is the default for a given type.
+ * Default service is invoked only if no package specific one exists. In case of
+ * more than one package specific service only the earlier registered is notified.
+ */
+ public static final int DEFAULT = 0x0000001;
+
+ /**
+ * The event types an {@link AccessibilityService} is interested in.
+ *
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_CLICKED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_FOCUSED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_SELECTED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_ACTIVITY_STARTED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED
+ * @see android.view.accessibility.AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED
+ */
+ public int eventTypes;
+
+ /**
+ * The package names an {@link AccessibilityService} is interested in. Setting
+ * to null is equivalent to all packages.
+ */
+ public String[] packageNames;
+
+ /**
+ * The feedback type an {@link AccessibilityService} provides.
+ *
+ * @see #FEEDBACK_AUDIBLE
+ * @see #FEEDBACK_GENERIC
+ * @see #FEEDBACK_HAPTIC
+ * @see #FEEDBACK_SPOKEN
+ * @see #FEEDBACK_VISUAL
+ */
+ public int feedbackType;
+
+ /**
+ * The timeout after the most recent event of a given type before an
+ * {@link AccessibilityService} is notified.
+ * <p>
+ * Note: The event notification timeout is useful to avoid propagating events to the client
+ * too frequently since this is accomplished via an expensive interprocess call.
+ * One can think of the timeout as a criteria to determine when event generation has
+ * settled down
+ */
+ public long notificationTimeout;
+
+ /**
+ * This field represents a set of flags used for configuring an
+ * {@link AccessibilityService}.
+ *
+ * @see #DEFAULT
+ */
+ public int flags;
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(eventTypes);
+ parcel.writeStringArray(packageNames);
+ parcel.writeInt(feedbackType);
+ parcel.writeLong(notificationTimeout);
+ parcel.writeInt(flags);
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
+ new Parcelable.Creator<AccessibilityServiceInfo>() {
+ public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = parcel.readInt();
+ info.packageNames = parcel.readStringArray();
+ info.feedbackType = parcel.readInt();
+ info.notificationTimeout = parcel.readLong();
+ info.flags = parcel.readInt();
+ return info;
+ }
+
+ public AccessibilityServiceInfo[] newArray(int size) {
+ return new AccessibilityServiceInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
new file mode 100644
index 0000000..7157def
--- /dev/null
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.accessibilityservice;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+
+/**
+ * Interface AccessibilityManagerService#Service implements, and passes to an
+ * AccessibilityService so it can dynamically configure how the system handles it.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityServiceConnection {
+
+ void setServiceInfo(in AccessibilityServiceInfo info);
+}
diff --git a/core/java/android/accessibilityservice/IEventListener.aidl b/core/java/android/accessibilityservice/IEventListener.aidl
new file mode 100644
index 0000000..5b849f1
--- /dev/null
+++ b/core/java/android/accessibilityservice/IEventListener.aidl
@@ -0,0 +1,34 @@
+/*
+** Copyright 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.accessibilityservice;
+
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * Top-level interface to accessibility service component (implemented in Service).
+ *
+ * @hide
+ */
+ oneway interface IEventListener {
+
+ void setConnection(in IAccessibilityServiceConnection connection);
+
+ void onAccessibilityEvent(in AccessibilityEvent event);
+
+ void onInterrupt();
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9b1f0f9..ca9632a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -16,11 +16,14 @@
package android.app;
+import com.android.internal.policy.PolicyManager;
+
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IIntentSender;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -32,11 +35,12 @@ import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.text.Selection;
import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.util.Config;
@@ -58,10 +62,10 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
-import com.android.internal.policy.PolicyManager;
-
import java.util.ArrayList;
import java.util.HashMap;
@@ -625,6 +629,8 @@ public class Activity extends ContextThemeWrapper
boolean mStartedActivity;
/*package*/ int mConfigChangeFlags;
/*package*/ Configuration mCurrentConfig;
+ private SearchManager mSearchManager;
+ private Bundle mSearchDialogState = null;
private Window mWindow;
@@ -785,6 +791,9 @@ public class Activity extends ContextThemeWrapper
protected void onCreate(Bundle savedInstanceState) {
mVisibleFromClient = mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, true);
+ // uses super.getSystemService() since this.getSystemService() looks at the
+ // mSearchManager field.
+ mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE);
mCalled = true;
}
@@ -802,9 +811,10 @@ public class Activity extends ContextThemeWrapper
// Also restore the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY);
+ Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY);
+ if (searchState != null) {
+ mSearchManager.restoreSearchDialog(searchState);
+ }
}
/**
@@ -854,13 +864,26 @@ public class Activity extends ContextThemeWrapper
final Integer dialogId = ids[i];
Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
if (dialogState != null) {
- final Dialog dialog = onCreateDialog(dialogId);
- dialog.onRestoreInstanceState(dialogState);
+ // Calling onRestoreInstanceState() below will invoke dispatchOnCreate
+ // so tell createDialog() not to do it, otherwise we get an exception
+ final Dialog dialog = createDialog(dialogId, false);
mManagedDialogs.put(dialogId, dialog);
+ onPrepareDialog(dialogId, dialog);
+ dialog.onRestoreInstanceState(dialogState);
}
}
}
+ private Dialog createDialog(Integer dialogId, boolean dispatchOnCreate) {
+ final Dialog dialog = onCreateDialog(dialogId);
+ if (dialog == null) {
+ throw new IllegalArgumentException("Activity#onCreateDialog did "
+ + "not create a dialog for id " + dialogId);
+ }
+ if (dispatchOnCreate) dialog.dispatchOnCreate(null);
+ return dialog;
+ }
+
private String savedDialogKeyFor(int key) {
return SAVED_DIALOG_KEY_PREFIX + key;
}
@@ -1010,9 +1033,11 @@ public class Activity extends ContextThemeWrapper
// Also save the state of a search dialog (if any)
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY);
+ // onPause() should always be called before this method, so mSearchManagerState
+ // should be up to date.
+ if (mSearchDialogState != null) {
+ outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState);
+ }
}
/**
@@ -1283,12 +1308,6 @@ public class Activity extends ContextThemeWrapper
}
}
}
-
- // also dismiss search dialog if showing
- // TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.stopSearch();
// close any cursors we are managing.
int numCursors = mManagedCursors.size();
@@ -1298,6 +1317,10 @@ public class Activity extends ContextThemeWrapper
c.mCursor.close();
}
}
+
+ // Clear any search state saved in performPause(). If the state may be needed in the
+ // future, it will have been saved by performSaveInstanceState()
+ mSearchDialogState = null;
}
/**
@@ -1321,9 +1344,7 @@ public class Activity extends ContextThemeWrapper
// also update search dialog if showing
// TODO more generic than just this manager
- SearchManager searchManager =
- (SearchManager) getSystemService(Context.SEARCH_SERVICE);
- searchManager.onConfigurationChanged(newConfig);
+ mSearchManager.onConfigurationChanged(newConfig);
if (mWindow != null) {
// Pass the configuration changed event to the window
@@ -2013,7 +2034,24 @@ public class Activity extends ContextThemeWrapper
}
return onTrackballEvent(ev);
}
-
+
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(getClass().getName());
+ event.setPackageName(getPackageName());
+
+ LayoutParams params = getWindow().getAttributes();
+ boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
+ (params.height == LayoutParams.FILL_PARENT);
+ event.setFullScreen(isFullScreen);
+
+ CharSequence title = getTitle();
+ if (!TextUtils.isEmpty(title)) {
+ event.getText().add(title);
+ }
+
+ return true;
+ }
+
/**
* Default implementation of
* {@link android.view.Window.Callback#onCreatePanelView}
@@ -2394,12 +2432,7 @@ public class Activity extends ContextThemeWrapper
}
Dialog dialog = mManagedDialogs.get(id);
if (dialog == null) {
- dialog = onCreateDialog(id);
- if (dialog == null) {
- throw new IllegalArgumentException("Activity#onCreateDialog did "
- + "not create a dialog for id " + id);
- }
- dialog.dispatchOnCreate(null);
+ dialog = createDialog(id, true);
mManagedDialogs.put(id, dialog);
}
@@ -2523,10 +2556,7 @@ public class Activity extends ContextThemeWrapper
*/
public void startSearch(String initialQuery, boolean selectInitialQuery,
Bundle appSearchData, boolean globalSearch) {
- // activate the search manager and start it up!
- SearchManager searchManager = (SearchManager)
- getSystemService(Context.SEARCH_SERVICE);
- searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+ mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
appSearchData, globalSearch);
}
@@ -3245,6 +3275,8 @@ public class Activity extends ContextThemeWrapper
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
+ } else if (SEARCH_SERVICE.equals(name)) {
+ return mSearchManager;
}
return super.getSystemService(name);
}
@@ -3543,10 +3575,21 @@ public class Activity extends ContextThemeWrapper
"Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()");
}
+
+ // restore search dialog, if any
+ if (mSearchDialogState != null) {
+ mSearchManager.restoreSearchDialog(mSearchDialogState);
+ }
+ mSearchDialogState = null;
}
final void performPause() {
onPause();
+
+ // save search dialog state if the search dialog is open,
+ // and then dismiss the search dialog
+ mSearchDialogState = mSearchManager.saveSearchDialog();
+ mSearchManager.stopSearch();
}
final void performUserLeaving() {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 541f413..dfa8139 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -17,9 +17,11 @@
package android.app;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IIntentSender;
+import android.content.IIntentReceiver;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.res.Configuration;
@@ -984,7 +986,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String process = data.readString();
boolean start = data.readInt() != 0;
String path = data.readString();
- boolean res = profileControl(process, start, path);
+ ParcelFileDescriptor fd = data.readInt() != 0
+ ? data.readFileDescriptor() : null;
+ boolean res = profileControl(process, start, path, fd);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -998,6 +1002,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case STOP_APP_SWITCHES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ stopAppSwitches();
+ reply.writeNoException();
+ return true;
+ }
+
+ case RESUME_APP_SWITCHES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ resumeAppSwitches();
+ reply.writeNoException();
+ return true;
+ }
+
case PEEK_SERVICE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
Intent service = Intent.CREATOR.createFromParcel(data);
@@ -1007,6 +1025,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeStrongBinder(binder);
return true;
}
+
+ case START_BACKUP_AGENT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data);
+ int backupRestoreMode = data.readInt();
+ boolean success = bindBackupAgent(info, backupRestoreMode);
+ reply.writeNoException();
+ reply.writeInt(success ? 1 : 0);
+ return true;
+ }
+
+ case BACKUP_AGENT_CREATED_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ String packageName = data.readString();
+ IBinder agent = data.readStrongBinder();
+ backupAgentCreated(packageName, agent);
+ reply.writeNoException();
+ return true;
+ }
+
+ case UNBIND_BACKUP_AGENT_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ ApplicationInfo info = ApplicationInfo.CREATOR.createFromParcel(data);
+ unbindBackupAgent(info);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -1667,6 +1712,43 @@ class ActivityManagerProxy implements IActivityManager
return binder;
}
+ public boolean bindBackupAgent(ApplicationInfo app, int backupRestoreMode)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ app.writeToParcel(data, 0);
+ data.writeInt(backupRestoreMode);
+ mRemote.transact(START_BACKUP_AGENT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean success = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return success;
+ }
+
+ public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeString(packageName);
+ data.writeStrongBinder(agent);
+ mRemote.transact(BACKUP_AGENT_CREATED_TRANSACTION, data, reply, 0);
+ reply.recycle();
+ data.recycle();
+ }
+
+ public void unbindBackupAgent(ApplicationInfo app) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ app.writeToParcel(data, 0);
+ mRemote.transact(UNBIND_BACKUP_AGENT_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
throws RemoteException {
@@ -2152,7 +2234,7 @@ class ActivityManagerProxy implements IActivityManager
}
public boolean profileControl(String process, boolean start,
- String path) throws RemoteException
+ String path, ParcelFileDescriptor fd) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2160,6 +2242,12 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(process);
data.writeInt(start ? 1 : 0);
data.writeString(path);
+ if (fd != null) {
+ data.writeInt(1);
+ fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(PROFILE_CONTROL_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
@@ -2182,5 +2270,25 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public void stopAppSwitches() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(STOP_APP_SWITCHES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
+ public void resumeAppSwitches() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(RESUME_APP_SWITCHES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1e15d14..5ee29ac 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -23,6 +23,7 @@ import android.content.ContentProvider;
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;
@@ -31,6 +32,7 @@ import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.PackageParser.Component;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -46,6 +48,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
+import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -72,6 +75,7 @@ import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -115,6 +119,7 @@ public final class ActivityThread {
private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
private static final boolean DEBUG_BROADCAST = false;
private static final boolean DEBUG_RESULTS = false;
+ private static final boolean DEBUG_BACKUP = true;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
@@ -161,7 +166,7 @@ public final class ActivityThread {
return metrics;
}
- Resources getTopLevelResources(String appDir, float applicationScale) {
+ Resources getTopLevelResources(String appDir, PackageInfo pkgInfo) {
synchronized (mPackages) {
//Log.w(TAG, "getTopLevelResources: " + appDir);
WeakReference<Resources> wr = mActiveResources.get(appDir);
@@ -180,23 +185,17 @@ public final class ActivityThread {
if (assets.addAssetPath(appDir) == 0) {
return null;
}
- DisplayMetrics metrics = getDisplayMetricsLocked(false);
- // density used to load resources
- // scaledDensity is calculated in Resources constructor
- //
- boolean usePreloaded = true;
-
- // TODO: use explicit flag to indicate the compatibility mode.
- if (applicationScale != 1.0f) {
- usePreloaded = false;
- DisplayMetrics newMetrics = new DisplayMetrics();
- newMetrics.setTo(metrics);
- float newDensity = metrics.density / applicationScale;
- newMetrics.updateDensity(newDensity);
- metrics = newMetrics;
+ ApplicationInfo appInfo;
+ try {
+ appInfo = getPackageManager().getApplicationInfo(
+ pkgInfo.getPackageName(),
+ PackageManager.GET_SUPPORTS_DENSITIES);
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
}
//Log.i(TAG, "Resource:" + appDir + ", display metrics=" + metrics);
- r = new Resources(assets, metrics, getConfiguration(), usePreloaded);
+ DisplayMetrics metrics = getDisplayMetricsLocked(false);
+ r = new Resources(assets, metrics, getConfiguration(), appInfo);
//Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration());
// XXX need to remove entries when weak references go away
mActiveResources.put(appDir, new WeakReference<Resources>(r));
@@ -224,7 +223,6 @@ public final class ActivityThread {
private Resources mResources;
private ClassLoader mClassLoader;
private Application mApplication;
- private float mApplicationScale;
private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
= new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>();
@@ -267,8 +265,6 @@ public final class ActivityThread {
mClassLoader = mSystemContext.getClassLoader();
mResources = mSystemContext.getResources();
}
-
- mApplicationScale = -1.0f;
}
public PackageInfo(ActivityThread activityThread, String name,
@@ -287,56 +283,20 @@ public final class ActivityThread {
mIncludeCode = true;
mClassLoader = systemContext.getClassLoader();
mResources = systemContext.getResources();
- mApplicationScale = systemContext.getApplicationScale();
}
public String getPackageName() {
return mPackageName;
}
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
public boolean isSecurityViolation() {
return mSecurityViolation;
}
- public float getApplicationScale() {
- if (mApplicationScale > 0.0f) {
- return mApplicationScale;
- }
- DisplayMetrics metrics = mActivityThread.getDisplayMetricsLocked(false);
- // Find out the density scale (relative to 160) of the supported density that
- // is closest to the system's density.
- try {
- ApplicationInfo ai = getPackageManager().getApplicationInfo(
- mPackageName, PackageManager.GET_SUPPORTS_DENSITIES);
-
- float appScale = -1.0f;
- if (ai.supportsDensities != null) {
- int minDiff = Integer.MAX_VALUE;
- for (int density : ai.supportsDensities) {
- int tmpDiff = (int) Math.abs(DisplayMetrics.DEVICE_DENSITY - density);
- if (tmpDiff == 0) {
- appScale = 1.0f;
- break;
- }
- // prefer higher density (appScale>1.0), unless that's only option.
- if (tmpDiff < minDiff && appScale < 1.0f) {
- appScale = DisplayMetrics.DEVICE_DENSITY / density;
- minDiff = tmpDiff;
- }
- }
- }
- if (appScale < 0.0f) {
- mApplicationScale = metrics.density;
- } else {
- mApplicationScale = appScale;
- }
- } catch (RemoteException e) {
- throw new AssertionError(e);
- }
- if (localLOGV) Log.v(TAG, "appScale=" + mApplicationScale + ", pkg=" + mPackageName);
- return mApplicationScale;
- }
-
/**
* Gets the array of shared libraries that are listed as
* used by the given package.
@@ -494,12 +454,12 @@ public final class ActivityThread {
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
- mResources = mainThread.getTopLevelResources(mResDir, getApplicationScale());
+ mResources = mainThread.getTopLevelResources(mResDir, this);
}
return mResources;
}
- public Application makeApplication() {
+ public Application makeApplication(boolean forceDefaultAppClass) {
if (mApplication != null) {
return mApplication;
}
@@ -507,7 +467,7 @@ public final class ActivityThread {
Application app = null;
String appClass = mApplicationInfo.className;
- if (appClass == null) {
+ if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
@@ -1199,6 +1159,16 @@ public final class ActivityThread {
}
}
+ private static final class CreateBackupAgentData {
+ ApplicationInfo appInfo;
+ int backupMode;
+ public String toString() {
+ return "CreateBackupAgentData{appInfo=" + appInfo
+ + " backupAgent=" + appInfo.backupAgentName
+ + " mode=" + backupMode + "}";
+ }
+ }
+
private static final class CreateServiceData {
IBinder token;
ServiceInfo info;
@@ -1239,6 +1209,7 @@ public final class ActivityThread {
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
int debugMode;
+ boolean restrictedBackupMode;
Configuration config;
boolean handlingProfiling;
public String toString() {
@@ -1267,6 +1238,11 @@ public final class ActivityThread {
String who;
}
+ private static final class ProfilerControlData {
+ String path;
+ ParcelFileDescriptor fd;
+ }
+
private final class ApplicationThread extends ApplicationThreadNative {
private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
private static final String ONE_COUNT_COLUMN = "%17s %8d";
@@ -1374,6 +1350,21 @@ public final class ActivityThread {
queueOrSendMessage(H.RECEIVER, r);
}
+ public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+ d.backupMode = backupMode;
+
+ queueOrSendMessage(H.CREATE_BACKUP_AGENT, d);
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app) {
+ CreateBackupAgentData d = new CreateBackupAgentData();
+ d.appInfo = app;
+
+ queueOrSendMessage(H.DESTROY_BACKUP_AGENT, d);
+ }
+
public final void scheduleCreateService(IBinder token,
ServiceInfo info) {
CreateServiceData s = new CreateServiceData();
@@ -1419,7 +1410,7 @@ public final class ActivityThread {
ApplicationInfo appInfo, List<ProviderInfo> providers,
ComponentName instrumentationName, String profileFile,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, Configuration config,
+ int debugMode, boolean isRestrictedBackupMode, Configuration config,
Map<String, IBinder> services) {
Process.setArgV0(processName);
@@ -1437,6 +1428,7 @@ public final class ActivityThread {
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.debugMode = debugMode;
+ data.restrictedBackupMode = isRestrictedBackupMode;
data.config = config;
queueOrSendMessage(H.BIND_APPLICATION, data);
}
@@ -1509,10 +1501,25 @@ public final class ActivityThread {
}
}
- public void profilerControl(boolean start, String path) {
- queueOrSendMessage(H.PROFILER_CONTROL, path, start ? 1 : 0);
+ public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) {
+ ProfilerControlData pcd = new ProfilerControlData();
+ pcd.path = path;
+ pcd.fd = fd;
+ queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0);
+ }
+
+ public void setSchedulingGroup(int group) {
+ // Note: do this immediately, since going into the foreground
+ // should happen regardless of what pending work we have to do
+ // and the activity manager will wait for us to report back that
+ // we are done before sending us to the background.
+ try {
+ Process.setProcessGroup(Process.myPid(), group);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed setting process group to " + group, e);
+ }
}
-
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
long nativeMax = Debug.getNativeHeapSize() / 1024;
@@ -1706,6 +1713,8 @@ public final class ActivityThread {
public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
+ public static final int CREATE_BACKUP_AGENT = 128;
+ public static final int DESTROY_BACKUP_AGENT = 129;
String codeToString(int code) {
if (localLOGV) {
switch (code) {
@@ -1737,6 +1746,8 @@ public final class ActivityThread {
case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
+ case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
+ case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
}
}
return "(unknown)";
@@ -1837,7 +1848,13 @@ public final class ActivityThread {
handleActivityConfigurationChanged((IBinder)msg.obj);
break;
case PROFILER_CONTROL:
- handleProfilerControl(msg.arg1 != 0, (String)msg.obj);
+ handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj);
+ break;
+ case CREATE_BACKUP_AGENT:
+ handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
+ break;
+ case DESTROY_BACKUP_AGENT:
+ handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
break;
}
}
@@ -1896,6 +1913,8 @@ public final class ActivityThread {
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;
@@ -2079,6 +2098,10 @@ public final class ActivityThread {
return mInitialApplication;
}
+ public String getProcessName() {
+ return mBoundApplication.processName;
+ }
+
public ApplicationContext getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
@@ -2257,7 +2280,7 @@ public final class ActivityThread {
}
try {
- Application app = r.packageInfo.makeApplication();
+ Application app = r.packageInfo.makeApplication(false);
if (localLOGV) Log.v(TAG, "Performing launch of " + r);
if (localLOGV) Log.v(
@@ -2452,7 +2475,7 @@ public final class ActivityThread {
}
try {
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
if (localLOGV) Log.v(
TAG, "Performing receive of " + data.intent
@@ -2495,6 +2518,85 @@ public final class ActivityThread {
}
}
+ // Instantiate a BackupAgent and tell it that it's alive
+ private final void handleCreateBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleCreateBackupAgent: " + data);
+
+ // no longer idle; we have backup work to do
+ unscheduleGcIdler();
+
+ // instantiate the BackupAgent class named in the manifest
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ if (mBackupAgents.get(packageName) != null) {
+ Log.d(TAG, "BackupAgent " + " for " + packageName
+ + " already exists");
+ return;
+ }
+
+ BackupAgent agent = null;
+ String classname = data.appInfo.backupAgentName;
+ if (classname == null) {
+ if (data.backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL) {
+ Log.e(TAG, "Attempted incremental backup but no defined agent for "
+ + packageName);
+ return;
+ }
+ classname = "android.app.FullBackupAgent";
+ }
+ try {
+ java.lang.ClassLoader cl = packageInfo.getClassLoader();
+ agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to instantiate backup agent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+
+ // set up the agent's context
+ try {
+ if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent "
+ + data.appInfo.backupAgentName);
+
+ ApplicationContext context = new ApplicationContext();
+ context.init(packageInfo, null, this);
+ context.setOuterContext(agent);
+ agent.attach(context);
+ agent.onCreate();
+
+ // tell the OS that we're live now
+ IBinder binder = agent.onBind();
+ try {
+ ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder);
+ } catch (RemoteException e) {
+ // nothing to do.
+ }
+ mBackupAgents.put(packageName, agent);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to create BackupAgent "
+ + data.appInfo.backupAgentName + ": " + e.toString(), e);
+ }
+ }
+
+ // Tear down a BackupAgent
+ private final void handleDestroyBackupAgent(CreateBackupAgentData data) {
+ if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data);
+
+ PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo);
+ String packageName = packageInfo.mPackageName;
+ BackupAgent agent = mBackupAgents.get(packageName);
+ if (agent != null) {
+ try {
+ agent.onDestroy();
+ } catch (Exception e) {
+ Log.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
+ e.printStackTrace();
+ }
+ mBackupAgents.remove(packageName);
+ } else {
+ Log.w(TAG, "Attempt to destroy unknown backup agent " + data);
+ }
+ }
+
private final void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2520,7 +2622,7 @@ public final class ActivityThread {
ApplicationContext context = new ApplicationContext();
context.init(packageInfo, null, this);
- Application app = packageInfo.makeApplication();
+ Application app = packageInfo.makeApplication(false);
context.setOuterContext(service);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
@@ -3134,7 +3236,7 @@ public final class ActivityThread {
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
- "Activity " + r.intent.getComponent().toShortString()
+ "Activity " + safeToComponentShortString(r.intent)
+ " did not call through to super.onPause()");
}
} catch (SuperNotCalledException e) {
@@ -3143,7 +3245,7 @@ public final class ActivityThread {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to pause activity "
- + r.intent.getComponent().toShortString()
+ + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3158,7 +3260,7 @@ public final class ActivityThread {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to stop activity "
- + r.intent.getComponent().toShortString()
+ + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3183,7 +3285,7 @@ public final class ActivityThread {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to retain child activities "
- + r.intent.getComponent().toShortString()
+ + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3194,7 +3296,7 @@ public final class ActivityThread {
r.activity.onDestroy();
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
- "Activity " + r.intent.getComponent().toShortString() +
+ "Activity " + safeToComponentShortString(r.intent) +
" did not call through to super.onDestroy()");
}
if (r.window != null) {
@@ -3205,8 +3307,7 @@ public final class ActivityThread {
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
- "Unable to destroy activity "
- + r.intent.getComponent().toShortString()
+ "Unable to destroy activity " + safeToComponentShortString(r.intent)
+ ": " + e.toString(), e);
}
}
@@ -3216,6 +3317,11 @@ public final class ActivityThread {
return r;
}
+ private static String safeToComponentShortString(Intent intent) {
+ ComponentName component = intent.getComponent();
+ return component == null ? "[Unknown]" : component.toShortString();
+ }
+
private final void handleDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
ActivityRecord r = performDestroyActivity(token, finishing,
@@ -3475,8 +3581,6 @@ public final class ActivityThread {
}
mConfiguration.updateFrom(config);
DisplayMetrics dm = getDisplayMetricsLocked(true);
- DisplayMetrics appDm = new DisplayMetrics();
- appDm.setTo(dm);
// set it for java, this also affects newly created Resources
if (config.locale != null) {
@@ -3496,11 +3600,7 @@ public final class ActivityThread {
WeakReference<Resources> v = it.next();
Resources r = v.get();
if (r != null) {
- // keep the original density based on application cale.
- appDm.updateDensity(r.getDisplayMetrics().density);
- r.updateConfiguration(config, appDm);
- // reset
- appDm.setTo(dm);
+ r.updateConfiguration(config, dm);
//Log.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -3528,15 +3628,20 @@ public final class ActivityThread {
performConfigurationChanged(r.activity, mConfiguration);
}
- final void handleProfilerControl(boolean start, String path) {
+ final void handleProfilerControl(boolean start, ProfilerControlData pcd) {
if (start) {
- File file = new File(path);
- file.getParentFile().mkdirs();
try {
- Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+ Debug.startMethodTracing(pcd.path, pcd.fd.getFileDescriptor(),
+ 8 * 1024 * 1024, 0);
} catch (RuntimeException e) {
- Log.w(TAG, "Profiling failed on path " + path
+ Log.w(TAG, "Profiling failed on path " + pcd.path
+ " -- can the process access this path?");
+ } finally {
+ try {
+ pcd.fd.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failure closing profile fd", e);
+ }
}
} else {
Debug.stopMethodTracing();
@@ -3592,6 +3697,13 @@ public final class ActivityThread {
*/
Locale.setDefault(data.config.locale);
+ /*
+ * Update the system configuration since its preloaded and might not
+ * reflect configuration changes. The configuration object passed
+ * in AppBindData can be safely assumed to be up to date
+ */
+ Resources.getSystem().updateConfiguration(mConfiguration, null);
+
data.info = getPackageInfoNoCheck(data.appInfo);
if (data.debugMode != IApplicationThread.DEBUG_OFF) {
@@ -3682,7 +3794,9 @@ public final class ActivityThread {
mInstrumentation = new Instrumentation();
}
- Application app = data.info.makeApplication();
+ // If the app is being launched for full backup or restore, bring it up in
+ // a restricted environment with the base application class.
+ Application app = data.info.makeApplication(data.restrictedBackupMode);
mInitialApplication = app;
List<ProviderInfo> providers = data.providers;
@@ -3867,7 +3981,10 @@ public final class ActivityThread {
ProviderRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
Log.i(TAG, "Removing dead content provider: " + name);
- mProviderMap.remove(name);
+ ProviderRecord removed = mProviderMap.remove(name);
+ if (removed != null) {
+ removed.mProvider.asBinder().unlinkToDeath(removed, 0);
+ }
}
}
}
@@ -3876,7 +3993,10 @@ public final class ActivityThread {
ProviderRecord pr = mProviderMap.get(name);
if (pr.mProvider.asBinder() == provider.asBinder()) {
Log.i(TAG, "Removing dead content provider: " + name);
- mProviderMap.remove(name);
+ ProviderRecord removed = mProviderMap.remove(name);
+ if (removed != null) {
+ removed.mProvider.asBinder().unlinkToDeath(removed, 0);
+ }
}
}
diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java
index bb17dc3..38ea686 100644
--- a/core/java/android/app/ApplicationContext.java
+++ b/core/java/android/app/ApplicationContext.java
@@ -16,8 +16,11 @@
package android.app;
-import com.google.android.collect.Maps;
+import com.android.internal.policy.PolicyManager;
import com.android.internal.util.XmlUtils;
+import com.google.android.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParserException;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothDevice;
@@ -29,6 +32,8 @@ import android.content.ContextWrapper;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IIntentReceiver;
+import android.content.IntentSender;
import android.content.ReceiverCallNotAllowedException;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
@@ -37,9 +42,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageStatsObserver;
import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
+import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -68,29 +73,30 @@ import android.net.wifi.IWifiManager;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.IPowerManager;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Vibrator;
import android.os.FileUtils.FileStatus;
import android.telephony.TelephonyManager;
import android.text.ClipboardManager;
import android.util.AndroidRuntimeException;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.LayoutInflater;
import android.view.WindowManagerImpl;
+import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputMethodManager;
-import com.android.internal.policy.PolicyManager;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -100,16 +106,14 @@ import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.WeakHashMap;
import java.util.Set;
-import java.util.HashSet;
+import java.util.WeakHashMap;
import java.util.Map.Entry;
-import org.xmlpull.v1.XmlPullParserException;
-
class ReceiverRestrictedContext extends ContextWrapper {
ReceiverRestrictedContext(Context base) {
super(base);
@@ -147,6 +151,7 @@ class ReceiverRestrictedContext extends ContextWrapper {
*/
class ApplicationContext extends Context {
private final static String TAG = "ApplicationContext";
+ private final static boolean DEBUG = false;
private final static boolean DEBUG_ICONS = false;
private static final Object sSync = new Object();
@@ -172,6 +177,7 @@ class ApplicationContext extends Context {
private Resources.Theme mTheme = null;
private PackageManager mPackageManager;
private NotificationManager mNotificationManager = null;
+ private AccessibilityManager mAccessibilityManager = null;
private ActivityManager mActivityManager = null;
private Context mReceiverRestrictedContext = null;
private SearchManager mSearchManager = null;
@@ -181,6 +187,7 @@ class ApplicationContext extends Context {
private StatusBarManager mStatusBarManager = null;
private TelephonyManager mTelephonyManager = null;
private ClipboardManager mClipboardManager = null;
+ private boolean mRestricted;
private final Object mSync = new Object();
@@ -280,6 +287,14 @@ class ApplicationContext extends Context {
}
@Override
+ public ApplicationInfo getApplicationInfo() {
+ if (mPackageInfo != null) {
+ return mPackageInfo.getApplicationInfo();
+ }
+ throw new RuntimeException("Not supported in system context");
+ }
+
+ @Override
public String getPackageResourcePath() {
if (mPackageInfo != null) {
return mPackageInfo.getResDir();
@@ -299,10 +314,14 @@ class ApplicationContext extends Context {
return new File(prefsFile.getPath() + ".bak");
}
+ public File getSharedPrefsFile(String name) {
+ return makeFilename(getPreferencesDir(), name + ".xml");
+ }
+
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
- File f = makeFilename(getPreferencesDir(), name + ".xml");
+ File f = getSharedPrefsFile(name);
synchronized (sSharedPrefs) {
sp = sSharedPrefs.get(f);
if (sp != null && !sp.hasFileChanged()) {
@@ -550,19 +569,6 @@ class ApplicationContext extends Context {
}
}
- /**
- * @hide
- */
- @Override
- public float getApplicationScale() {
- if (mPackageInfo != null) {
- return mPackageInfo.getApplicationScale();
- } else {
- // same as system density
- return 1.0f;
- }
- }
-
@Override
public void setWallpaper(Bitmap bitmap) throws IOException {
try {
@@ -904,6 +910,8 @@ class ApplicationContext extends Context {
return getNotificationManager();
} else if (KEYGUARD_SERVICE.equals(name)) {
return new KeyguardManager();
+ } else if (ACCESSIBILITY_SERVICE.equals(name)) {
+ return AccessibilityManager.getInstance(this);
} else if (LOCATION_SERVICE.equals(name)) {
return getLocationManager();
} else if (SEARCH_SERVICE.equals(name)) {
@@ -1033,11 +1041,6 @@ class ApplicationContext extends Context {
}
private SearchManager getSearchManager() {
- // This is only useable in Activity Contexts
- if (getActivityToken() == null) {
- throw new AndroidRuntimeException(
- "Acquiring SearchManager objects only valid in Activity Contexts.");
- }
synchronized (mSync) {
if (mSearchManager == null) {
mSearchManager = new SearchManager(getOuterContext(), mMainThread.getHandler());
@@ -1238,7 +1241,7 @@ class ApplicationContext extends Context {
@Override
public int checkUriPermission(Uri uri, String readPermission,
String writePermission, int pid, int uid, int modeFlags) {
- if (false) {
+ if (DEBUG) {
Log.i("foo", "checkUriPermission: uri=" + uri + "readPermission="
+ readPermission + " writePermission=" + writePermission
+ " pid=" + pid + " uid=" + uid + " mode" + modeFlags);
@@ -1337,8 +1340,22 @@ class ApplicationContext extends Context {
mMainThread.getPackageInfo(packageName, flags);
if (pi != null) {
ApplicationContext c = new ApplicationContext();
+ c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
c.init(pi, null, mMainThread);
if (c.mResources != null) {
+ Resources newRes = c.mResources;
+ if (mResources.getCompatibilityInfo().applicationScale !=
+ newRes.getCompatibilityInfo().applicationScale) {
+ DisplayMetrics dm = mMainThread.getDisplayMetricsLocked(false);
+ c.mResources = new Resources(newRes.getAssets(), dm,
+ newRes.getConfiguration(),
+ mResources.getCompatibilityInfo().copy());
+ if (DEBUG) {
+ Log.d(TAG, "loaded context has different scaling. Using container's" +
+ " compatiblity info:" + mResources.getDisplayMetrics());
+ }
+
+ }
return c;
}
}
@@ -1348,6 +1365,11 @@ class ApplicationContext extends Context {
"Application package " + packageName + " not found");
}
+ @Override
+ public boolean isRestricted() {
+ return mRestricted;
+ }
+
private File getDataDirFile() {
if (mPackageInfo != null) {
return mPackageInfo.getDataDirFile();
@@ -1453,7 +1475,7 @@ class ApplicationContext extends Context {
if ((mode&MODE_WORLD_WRITEABLE) != 0) {
perms |= FileUtils.S_IWOTH;
}
- if (false) {
+ if (DEBUG) {
Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode)
+ ", perms=0x" + Integer.toHexString(perms));
}
@@ -1516,43 +1538,33 @@ class ApplicationContext extends Context {
throw new NameNotFoundException(packageName);
}
- public Intent getLaunchIntentForPackage(String packageName)
- throws NameNotFoundException {
+ @Override
+ public Intent getLaunchIntentForPackage(String packageName) {
// First see if the package has an INFO activity; the existence of
// such an activity is implied to be the desired front-door for the
// overall package (such as if it has multiple launcher entries).
- Intent intent = getLaunchIntentForPackageCategory(this, packageName,
- Intent.CATEGORY_INFO);
- if (intent != null) {
- return intent;
- }
-
+ Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+ intentToResolve.addCategory(Intent.CATEGORY_INFO);
+ intentToResolve.setPackage(packageName);
+ ResolveInfo resolveInfo = resolveActivity(intentToResolve, 0);
+
// Otherwise, try to find a main launcher activity.
- return getLaunchIntentForPackageCategory(this, packageName,
- Intent.CATEGORY_LAUNCHER);
- }
-
- // XXX This should be implemented as a call to the package manager,
- // to reduce the work needed.
- static Intent getLaunchIntentForPackageCategory(PackageManager pm,
- String packageName, String category) {
+ if (resolveInfo == null) {
+ // reuse the intent instance
+ intentToResolve.removeCategory(Intent.CATEGORY_INFO);
+ intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+ intentToResolve.setPackage(packageName);
+ resolveInfo = resolveActivity(intentToResolve, 0);
+ }
+ if (resolveInfo == null) {
+ return null;
+ }
Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(packageName, resolveInfo.activityInfo.name);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Intent intentToResolve = new Intent(Intent.ACTION_MAIN, null);
- intentToResolve.addCategory(category);
- final List<ResolveInfo> apps =
- pm.queryIntentActivities(intentToResolve, 0);
- // I wish there were a way to directly get the "main" activity of a
- // package but ...
- for (ResolveInfo app : apps) {
- if (app.activityInfo.packageName.equals(packageName)) {
- intent.setClassName(packageName, app.activityInfo.name);
- return intent;
- }
- }
- return null;
+ return intent;
}
-
+
@Override
public int[] getPackageGids(String packageName)
throws NameNotFoundException {
@@ -2024,8 +2036,7 @@ class ApplicationContext extends Context {
ActivityThread.PackageInfo pi = mContext.mMainThread.getPackageInfoNoCheck(app);
Resources r = mContext.mMainThread.getTopLevelResources(
app.uid == Process.myUid() ? app.sourceDir
- : app.publicSourceDir,
- pi.getApplicationScale());
+ : app.publicSourceDir, pi);
if (r != null) {
return r;
}
@@ -2363,11 +2374,11 @@ class ApplicationContext extends Context {
// Should never happen!
}
}
-
+
@Override
- public void freeStorage(long idealStorageSize, PendingIntent opFinishedIntent) {
+ public void freeStorage(long freeStorageSize, IntentSender pi) {
try {
- mPM.freeStorage(idealStorageSize, opFinishedIntent);
+ mPM.freeStorage(freeStorageSize, pi);
} catch (RemoteException e) {
// Should never happen!
}
@@ -2421,6 +2432,16 @@ class ApplicationContext extends Context {
}
@Override
+ public void replacePreferredActivity(IntentFilter filter,
+ int match, ComponentName[] set, ComponentName activity) {
+ try {
+ mPM.replacePreferredActivity(filter, match, set, activity);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
public void clearPackagePreferredActivities(String packageName) {
try {
mPM.clearPackagePreferredActivities(packageName);
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
new file mode 100644
index 0000000..6b17236
--- /dev/null
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -0,0 +1,308 @@
+/*
+ * 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.app;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Printer;
+
+/**
+ * Describes an application error.
+ *
+ * A report has a type, which is one of
+ * <ul>
+ * <li> {@link #TYPE_CRASH} application crash. Information about the crash
+ * is stored in {@link #crashInfo}.
+ * <li> {@link #TYPE_ANR} application not responding. Information about the
+ * ANR is stored in {@link #anrInfo}.
+ * <li> {@link #TYPE_NONE} uninitialized instance of {@link ApplicationErrorReport}.
+ * </ul>
+ *
+ * @hide
+ */
+
+public class ApplicationErrorReport implements Parcelable {
+ /**
+ * Uninitialized error report.
+ */
+ public static final int TYPE_NONE = 0;
+
+ /**
+ * An error report about an application crash.
+ */
+ public static final int TYPE_CRASH = 1;
+
+ /**
+ * An error report about an application that's not responding.
+ */
+ public static final int TYPE_ANR = 2;
+
+ /**
+ * Type of this report. Can be one of {@link #TYPE_NONE},
+ * {@link #TYPE_CRASH} or {@link #TYPE_ANR}.
+ */
+ public int type;
+
+ /**
+ * Package name of the application.
+ */
+ public String packageName;
+
+ /**
+ * Package name of the application which installed the application this
+ * report pertains to.
+ * This identifies which Market the application came from.
+ */
+ public String installerPackageName;
+
+ /**
+ * Process name of the application.
+ */
+ public String processName;
+
+ /**
+ * Time at which the error occurred.
+ */
+ public long time;
+
+ /**
+ * If this report is of type {@link #TYPE_CRASH}, contains an instance
+ * of CrashInfo describing the crash; otherwise null.
+ */
+ public CrashInfo crashInfo;
+
+ /**
+ * If this report is of type {@link #TYPE_ANR}, contains an instance
+ * of AnrInfo describing the ANR; otherwise null.
+ */
+ public AnrInfo anrInfo;
+
+ /**
+ * Create an uninitialized instance of {@link ApplicationErrorReport}.
+ */
+ public ApplicationErrorReport() {
+ }
+
+ /**
+ * Create an instance of {@link ApplicationErrorReport} initialized from
+ * a parcel.
+ */
+ ApplicationErrorReport(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeString(packageName);
+ dest.writeString(installerPackageName);
+ dest.writeString(processName);
+ dest.writeLong(time);
+
+ switch (type) {
+ case TYPE_CRASH:
+ crashInfo.writeToParcel(dest, flags);
+ break;
+ case TYPE_ANR:
+ anrInfo.writeToParcel(dest, flags);
+ break;
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ type = in.readInt();
+ packageName = in.readString();
+ installerPackageName = in.readString();
+ processName = in.readString();
+ time = in.readLong();
+
+ switch (type) {
+ case TYPE_CRASH:
+ crashInfo = new CrashInfo(in);
+ anrInfo = null;
+ break;
+ case TYPE_ANR:
+ anrInfo = new AnrInfo(in);
+ crashInfo = null;
+ break;
+ }
+ }
+
+ /**
+ * Describes an application crash.
+ */
+ public static class CrashInfo {
+ /**
+ * Class name of the exception that caused the crash.
+ */
+ public String exceptionClassName;
+
+ /**
+ * Message stored in the exception.
+ */
+ public String exceptionMessage;
+
+ /**
+ * File which the exception was thrown from.
+ */
+ public String throwFileName;
+
+ /**
+ * Class which the exception was thrown from.
+ */
+ public String throwClassName;
+
+ /**
+ * Method which the exception was thrown from.
+ */
+ public String throwMethodName;
+
+ /**
+ * Stack trace.
+ */
+ public String stackTrace;
+
+ /**
+ * Create an uninitialized instance of CrashInfo.
+ */
+ public CrashInfo() {
+ }
+
+ /**
+ * Create an instance of CrashInfo initialized from a Parcel.
+ */
+ public CrashInfo(Parcel in) {
+ exceptionClassName = in.readString();
+ exceptionMessage = in.readString();
+ throwFileName = in.readString();
+ throwClassName = in.readString();
+ throwMethodName = in.readString();
+ stackTrace = in.readString();
+ }
+
+ /**
+ * Save a CrashInfo instance to a parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(exceptionClassName);
+ dest.writeString(exceptionMessage);
+ dest.writeString(throwFileName);
+ dest.writeString(throwClassName);
+ dest.writeString(throwMethodName);
+ dest.writeString(stackTrace);
+ }
+
+ /**
+ * Dump a CrashInfo instance to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "exceptionClassName: " + exceptionClassName);
+ pw.println(prefix + "exceptionMessage: " + exceptionMessage);
+ pw.println(prefix + "throwFileName: " + throwFileName);
+ pw.println(prefix + "throwClassName: " + throwClassName);
+ pw.println(prefix + "throwMethodName: " + throwMethodName);
+ pw.println(prefix + "stackTrace: " + stackTrace);
+ }
+ }
+
+ /**
+ * Describes an application not responding error.
+ */
+ public static class AnrInfo {
+ /**
+ * Activity name.
+ */
+ public String activity;
+
+ /**
+ * Description of the operation that timed out.
+ */
+ public String cause;
+
+ /**
+ * Additional info, including CPU stats.
+ */
+ public String info;
+
+ /**
+ * Create an uninitialized instance of AnrInfo.
+ */
+ public AnrInfo() {
+ }
+
+ /**
+ * Create an instance of AnrInfo initialized from a Parcel.
+ */
+ public AnrInfo(Parcel in) {
+ activity = in.readString();
+ cause = in.readString();
+ info = in.readString();
+ }
+
+ /**
+ * Save an AnrInfo instance to a parcel.
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(activity);
+ dest.writeString(cause);
+ dest.writeString(info);
+ }
+
+ /**
+ * Dump an AnrInfo instance to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "activity: " + activity);
+ pw.println(prefix + "cause: " + cause);
+ pw.println(prefix + "info: " + info);
+ }
+ }
+
+ public static final Parcelable.Creator<ApplicationErrorReport> CREATOR
+ = new Parcelable.Creator<ApplicationErrorReport>() {
+ public ApplicationErrorReport createFromParcel(Parcel source) {
+ return new ApplicationErrorReport(source);
+ }
+
+ public ApplicationErrorReport[] newArray(int size) {
+ return new ApplicationErrorReport[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Dump the report to a Printer.
+ */
+ public void dump(Printer pw, String prefix) {
+ pw.println(prefix + "type: " + type);
+ pw.println(prefix + "packageName: " + packageName);
+ pw.println(prefix + "installerPackageName: " + installerPackageName);
+ pw.println(prefix + "processName: " + processName);
+ pw.println(prefix + "time: " + time);
+
+ switch (type) {
+ case TYPE_CRASH:
+ crashInfo.dump(pw, prefix);
+ break;
+ case TYPE_ANR:
+ anrInfo.dump(pw, prefix);
+ break;
+ }
+ }
+}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index bcc9302..b052c99 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -18,6 +18,7 @@ package android.app;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IIntentReceiver;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
@@ -25,6 +26,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
@@ -230,11 +232,13 @@ public abstract class ApplicationThreadNative extends Binder
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
int testMode = data.readInt();
+ boolean restrictedBackupMode = (data.readInt() != 0);
Configuration config = Configuration.CREATOR.createFromParcel(data);
HashMap<String, IBinder> services = data.readHashMap(null);
bindApplication(packageName, info,
providers, testName, profileName,
- testArgs, testWatcher, testMode, config, services);
+ testArgs, testWatcher, testMode, restrictedBackupMode,
+ config, services);
return true;
}
@@ -328,7 +332,34 @@ public abstract class ApplicationThreadNative extends Binder
data.enforceInterface(IApplicationThread.descriptor);
boolean start = data.readInt() != 0;
String path = data.readString();
- profilerControl(start, path);
+ ParcelFileDescriptor fd = data.readInt() != 0
+ ? data.readFileDescriptor() : null;
+ profilerControl(start, path, fd);
+ return true;
+ }
+
+ case SET_SCHEDULING_GROUP_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ int group = data.readInt();
+ setSchedulingGroup(group);
+ return true;
+ }
+
+ case SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
+ int backupMode = data.readInt();
+ scheduleCreateBackupAgent(appInfo, backupMode);
+ return true;
+ }
+
+ case SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(data);
+ scheduleDestroyBackupAgent(appInfo);
return true;
}
}
@@ -484,6 +515,24 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ public final void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ app.writeToParcel(data, 0);
+ data.writeInt(backupMode);
+ mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
+ public final void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ app.writeToParcel(data, 0);
+ mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null, 0);
+ data.recycle();
+ }
+
public final void scheduleCreateService(IBinder token, ServiceInfo info)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -543,7 +592,8 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName,
String profileName, Bundle testArgs, IInstrumentationWatcher testWatcher, int debugMode,
- Configuration config, Map<String, IBinder> services) throws RemoteException {
+ boolean restrictedBackupMode, Configuration config,
+ Map<String, IBinder> services) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(packageName);
@@ -559,6 +609,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
data.writeInt(debugMode);
+ data.writeInt(restrictedBackupMode ? 1 : 0);
config.writeToParcel(data, 0);
data.writeMap(services);
mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null,
@@ -663,14 +714,30 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public void profilerControl(boolean start, String path) throws RemoteException {
+ public void profilerControl(boolean start, String path,
+ ParcelFileDescriptor fd) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeInt(start ? 1 : 0);
data.writeString(path);
+ if (fd != null) {
+ data.writeInt(1);
+ fd.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(PROFILER_CONTROL_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
+
+ public void setSchedulingGroup(int group) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeInt(group);
+ mRemote.transact(SET_SCHEDULING_GROUP_TRANSACTION, data, null,
+ IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
}
diff --git a/core/java/android/backup/BackupService.java b/core/java/android/app/BackupAgent.java
index 50a5921..0ac8a1e 100644
--- a/core/java/android/backup/BackupService.java
+++ b/core/java/android/app/BackupAgent.java
@@ -14,47 +14,38 @@
* limitations under the License.
*/
-package android.backup;
+package android.app;
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.app.Service;
-import android.backup.IBackupService;
-import android.content.Intent;
+import android.app.IBackupAgent;
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.content.Context;
+import android.content.ContextWrapper;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
+import java.io.IOException;
+
/**
* This is the central interface between an application and Android's
* settings backup mechanism.
*
- * In order to use the backup service, your application must implement a
- * subclass of BackupService, and declare an intent filter
- * in the application manifest specifying that your BackupService subclass
- * handles the {@link BackupService#SERVICE_ACTION} intent action. For example:
- *
- * <pre class="prettyprint">
- * &lt;!-- Use the class "MyBackupService" to perform backups for my app --&gt;
- * &lt;service android:name=".MyBackupService"&gt;
- * &lt;intent-filter&gt;
- * &lt;action android:name="android.backup.BackupService.SERVICE" /&gt;
- * &lt;/intent-filter&gt;
- * &lt;/service&gt;</pre>
- *
* @hide pending API solidification
*/
+public abstract class BackupAgent extends ContextWrapper {
+ private static final String TAG = "BackupAgent";
-public abstract class BackupService extends Service {
- /**
- * Service Action: Participate in the backup infrastructure. Applications
- * that wish to use the Android backup mechanism must provide an exported
- * subclass of BackupService and give it an {@link android.content.IntentFilter
- * IntentFilter} that accepts this action.
- */
- @SdkConstant(SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_ACTION = "android.backup.BackupService.SERVICE";
+ public BackupAgent() {
+ super(null);
+ }
+
+ public void onCreate() {
+ }
+
+ public void onDestroy() {
+ }
/**
* The application is being asked to write any data changed since the
@@ -76,7 +67,7 @@ public abstract class BackupService extends Service {
* here after writing the requested data to dataFd.
*/
public abstract void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState);
+ ParcelFileDescriptor newState) throws IOException;
/**
* The application is being restored from backup, and should replace any
@@ -87,11 +78,17 @@ public abstract class BackupService extends Service {
*
* @param data An open, read-only ParcelFileDescriptor pointing to a full snapshot
* of the application's data.
+ * @param appVersionCode The android:versionCode value of the application that backed
+ * up this particular data set. This makes it easier for an application's
+ * agent to distinguish among several possible older data versions when
+ * asked to perform the restore operation.
* @param newState An open, read/write ParcelFileDescriptor pointing to an empty
* file. The application should record the final backup state
* here after restoring its data from dataFd.
*/
- public abstract void onRestore(ParcelFileDescriptor /* TODO: BackupDataInput */ data, ParcelFileDescriptor newState);
+ public abstract void onRestore(BackupDataInput data, int appVersionCode,
+ ParcelFileDescriptor newState)
+ throws IOException;
// ----- Core implementation -----
@@ -100,38 +97,52 @@ public abstract class BackupService extends Service {
* Returns the private interface called by the backup system. Applications will
* not typically override this.
*/
- public IBinder onBind(Intent intent) {
- if (intent.getAction().equals(SERVICE_ACTION)) {
- return mBinder;
- }
- return null;
+ public IBinder onBind() {
+ return mBinder;
}
private final IBinder mBinder = new BackupServiceBinder().asBinder();
+ /** @hide */
+ public void attach(Context context) {
+ attachBaseContext(context);
+ }
+
// ----- IBackupService binder interface -----
- private class BackupServiceBinder extends IBackupService.Stub {
+ private class BackupServiceBinder extends IBackupAgent.Stub {
+ private static final String TAG = "BackupServiceBinder";
+
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState) throws RemoteException {
// !!! TODO - real implementation; for now just invoke the callbacks directly
- Log.v("BackupServiceBinder", "doBackup() invoked");
- BackupDataOutput output = new BackupDataOutput(BackupService.this,
- data.getFileDescriptor());
+ Log.v(TAG, "doBackup() invoked");
+ BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
try {
- BackupService.this.onBackup(oldState, output, newState);
+ BackupAgent.this.onBackup(oldState, output, newState);
+ } catch (IOException ex) {
+ Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
} catch (RuntimeException ex) {
- Log.d("BackupService", "onBackup ("
- + BackupService.this.getClass().getName() + ") threw", ex);
+ Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
}
}
- public void doRestore(ParcelFileDescriptor data,
+ public void doRestore(ParcelFileDescriptor data, int appVersionCode,
ParcelFileDescriptor newState) throws RemoteException {
// !!! TODO - real implementation; for now just invoke the callbacks directly
- Log.v("BackupServiceBinder", "doRestore() invoked");
- BackupService.this.onRestore(data, newState);
+ Log.v(TAG, "doRestore() invoked");
+ BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
+ try {
+ BackupAgent.this.onRestore(input, appVersionCode, newState);
+ } catch (IOException ex) {
+ Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
+ } catch (RuntimeException ex) {
+ Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw ex;
+ }
}
}
}
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 863cbcc..78bbb4f 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -46,7 +46,6 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
private final DatePicker mDatePicker;
private final OnDateSetListener mCallBack;
private final Calendar mCalendar;
- private final java.text.DateFormat mDateFormat;
private final java.text.DateFormat mTitleDateFormat;
private final String[] mWeekDays;
@@ -108,7 +107,6 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
DateFormatSymbols symbols = new DateFormatSymbols();
mWeekDays = symbols.getShortWeekdays();
- mDateFormat = DateFormat.getMediumDateFormat(context);
mTitleDateFormat = java.text.DateFormat.
getDateInstance(java.text.DateFormat.FULL);
mCalendar = Calendar.getInstance();
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index b09a57f..222fe75 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,32 +16,34 @@
package android.app;
+import com.android.internal.policy.PolicyManager;
+
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.os.Bundle;
import android.util.Config;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManager;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
-
-import com.android.internal.policy.PolicyManager;
+import android.view.ViewGroup.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
import java.lang.ref.WeakReference;
@@ -81,6 +83,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* {@hide}
*/
protected boolean mCancelable = true;
+
private Message mCancelMessage;
private Message mDismissMessage;
@@ -209,7 +212,9 @@ public class Dialog implements DialogInterface, Window.Callback,
if (mShowing) {
if (Config.LOGV) Log.v(LOG_TAG,
"[Dialog] start: already showing, ignore");
- if (mDecor != null) mDecor.setVisibility(View.VISIBLE);
+ if (mDecor != null) {
+ mDecor.setVisibility(View.VISIBLE);
+ }
return;
}
@@ -236,7 +241,9 @@ public class Dialog implements DialogInterface, Window.Callback,
* Hide the dialog, but do not dismiss it.
*/
public void hide() {
- if (mDecor != null) mDecor.setVisibility(View.GONE);
+ if (mDecor != null) {
+ mDecor.setVisibility(View.GONE);
+ }
}
/**
@@ -266,6 +273,7 @@ public class Dialog implements DialogInterface, Window.Callback,
}
mWindowManager.removeView(mDecor);
+
mDecor = null;
mWindow.closeAllPanels();
onStop();
@@ -280,7 +288,7 @@ public class Dialog implements DialogInterface, Window.Callback,
Message.obtain(mDismissMessage).sendToTarget();
}
}
-
+
// internal method to make sure mcreated is set properly without requiring
// users to call through to super in onCreate
void dispatchOnCreate(Bundle savedInstanceState) {
@@ -608,6 +616,18 @@ public class Dialog implements DialogInterface, Window.Callback,
return onTrackballEvent(ev);
}
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ event.setClassName(getClass().getName());
+ event.setPackageName(mContext.getPackageName());
+
+ LayoutParams params = getWindow().getAttributes();
+ boolean isFullScreen = (params.width == LayoutParams.FILL_PARENT) &&
+ (params.height == LayoutParams.FILL_PARENT);
+ event.setFullScreen(isFullScreen);
+
+ return false;
+ }
+
/**
* @see Activity#onCreatePanelView(int)
*/
diff --git a/core/java/android/app/FullBackupAgent.java b/core/java/android/app/FullBackupAgent.java
new file mode 100644
index 0000000..d89db96
--- /dev/null
+++ b/core/java/android/app/FullBackupAgent.java
@@ -0,0 +1,58 @@
+package android.app;
+
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.backup.FileBackupHelper;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * Backs up an application's entire /data/data/&lt;package&gt;/... file system. This
+ * class is used by the desktop full backup mechanism and is not intended for direct
+ * use by applications.
+ *
+ * {@hide}
+ */
+
+public class FullBackupAgent extends BackupAgent {
+ // !!! TODO: turn off debugging
+ private static final String TAG = "FullBackupAgent";
+ private static final boolean DEBUG = true;
+
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ LinkedList<File> dirsToScan = new LinkedList<File>();
+ ArrayList<String> allFiles = new ArrayList<String>();
+
+ // build the list of files in the app's /data/data tree
+ dirsToScan.add(getFilesDir());
+ if (DEBUG) Log.v(TAG, "Backing up dir tree @ " + getFilesDir().getAbsolutePath() + " :");
+ while (dirsToScan.size() > 0) {
+ File dir = dirsToScan.removeFirst();
+ File[] contents = dir.listFiles();
+ if (contents != null) {
+ for (File f : contents) {
+ if (f.isDirectory()) {
+ dirsToScan.add(f);
+ } else if (f.isFile()) {
+ if (DEBUG) Log.v(TAG, " " + f.getAbsolutePath());
+ allFiles.add(f.getAbsolutePath());
+ }
+ }
+ }
+ }
+
+ // That's the file set; now back it all up
+ FileBackupHelper helper = new FileBackupHelper(this, (String[])allFiles.toArray());
+ helper.performBackup(oldState, data, newState);
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) {
+ }
+}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 56b29c1..3ec7938 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -21,6 +21,9 @@ import android.content.ContentProviderNative;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IIntentSender;
+import android.content.IIntentReceiver;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.ProviderInfo;
@@ -44,9 +47,30 @@ import java.util.List;
* {@hide}
*/
public interface IActivityManager extends IInterface {
+ /**
+ * Returned by startActivity() if the start request was canceled because
+ * app switches are temporarily canceled to ensure the user's last request
+ * (such as pressing home) is performed.
+ */
+ public static final int START_SWITCHES_CANCELED = 4;
+ /**
+ * Returned by startActivity() if an activity wasn't really started, but
+ * the given Intent was given to the existing top activity.
+ */
public static final int START_DELIVERED_TO_TOP = 3;
+ /**
+ * Returned by startActivity() if an activity wasn't really started, but
+ * a task was simply brought to the foreground.
+ */
public static final int START_TASK_TO_FRONT = 2;
+ /**
+ * Returned by startActivity() if the caller asked that the Intent not
+ * be executed if it is the recipient, and that is indeed the case.
+ */
public static final int START_RETURN_INTENT_TO_CALLER = 1;
+ /**
+ * Activity was started successfully as normal.
+ */
public static final int START_SUCCESS = 0;
public static final int START_INTENT_NOT_RESOLVED = -1;
public static final int START_CLASS_NOT_FOUND = -2;
@@ -128,6 +152,11 @@ public interface IActivityManager extends IInterface {
public void serviceDoneExecuting(IBinder token) throws RemoteException;
public IBinder peekService(Intent service, String resolvedType) throws RemoteException;
+ public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode)
+ throws RemoteException;
+ public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException;
+ public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException;
+
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher)
throws RemoteException;
@@ -221,10 +250,13 @@ public interface IActivityManager extends IInterface {
// Turn on/off profiling in a particular process.
public boolean profileControl(String process, boolean start,
- String path) throws RemoteException;
+ String path, ParcelFileDescriptor fd) throws RemoteException;
public boolean shutdown(int timeout) throws RemoteException;
+ public void stopAppSwitches() throws RemoteException;
+ public void resumeAppSwitches() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -371,4 +403,9 @@ public interface IActivityManager extends IInterface {
int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
int PROFILE_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+85;
int SHUTDOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+86;
+ int STOP_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+87;
+ int RESUME_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+88;
+ int START_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+89;
+ int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90;
+ int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 9f3534b..c0bc2a0 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -18,12 +18,14 @@ package android.app;
import android.content.ComponentName;
import android.content.Intent;
+import android.content.IIntentReceiver;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
@@ -59,6 +61,11 @@ public interface IApplicationThread extends IInterface {
int configChanges) throws RemoteException;
void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode,
String data, Bundle extras, boolean sync) throws RemoteException;
+ static final int BACKUP_MODE_INCREMENTAL = 0;
+ static final int BACKUP_MODE_FULL = 1;
+ static final int BACKUP_MODE_RESTORE = 2;
+ void scheduleCreateBackupAgent(ApplicationInfo app, int backupMode) throws RemoteException;
+ void scheduleDestroyBackupAgent(ApplicationInfo app) throws RemoteException;
void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException;
void scheduleBindService(IBinder token,
Intent intent, boolean rebind) throws RemoteException;
@@ -71,8 +78,8 @@ public interface IApplicationThread extends IInterface {
static final int DEBUG_WAIT = 2;
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, Bundle testArguments,
- IInstrumentationWatcher testWatcher, int debugMode, Configuration config, Map<String,
- IBinder> services) throws RemoteException;
+ IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode,
+ Configuration config, Map<String, IBinder> services) throws RemoteException;
void scheduleExit() throws RemoteException;
void requestThumbnail(IBinder token) throws RemoteException;
void scheduleConfigurationChanged(Configuration config) throws RemoteException;
@@ -86,8 +93,10 @@ public interface IApplicationThread extends IInterface {
void scheduleLowMemory() throws RemoteException;
void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
void requestPss() throws RemoteException;
- void profilerControl(boolean start, String path) throws RemoteException;
-
+ void profilerControl(boolean start, String path, ParcelFileDescriptor fd)
+ throws RemoteException;
+ void setSchedulingGroup(int group) throws RemoteException;
+
String descriptor = "android.app.IApplicationThread";
int SCHEDULE_PAUSE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
@@ -117,4 +126,7 @@ public interface IApplicationThread extends IInterface {
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;
+ int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30;
}
diff --git a/core/java/android/backup/IBackupService.aidl b/core/java/android/app/IBackupAgent.aidl
index 1bde8ea..9b0550f 100644
--- a/core/java/android/backup/IBackupService.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -14,18 +14,18 @@
* limitations under the License.
*/
-package android.backup;
+package android.app;
import android.os.ParcelFileDescriptor;
/**
* Interface presented by applications being asked to participate in the
* backup & restore mechanism. End user code does not typically implement
- * this interface; they subclass BackupService instead.
+ * this interface; they subclass BackupAgent instead.
*
* {@hide}
*/
-interface IBackupService {
+interface IBackupAgent {
/**
* Request that the app perform an incremental backup.
*
@@ -51,9 +51,14 @@ interface IBackupService {
* app's backup. This is to be a <i>replacement</i> of the app's
* current data, not to be merged into it.
*
+ * @param appVersionCode The android:versionCode attribute of the application
+ * that created this data set. This can help the agent distinguish among
+ * various historical backup content possibilities.
+ *
* @param newState Read-write file, empty when onRestore() is called,
* that is to be written with the state description that holds after
* the restore has been completed.
*/
- void doRestore(in ParcelFileDescriptor data, in ParcelFileDescriptor newState);
+ void doRestore(in ParcelFileDescriptor data, int appVersionCode,
+ in ParcelFileDescriptor newState);
}
diff --git a/core/java/android/app/IIntentReceiver.aidl b/core/java/android/app/IIntentReceiver.aidl
deleted file mode 100755
index 5f5d0eb..0000000
--- a/core/java/android/app/IIntentReceiver.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-**
-** Copyright 2006, 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.Intent;
-import android.os.Bundle;
-
-/**
- * System private API for dispatching intent broadcasts. This is given to the
- * activity manager as part of registering for an intent broadcasts, and is
- * called when it receives intents.
- *
- * {@hide}
- */
-oneway interface IIntentReceiver {
- void performReceive(in Intent intent, int resultCode,
- String data, in Bundle extras, boolean ordered);
-}
-
diff --git a/core/java/android/app/IIntentSender.aidl b/core/java/android/app/IIntentSender.aidl
deleted file mode 100644
index 53e135a..0000000
--- a/core/java/android/app/IIntentSender.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/* //device/java/android/android/app/IActivityPendingResult.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.app;
-
-import android.app.IIntentReceiver;
-import android.content.Intent;
-
-/** @hide */
-interface IIntentSender {
- int send(int code, in Intent intent, String resolvedType,
- IIntentReceiver finishedReceiver);
-}
diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl
index 39eb4f1..e8bd60a 100644
--- a/core/java/android/app/ISearchManager.aidl
+++ b/core/java/android/app/ISearchManager.aidl
@@ -16,11 +16,28 @@
package android.app;
+import android.app.ISearchManagerCallback;
import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.server.search.SearchableInfo;
/** @hide */
interface ISearchManager {
SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch);
List<SearchableInfo> getSearchablesInGlobalSearch();
+ List<SearchableInfo> getSearchablesForWebSearch();
+ SearchableInfo getDefaultSearchableForWebSearch();
+ void setDefaultWebSearch(in ComponentName component);
+ void startSearch(in String initialQuery,
+ boolean selectInitialQuery,
+ in ComponentName launchActivity,
+ in Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback);
+ void stopSearch();
+ boolean isVisible();
+ Bundle onSaveInstanceState();
+ void onRestoreInstanceState(in Bundle savedInstanceState);
+ void onConfigurationChanged(in Configuration newConfig);
}
diff --git a/core/java/android/app/ISearchManagerCallback.aidl b/core/java/android/app/ISearchManagerCallback.aidl
new file mode 100644
index 0000000..bdfb2ba
--- /dev/null
+++ b/core/java/android/app/ISearchManagerCallback.aidl
@@ -0,0 +1,23 @@
+/**
+ * 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.app;
+
+/** @hide */
+oneway interface ISearchManagerCallback {
+ void onDismiss();
+ void onCancel();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index f6a28b2..e31f4f8 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -446,13 +446,13 @@ public class Instrumentation {
if (ai == null) {
throw new RuntimeException("Unable to resolve activity for: " + intent);
}
- if (!ai.applicationInfo.processName.equals(
- getTargetContext().getPackageName())) {
+ String myProc = mThread.getProcessName();
+ if (!ai.processName.equals(myProc)) {
// todo: if this intent is ambiguous, look here to see if
// there is a single match that is in our package.
- throw new RuntimeException("Intent resolved to different package "
- + ai.applicationInfo.packageName + ": "
- + intent);
+ throw new RuntimeException("Intent in process "
+ + myProc + " resolved to different process "
+ + ai.processName + ": " + intent);
}
intent.setComponent(new ComponentName(
diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java
index 8d249da..accdda9 100644
--- a/core/java/android/app/LauncherActivity.java
+++ b/core/java/android/app/LauncherActivity.java
@@ -60,26 +60,20 @@ public abstract class LauncherActivity extends ListActivity {
* An item in the list
*/
public static class ListItem {
+ public ResolveInfo resolveInfo;
public CharSequence label;
- //public CharSequence description;
public Drawable icon;
public String packageName;
public String className;
public Bundle extras;
ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) {
+ this.resolveInfo = resolveInfo;
label = resolveInfo.loadLabel(pm);
if (label == null && resolveInfo.activityInfo != null) {
label = resolveInfo.activityInfo.name;
}
- /*
- if (resolveInfo.activityInfo != null &&
- resolveInfo.activityInfo.applicationInfo != null) {
- description = resolveInfo.activityInfo.applicationInfo.loadDescription(pm);
- }
- */
-
icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm));
packageName = resolveInfo.activityInfo.applicationInfo.packageName;
className = resolveInfo.activityInfo.name;
@@ -122,6 +116,14 @@ public abstract class LauncherActivity extends ListActivity {
return intent;
}
+ public ListItem itemForPosition(int position) {
+ if (mActivitiesList == null) {
+ return null;
+ }
+
+ return mActivitiesList.get(position);
+ }
+
public int getCount() {
return mActivitiesList != null ? mActivitiesList.size() : 0;
}
@@ -354,6 +356,16 @@ public abstract class LauncherActivity extends ListActivity {
}
/**
+ * Return the {@link ListItem} for a specific position in our
+ * {@link android.widget.ListView}.
+ * @param position The item to return
+ */
+ protected ListItem itemForPosition(int position) {
+ ActivityAdapter adapter = (ActivityAdapter) mAdapter;
+ return adapter.itemForPosition(position);
+ }
+
+ /**
* Get the base intent to use when running
* {@link PackageManager#queryIntentActivities(Intent, int)}.
*/
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index cb660c7..f9c38f9 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -18,6 +18,9 @@ package android.app;
import android.content.Context;
import android.content.Intent;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.IntentSender;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.Handler;
@@ -105,7 +108,7 @@ public final class PendingIntent implements Parcelable {
public CanceledException(Exception cause) {
super(cause);
}
- };
+ }
/**
* Callback interface for discovering when a send operation has
@@ -270,6 +273,21 @@ public final class PendingIntent implements Parcelable {
return null;
}
+ private class IntentSenderWrapper extends IntentSender {
+ protected IntentSenderWrapper(IIntentSender target) {
+ super(target);
+ }
+ }
+ /**
+ * Retrieve a IntentSender object that wraps the existing sender of the PendingIntent
+ *
+ * @return Returns a IntentSender object that wraps the sender of PendingIntent
+ *
+ */
+ public IntentSender getIntentSender() {
+ return new IntentSenderWrapper(mTarget);
+ }
+
/**
* Cancel a currently active PendingIntent. Only the original application
* owning an PendingIntent can cancel it.
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 343380c..fdb619a 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -24,6 +24,8 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -32,6 +34,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
@@ -41,8 +44,10 @@ import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
+import android.text.util.Regex;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -51,6 +56,7 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
@@ -67,8 +73,8 @@ import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
- * In-application-process implementation of Search Bar. This is still controlled by the
- * SearchManager, but it runs in the current activity's process to keep things lighter weight.
+ * System search dialog. This is controlled by the
+ * SearchManagerService and runs in the system process.
*
* @hide
*/
@@ -82,13 +88,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private static final String INSTANCE_KEY_COMPONENT = "comp";
private static final String INSTANCE_KEY_APPDATA = "data";
private static final String INSTANCE_KEY_GLOBALSEARCH = "glob";
- private static final String INSTANCE_KEY_DISPLAY_QUERY = "dQry";
- private static final String INSTANCE_KEY_DISPLAY_SEL_START = "sel1";
- private static final String INSTANCE_KEY_DISPLAY_SEL_END = "sel2";
- private static final String INSTANCE_KEY_SELECTED_ELEMENT = "slEl";
- private static final int INSTANCE_SELECTED_BUTTON = -2;
- private static final int INSTANCE_SELECTED_QUERY = -1;
-
+ private static final String INSTANCE_KEY_STORED_COMPONENT = "sComp";
+ private static final String INSTANCE_KEY_STORED_APPDATA = "sData";
+ private static final String INSTANCE_KEY_PREVIOUS_COMPONENTS = "sPrev";
+ private static final String INSTANCE_KEY_USER_QUERY = "uQry";
+
private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12;
private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
@@ -103,6 +107,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private Button mGoButton;
private ImageButton mVoiceButton;
private View mSearchPlate;
+ private Drawable mWorkingSpinner;
// interaction with searchable application
private SearchableInfo mSearchable;
@@ -129,9 +134,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private SuggestionsAdapter mSuggestionsAdapter;
// Whether to rewrite queries when selecting suggestions
- // TODO: This is disabled because of problems with persistent selections
- // causing non-user-initiated rewrites.
- private static final boolean REWRITE_QUERIES = false;
+ private static final boolean REWRITE_QUERIES = true;
// The query entered by the user. This is not changed when selecting a suggestion
// that modifies the contents of the text field. But if the user then edits
@@ -142,14 +145,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// more than once.
private final WeakHashMap<String, Drawable> mOutsideDrawablesCache =
new WeakHashMap<String, Drawable>();
-
+
+ // Last known IME options value for the search edit text.
+ private int mSearchAutoCompleteImeOptions;
+
/**
* Constructor - fires it up and makes it look like the search UI.
*
* @param context Application Context we can use for system acess
*/
public SearchDialog(Context context) {
- super(context, com.android.internal.R.style.Theme_SearchBar);
+ super(context, com.android.internal.R.style.Theme_GlobalSearchBar);
}
/**
@@ -160,17 +166,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Window theWindow = getWindow();
- theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);
-
setContentView(com.android.internal.R.layout.search_bar);
- theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
- // taking up the whole window (even when transparent) is less than ideal,
- // but necessary to show the popup window until the window manager supports
- // having windows anchored by their parent but not clipped by them.
- ViewGroup.LayoutParams.FILL_PARENT);
+ Window theWindow = getWindow();
WindowManager.LayoutParams lp = theWindow.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+ lp.width = ViewGroup.LayoutParams.FILL_PARENT;
+ // taking up the whole window (even when transparent) is less than ideal,
+ // but necessary to show the popup window until the window manager supports
+ // having windows anchored by their parent but not clipped by them.
+ lp.height = ViewGroup.LayoutParams.FILL_PARENT;
+ lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
theWindow.setAttributes(lp);
@@ -182,6 +188,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
+ mWorkingSpinner = getContext().getResources().
+ getDrawable(com.android.internal.R.drawable.search_spinner);
// attach listeners
mSearchAutoComplete.addTextChangedListener(mTextWatcher);
@@ -213,10 +221,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Save voice intent for later queries/launching
mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
+ mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions();
}
/**
@@ -226,20 +238,21 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
public boolean show(String initialQuery, boolean selectInitialQuery,
ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
- if (isShowing()) {
- // race condition - already showing but not handling events yet.
- // in this case, just discard the "show" request
- return true;
- }
-
+
// Reset any stored values from last time dialog was shown.
mStoredComponentName = null;
mStoredAppSearchData = null;
-
- return doShow(initialQuery, selectInitialQuery, componentName, appSearchData, globalSearch);
+
+ boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData,
+ globalSearch);
+ if (success) {
+ // Display the drop down as soon as possible instead of waiting for the rest of the
+ // pending UI stuff to get done, so that things appear faster to the user.
+ mSearchAutoComplete.showDropDownAfterLayout();
+ }
+ return success;
}
-
/**
* Called in response to a press of the hard search button in
* {@link #onKeyDown(int, KeyEvent)}, this method toggles between in-app
@@ -309,15 +322,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
+ appSearchData + ", " + globalSearch + ")");
}
+ SearchManager searchManager = (SearchManager)
+ mContext.getSystemService(Context.SEARCH_SERVICE);
// Try to get the searchable info for the provided component (or for global search,
// if globalSearch == true).
- mSearchable = SearchManager.getSearchableInfo(componentName, globalSearch);
+ mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
// If we got back nothing, and it wasn't a request for global search, then try again
// for global search, as we'll try to launch that in lieu of any component-specific search.
if (!globalSearch && mSearchable == null) {
globalSearch = true;
- mSearchable = SearchManager.getSearchableInfo(componentName, globalSearch);
+ mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
// If we still get back null (i.e., there's not even a searchable info available
// for global search), then really give up.
@@ -332,7 +347,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mAppSearchData = appSearchData;
// Using globalSearch here is just an optimization, just calling
// isDefaultSearchable() should always give the same result.
- mGlobalSearchMode = globalSearch || SearchManager.isDefaultSearchable(mSearchable);
+ mGlobalSearchMode = globalSearch || searchManager.isDefaultSearchable(mSearchable);
mActivityContext = mSearchable.getActivityContext(getContext());
// show the dialog. this will call onStart().
@@ -345,6 +360,21 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInputUnchecked(0, null);
}
+
+ // The Dialog uses a ContextThemeWrapper for the context; use this to change the
+ // theme out from underneath us, between the global search theme and the in-app
+ // search theme. They are identical except that the global search theme does not
+ // dim the background of the window (because global search is full screen so it's
+ // not needed and this should save a little bit of time on global search invocation).
+ Object context = getContext();
+ if (context instanceof ContextThemeWrapper) {
+ ContextThemeWrapper wrapper = (ContextThemeWrapper) context;
+ if (globalSearch) {
+ wrapper.setTheme(com.android.internal.R.style.Theme_GlobalSearchBar);
+ } else {
+ wrapper.setTheme(com.android.internal.R.style.Theme_SearchBar);
+ }
+ }
show();
}
@@ -372,11 +402,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
public void onStop() {
super.onStop();
- // TODO: Removing the listeners means that they never get called, since
- // Dialog.dismissDialog() calls onStop() before sendDismissMessage().
- setOnCancelListener(null);
- setOnDismissListener(null);
-
// stop receiving broadcasts (throws exception if none registered)
try {
getContext().unregisterReceiver(mBroadcastReceiver);
@@ -394,6 +419,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mUserQuery = null;
mPreviousComponents = null;
}
+
+ /**
+ * Sets the search dialog to the 'working' state, which shows a working spinner in the
+ * right hand size of the text field.
+ *
+ * @param working true to show spinner, false to hide spinner
+ */
+ public void setWorking(boolean working) {
+ if (working) {
+ mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, mWorkingSpinner, null);
+ ((Animatable) mWorkingSpinner).start();
+ } else {
+ mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, null, null);
+ ((Animatable) mWorkingSpinner).stop();
+ }
+ }
/**
* Closes and gets rid of the suggestions adapter.
@@ -412,8 +455,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
/**
* Save the minimal set of data necessary to recreate the search
*
- * TODO: go through this and make sure that it saves everything that is needed
- *
* @return A bundle with the state of the dialog.
*/
@Override
@@ -424,20 +465,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
bundle.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode);
-
- // UI state
- bundle.putString(INSTANCE_KEY_DISPLAY_QUERY, mSearchAutoComplete.getText().toString());
- bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_START, mSearchAutoComplete.getSelectionStart());
- bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_END, mSearchAutoComplete.getSelectionEnd());
-
- int selectedElement = INSTANCE_SELECTED_QUERY;
- if (mGoButton.isFocused()) {
- selectedElement = INSTANCE_SELECTED_BUTTON;
- } else if (mSearchAutoComplete.isPopupShowing()) {
- selectedElement = 0; // TODO mSearchTextField.getListSelection() // 0..n
- }
- bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement);
-
+ bundle.putParcelable(INSTANCE_KEY_STORED_COMPONENT, mStoredComponentName);
+ bundle.putBundle(INSTANCE_KEY_STORED_APPDATA, mStoredAppSearchData);
+ bundle.putParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS, mPreviousComponents);
+ bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
+
return bundle;
}
@@ -451,45 +483,27 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
- // Get the launch info
ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH);
-
- // get the UI state
- String displayQuery = savedInstanceState.getString(INSTANCE_KEY_DISPLAY_QUERY);
- int querySelStart = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_START, -1);
- int querySelEnd = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_END, -1);
- int selectedElement = savedInstanceState.getInt(INSTANCE_KEY_SELECTED_ELEMENT);
-
- // show the dialog. skip any show/hide animation, we want to go fast.
- // send the text that actually generates the suggestions here; we'll replace the display
- // text as necessary in a moment.
- if (!show(displayQuery, false, launchComponent, appSearchData, globalSearch)) {
+ ComponentName storedComponentName =
+ savedInstanceState.getParcelable(INSTANCE_KEY_STORED_COMPONENT);
+ Bundle storedAppSearchData =
+ savedInstanceState.getBundle(INSTANCE_KEY_STORED_APPDATA);
+ ArrayList<ComponentName> previousComponents =
+ savedInstanceState.getParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS);
+ String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
+
+ // Set stored state
+ mStoredComponentName = storedComponentName;
+ mStoredAppSearchData = storedAppSearchData;
+ mPreviousComponents = previousComponents;
+
+ // show the dialog.
+ if (!doShow(userQuery, false, launchComponent, appSearchData, globalSearch)) {
// for some reason, we couldn't re-instantiate
return;
}
-
- mSearchAutoComplete.setText(displayQuery);
-
- // clean up the selection state
- switch (selectedElement) {
- case INSTANCE_SELECTED_BUTTON:
- mGoButton.setEnabled(true);
- mGoButton.setFocusable(true);
- mGoButton.requestFocus();
- break;
- case INSTANCE_SELECTED_QUERY:
- if (querySelStart >= 0 && querySelEnd >= 0) {
- mSearchAutoComplete.requestFocus();
- mSearchAutoComplete.setSelection(querySelStart, querySelEnd);
- }
- break;
- default:
- // TODO: defer selecting a list element until suggestion list appears
-// mSearchAutoComplete.setListSelection(selectedElement)
- break;
- }
}
/**
@@ -534,7 +548,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
mSearchAutoComplete.setInputType(inputType);
- mSearchAutoComplete.setImeOptions(mSearchable.getImeOptions());
+ mSearchAutoCompleteImeOptions = mSearchable.getImeOptions();
+ mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions);
}
}
@@ -547,24 +562,20 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold());
+ // we dismiss the entire dialog instead
+ mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
if (mGlobalSearchMode) {
mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in
- mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
- mSearchAutoComplete.setDropDownBackgroundResource(
- com.android.internal.R.drawable.search_dropdown_background);
} else {
mSearchAutoComplete.setDropDownAlwaysVisible(false);
- mSearchAutoComplete.setDropDownDismissedOnCompletion(true);
- mSearchAutoComplete.setDropDownBackgroundResource(
- com.android.internal.R.drawable.search_dropdown_background_apps);
}
// attach the suggestions adapter, if suggestions are available
// The existence of a suggestions authority is the proxy for "suggestions available here"
if (mSearchable.getSuggestAuthority() != null) {
- mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable,
- mOutsideDrawablesCache);
+ mSuggestionsAdapter = new SuggestionsAdapter(getContext(), this, mSearchable,
+ mOutsideDrawablesCache, mGlobalSearchMode);
mSearchAutoComplete.setAdapter(mSuggestionsAdapter);
}
}
@@ -597,7 +608,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mSearchPlate.getPaddingBottom());
} else {
PackageManager pm = getContext().getPackageManager();
- Drawable icon = null;
+ Drawable icon;
try {
ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
icon = pm.getApplicationIcon(info.applicationInfo);
@@ -765,7 +776,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
- public void afterTextChanged(Editable s) { }
+ public void afterTextChanged(Editable s) {
+ if (!mSearchAutoComplete.isPerformingCompletion()) {
+ // The user changed the query, check if it is a URL and if so change the search
+ // button in the soft keyboard to the 'Go' button.
+ int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION));
+ if (Regex.WEB_URL_PATTERN.matcher(mUserQuery).matches()) {
+ options = options | EditorInfo.IME_ACTION_GO;
+ } else {
+ options = options | EditorInfo.IME_ACTION_SEARCH;
+ }
+ if (options != mSearchAutoCompleteImeOptions) {
+ mSearchAutoCompleteImeOptions = options;
+ mSearchAutoComplete.setImeOptions(options);
+ // This call is required to update the soft keyboard UI with latest IME flags.
+ mSearchAutoComplete.setInputType(mSearchAutoComplete.getInputType());
+ }
+ }
+ }
};
/**
@@ -903,6 +931,32 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
+ * Corrects http/https typo errors in the given url string, and if the protocol specifier was
+ * not present defaults to http.
+ *
+ * @param inUrl URL to check and fix
+ * @return fixed URL string.
+ */
+ private String fixUrl(String inUrl) {
+ if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
+ return inUrl;
+
+ if (inUrl.startsWith("http:") || inUrl.startsWith("https:")) {
+ if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
+ inUrl = inUrl.replaceFirst("/", "//");
+ } else {
+ inUrl = inUrl.replaceFirst(":", "://");
+ }
+ }
+
+ if (inUrl.indexOf("://") == -1) {
+ inUrl = "http://" + inUrl;
+ }
+
+ return inUrl;
+ }
+
+ /**
* React to the user typing "enter" or other hardwired keys while typing in the search box.
* This handles these special keys while the edit box has focus.
*/
@@ -932,7 +986,19 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (keyCode == KeyEvent.KEYCODE_ENTER
&& event.getAction() == KeyEvent.ACTION_UP) {
v.cancelLongPress();
- launchQuerySearch();
+
+ // If this is a url entered by the user and we displayed the 'Go' button which
+ // the user clicked, launch the url instead of using it as a search query.
+ if ((mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION)
+ == EditorInfo.IME_ACTION_GO) {
+ Uri uri = Uri.parse(fixUrl(mSearchAutoComplete.getText().toString()));
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ launchIntent(intent);
+ } else {
+ // Launch as a regular search.
+ launchQuerySearch();
+ }
return true;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -1069,7 +1135,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/
protected void launchQuerySearch(int actionKey, String actionMsg) {
String query = mSearchAutoComplete.getText().toString();
- Intent intent = createIntent(Intent.ACTION_SEARCH, null, query, null,
+ Intent intent = createIntent(Intent.ACTION_SEARCH, null, null, query, null,
actionKey, actionMsg);
launchIntent(intent);
}
@@ -1097,15 +1163,121 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
protected boolean launchSuggestion(int position, int actionKey, String actionMsg) {
Cursor c = mSuggestionsAdapter.getCursor();
if ((c != null) && c.moveToPosition(position)) {
+
Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
+
+ // report back about the click
+ if (mGlobalSearchMode) {
+ // in global search mode, do it via cursor
+ mSuggestionsAdapter.callCursorOnClick(c, position);
+ } else if (intent != null
+ && mPreviousComponents != null
+ && !mPreviousComponents.isEmpty()) {
+ // in-app search (and we have pivoted in as told by mPreviousComponents,
+ // which is used for keeping track of what we pop back to when we are pivoting into
+ // in app search.)
+ reportInAppClickToGlobalSearch(c, intent);
+ }
+
+ // launch the intent
launchIntent(intent);
+
return true;
}
return false;
}
-
+
+ /**
+ * Report a click from an in app search result back to global search for shortcutting porpoises.
+ *
+ * @param c The cursor that is pointing to the clicked position.
+ * @param intent The intent that will be launched for the click.
+ */
+ private void reportInAppClickToGlobalSearch(Cursor c, Intent intent) {
+ // for in app search, still tell global search via content provider
+ Uri uri = getClickReportingUri();
+ final ContentValues cv = new ContentValues();
+ cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_QUERY, mUserQuery);
+ final ComponentName source = mSearchable.getSearchActivity();
+ cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_COMPONENT, source.flattenToShortString());
+
+ // grab the intent columns from the intent we created since it has additional
+ // logic for falling back on the searchable default
+ cv.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION, intent.getAction());
+ cv.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, intent.getDataString());
+ cv.put(SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME,
+ intent.getStringExtra(SearchManager.COMPONENT_NAME_KEY));
+
+ // ensure the icons will work for global search
+ cv.put(SearchManager.SUGGEST_COLUMN_ICON_1,
+ wrapIconForPackage(
+ source,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_1)));
+ cv.put(SearchManager.SUGGEST_COLUMN_ICON_2,
+ wrapIconForPackage(
+ source,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_2)));
+
+ // the rest can be passed through directly
+ cv.put(SearchManager.SUGGEST_COLUMN_FORMAT,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_FORMAT));
+ cv.put(SearchManager.SUGGEST_COLUMN_TEXT_1,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_1));
+ cv.put(SearchManager.SUGGEST_COLUMN_TEXT_2,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_2));
+ cv.put(SearchManager.SUGGEST_COLUMN_QUERY,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY));
+ cv.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
+ getColumnString(c, SearchManager.SUGGEST_COLUMN_SHORTCUT_ID));
+ // note: deliberately omitting background color since it is only for global search
+ // "more results" entries
+ mContext.getContentResolver().insert(uri, cv);
+ }
+
/**
- * Launches an intent. Also dismisses the search dialog if not in global search mode.
+ * @return A URI appropriate for reporting a click.
+ */
+ private Uri getClickReportingUri() {
+ Uri.Builder uriBuilder = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SearchManager.SEARCH_CLICK_REPORT_AUTHORITY);
+
+ uriBuilder.appendPath(SearchManager.SEARCH_CLICK_REPORT_URI_PATH);
+
+ return uriBuilder
+ .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+ .fragment("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
+ .build();
+ }
+
+ /**
+ * Wraps an icon for a particular package. If the icon is a resource id, it is converted into
+ * an android.resource:// URI.
+ *
+ * @param source The source of the icon
+ * @param icon The icon retrieved from a suggestion column
+ * @return An icon string appropriate for the package.
+ */
+ private String wrapIconForPackage(ComponentName source, String icon) {
+ if (icon == null || icon.length() == 0 || "0".equals(icon)) {
+ // SearchManager specifies that null or zero can be returned to indicate
+ // no icon. We also allow empty string.
+ return null;
+ } else if (!Character.isDigit(icon.charAt(0))){
+ return icon;
+ } else {
+ String packageName = source.getPackageName();
+ return new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ .authority(packageName)
+ .encodedPath(icon)
+ .toString();
+ }
+ }
+
+ /**
+ * Launches an intent and dismisses the search dialog (unless the intent
+ * is one of the special intents that modifies the state of the search dialog).
*/
private void launchIntent(Intent intent) {
if (intent == null) {
@@ -1114,9 +1286,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (handleSpecialIntent(intent)){
return;
}
- if (!mGlobalSearchMode) {
- dismiss();
- }
+ dismiss();
getContext().startActivity(intent);
}
@@ -1130,15 +1300,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.equals(action)) {
handleChangeSourceIntent(intent);
return true;
- } else if (SearchManager.INTENT_ACTION_CURSOR_RESPOND.equals(action)) {
- handleCursorRespondIntent(intent);
- return true;
}
return false;
}
/**
- * Handles SearchManager#INTENT_ACTION_CHANGE_SOURCE.
+ * Handles {@link SearchManager#INTENT_ACTION_CHANGE_SEARCH_SOURCE}.
*/
private void handleChangeSourceIntent(Intent intent) {
Uri dataUri = intent.getData();
@@ -1162,18 +1329,16 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
String query = intent.getStringExtra(SearchManager.QUERY);
setUserQuery(query);
+ mSearchAutoComplete.showDropDown();
}
-
+
/**
- * Handles {@link SearchManager#INTENT_ACTION_CURSOR_RESPOND}.
+ * Sets the list item selection in the AutoCompleteTextView's ListView.
*/
- private void handleCursorRespondIntent(Intent intent) {
- Cursor c = mSuggestionsAdapter.getCursor();
- if (c != null) {
- c.respond(intent.getExtras());
- }
+ public void setListSelection(int index) {
+ mSearchAutoComplete.setListSelection(index);
}
-
+
/**
* Saves the previous component that was searched, so that we can go
* back to it.
@@ -1243,6 +1408,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
try {
// use specific action if supplied, or default action if supplied, or fixed default
String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
+
+ // some items are display only, or have effect via the cursor respond click reporting.
+ if (SearchManager.INTENT_ACTION_NONE.equals(action)) {
+ return null;
+ }
+
if (action == null) {
action = mSearchable.getSuggestIntentAction();
}
@@ -1264,11 +1435,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
Uri dataUri = (data == null) ? null : Uri.parse(data);
- String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
-
+ String componentName = getColumnString(
+ c, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME);
+
String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
+ String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
- return createIntent(action, dataUri, query, extraData, actionKey, actionMsg);
+ return createIntent(action, dataUri, extraData, query, componentName, actionKey,
+ actionMsg);
} catch (RuntimeException e ) {
int rowNum;
try { // be really paranoid now
@@ -1287,27 +1461,33 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*
* @param action Intent action.
* @param data Intent data, or <code>null</code>.
- * @param query Intent query, or <code>null</code>.
* @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
+ * @param query Intent query, or <code>null</code>.
+ * @param componentName Data for {@link SearchManager#COMPONENT_NAME_KEY} or <code>null</code>.
* @param actionKey The key code of the action key that was pressed,
* or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
* @param actionMsg The message for the action key that was pressed,
* or <code>null</code> if none.
* @return The intent.
*/
- private Intent createIntent(String action, Uri data, String query, String extraData,
- int actionKey, String actionMsg) {
+ private Intent createIntent(String action, Uri data, String extraData, String query,
+ String componentName, int actionKey, String actionMsg) {
// Now build the Intent
Intent intent = new Intent(action);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (data != null) {
intent.setData(data);
}
+ intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
if (query != null) {
intent.putExtra(SearchManager.QUERY, query);
}
if (extraData != null) {
intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
}
+ if (componentName != null) {
+ intent.putExtra(SearchManager.COMPONENT_NAME_KEY, componentName);
+ }
if (mAppSearchData != null) {
intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
}
@@ -1383,20 +1563,22 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private boolean isEmpty() {
return TextUtils.getTrimmedLength(getText()) == 0;
}
-
+
/**
- * Clears the entered text.
+ * We override this method to avoid replacing the query box text
+ * when a suggestion is clicked.
*/
- private void clear() {
- setText("");
+ @Override
+ protected void replaceText(CharSequence text) {
}
/**
- * We override this method to avoid replacing the query box text
- * when a suggestion is clicked.
+ * We override this method to avoid an extra onItemClick being called on the
+ * drop-down's OnItemClickListener by {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)}
+ * when an item is clicked with the trackball.
*/
@Override
- protected void replaceText(CharSequence text) {
+ public void performCompletion() {
}
/**
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 3bf37c3..e5ba6a4 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.server.search.SearchableInfo;
+import android.util.Log;
import android.view.KeyEvent;
import java.util.List;
@@ -1108,6 +1109,10 @@ import java.util.List;
public class SearchManager
implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
{
+
+ private static final boolean DBG = false;
+ private static final String TAG = "SearchManager";
+
/**
* This is a shortcut definition for the default menu key to use for invoking search.
*
@@ -1131,6 +1136,20 @@ public class SearchManager
public final static String QUERY = "query";
/**
+ * Intent extra data key: Use this key with
+ * {@link android.content.Intent#getStringExtra
+ * content.Intent.getStringExtra()}
+ * to obtain the query string typed in by the user.
+ * This may be different from the value of {@link #QUERY}
+ * if the intent is the result of selecting a suggestion.
+ * In that case, {@link #QUERY} will contain the value of
+ * {@link #SUGGEST_COLUMN_QUERY} for the suggestion, and
+ * {@link #USER_QUERY} will contain the string typed by the
+ * user.
+ */
+ public final static String USER_QUERY = "user_query";
+
+ /**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getBundleExtra
* content.Intent.getBundleExtra()}
@@ -1148,7 +1167,7 @@ public class SearchManager
* @hide
*/
public final static String SOURCE = "source";
-
+
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()}
@@ -1160,12 +1179,66 @@ public class SearchManager
public final static String ACTION_KEY = "action_key";
/**
+ * Intent component name key: This key will be used for the extra populated by the
+ * {@link #SUGGEST_COLUMN_INTENT_COMPONENT_NAME} column.
+ *
+ * {@hide}
+ */
+ public final static String COMPONENT_NAME_KEY = "intent_component_name_key";
+
+ /**
* Intent extra data key: This key will be used for the extra populated by the
* {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
+ *
* {@hide}
*/
public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
-
+
+ /**
+ * Defines the constants used in the communication between {@link android.app.SearchDialog} and
+ * the global search provider via {@link Cursor#respond(android.os.Bundle)}.
+ *
+ * @hide
+ */
+ public static class DialogCursorProtocol {
+
+ /**
+ * The sent bundle will contain this integer key, with a value set to one of the events
+ * below.
+ */
+ public final static String METHOD = "DialogCursorProtocol.method";
+
+ /**
+ * After data has been refreshed.
+ */
+ public final static int POST_REFRESH = 0;
+ public final static String POST_REFRESH_RECEIVE_ISPENDING
+ = "DialogCursorProtocol.POST_REFRESH.isPending";
+ public final static String POST_REFRESH_RECEIVE_DISPLAY_NOTIFY
+ = "DialogCursorProtocol.POST_REFRESH.displayNotify";
+
+ /**
+ * Just before closing the cursor.
+ */
+ public final static int PRE_CLOSE = 1;
+ public final static String PRE_CLOSE_SEND_MAX_DISPLAY_POS
+ = "DialogCursorProtocol.PRE_CLOSE.sendDisplayPosition";
+
+ /**
+ * When a position has been clicked.
+ */
+ public final static int CLICK = 2;
+ public final static String CLICK_SEND_POSITION
+ = "DialogCursorProtocol.CLICK.sendPosition";
+ public final static String CLICK_RECEIVE_SELECTED_POS
+ = "DialogCursorProtocol.CLICK.receiveSelectedPosition";
+
+ /**
+ * When the threshold received in {@link #POST_REFRESH_RECEIVE_DISPLAY_NOTIFY} is displayed.
+ */
+ public final static int THRESH_HIT = 3;
+ }
+
/**
* Intent extra data key: Use this key with Intent.ACTION_SEARCH and
* {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
@@ -1210,6 +1283,41 @@ public class SearchManager
*/
public final static String SHORTCUT_MIME_TYPE =
"vnd.android.cursor.item/vnd.android.search.suggest";
+
+
+ /**
+ * The authority of the provider to report clicks to when a click is detected after pivoting
+ * into a specific app's search from global search.
+ *
+ * In addition to the columns below, the suggestion columns are used to pass along the full
+ * suggestion so it can be shortcutted.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_AUTHORITY =
+ "com.android.globalsearch.stats";
+
+ /**
+ * The path the write goes to.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_URI_PATH = "click";
+
+ /**
+ * The column storing the query for the click.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_COLUMN_QUERY = "query";
+
+ /**
+ * The column storing the component name of the application that was pivoted into.
+ *
+ * @hide
+ */
+ public final static String SEARCH_CLICK_REPORT_COLUMN_COMPONENT = "component";
+
/**
* Column name for suggestions cursor. <i>Unused - can be null or column can be omitted.</i>
*/
@@ -1258,28 +1366,6 @@ public class SearchManager
*/
public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
/**
- * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
- * then all suggestions will be provided in a format that includes space for two small icons,
- * one at the left and one at the right of each suggestion. The data in the column must
- * be a blob that contains a bitmap.
- *
- * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_1} column.
- *
- * @hide
- */
- public final static String SUGGEST_COLUMN_ICON_1_BITMAP = "suggest_icon_1_bitmap";
- /**
- * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column,
- * then all suggestions will be provided in a format that includes space for two small icons,
- * one at the left and one at the right of each suggestion. The data in the column must
- * be a blob that contains a bitmap.
- *
- * This column overrides any icon provided in the {@link #SUGGEST_COLUMN_ICON_2} column.
- *
- * @hide
- */
- public final static String SUGGEST_COLUMN_ICON_2_BITMAP = "suggest_icon_2_bitmap";
- /**
* Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i>
* this element exists at the given row, this is the action that will be used when
* forming the suggestion's intent. If the element is not provided, the action will be taken
@@ -1300,13 +1386,24 @@ public class SearchManager
*/
public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data";
/**
+ * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i>
+ * this element exists at the given row, this is the data that will be used when
+ * forming the suggestion's intent. If not provided, the Intent's extra data field will be null.
+ * This column allows suggestions to provide additional arbitrary data which will be included as
+ * an extra under the key EXTRA_DATA_KEY.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
+ /**
* Column name for suggestions cursor. <i>Optional.</i> This column allows suggestions
* to provide additional arbitrary data which will be included as an extra under the key
- * {@link #EXTRA_DATA_KEY}.
- *
- * @hide pending API council approval
+ * {@link #COMPONENT_NAME_KEY}. For use by the global search system only - if other providers
+ * attempt to use this column, the value will be overwritten by global search.
+ *
+ * @hide
*/
- public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
+ public final static String SUGGEST_COLUMN_INTENT_COMPONENT_NAME = "suggest_intent_component";
/**
* Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i>
* this element exists at the given row, then "/" and this value will be appended to the data
@@ -1335,6 +1432,25 @@ public class SearchManager
public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id";
/**
+ * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify the
+ * cursor item's background color if it needs a non-default background color. A non-zero value
+ * indicates a valid background color to override the default.
+ *
+ * @hide For internal use, not part of the public API.
+ */
+ public final static String SUGGEST_COLUMN_BACKGROUND_COLOR = "suggest_background_color";
+
+ /**
+ * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
+ * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion
+ * is being refreshed.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING =
+ "suggest_spinner_while_refreshing";
+
+ /**
* Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion
* should not be stored as a shortcut in global search.
*
@@ -1362,21 +1478,7 @@ public class SearchManager
*/
public final static String INTENT_ACTION_CHANGE_SEARCH_SOURCE
= "android.search.action.CHANGE_SEARCH_SOURCE";
-
- /**
- * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
- * the search dialog will call {@link Cursor#respond(Bundle)} when the
- * suggestion is clicked.
- *
- * The {@link Bundle} argument will be constructed
- * in the same way as the "extra" bundle included in an Intent constructed
- * from the suggestion.
- *
- * @hide Pending API council approval.
- */
- public final static String INTENT_ACTION_CURSOR_RESPOND
- = "android.search.action.CURSOR_RESPOND";
-
+
/**
* Intent action for finding the global search activity.
* The global search provider should handle this intent.
@@ -1396,21 +1498,53 @@ public class SearchManager
= "android.search.action.SEARCH_SETTINGS";
/**
+ * Intent action for starting a web search provider's settings activity.
+ * Web search providers should handle this intent if they have provider-specific
+ * settings to implement.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS
+ = "android.search.action.WEB_SEARCH_SETTINGS";
+
+ /**
+ * Intent action broadcasted to inform that the searchables list or default have changed.
+ * Components should handle this intent if they cache any searchable data and wish to stay
+ * up to date on changes.
+ *
+ * @hide Pending API council approval.
+ */
+ public final static String INTENT_ACTION_SEARCHABLES_CHANGED
+ = "android.search.action.SEARCHABLES_CHANGED";
+
+ /**
+ * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION},
+ * the search dialog will take no action.
+ *
+ * @hide
+ */
+ public final static String INTENT_ACTION_NONE = "android.search.action.ZILCH";
+
+ /**
* Reference to the shared system search service.
*/
- private static ISearchManager sService = getSearchManagerService();
+ private static ISearchManager mService;
private final Context mContext;
- private final Handler mHandler;
-
- private SearchDialog mSearchDialog;
-
- private OnDismissListener mDismissListener = null;
- private OnCancelListener mCancelListener = null;
+
+ // package private since they are used by the inner class SearchManagerCallback
+ /* package */ boolean mIsShowing = false;
+ /* package */ final Handler mHandler;
+ /* package */ OnDismissListener mDismissListener = null;
+ /* package */ OnCancelListener mCancelListener = null;
+
+ private final SearchManagerCallback mSearchManagerCallback = new SearchManagerCallback();
/*package*/ SearchManager(Context context, Handler handler) {
mContext = context;
mHandler = handler;
+ mService = ISearchManager.Stub.asInterface(
+ ServiceManager.getService(Context.SEARCH_SERVICE));
}
/**
@@ -1458,17 +1592,16 @@ public class SearchManager
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch) {
-
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
+ if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing);
+ if (mIsShowing) return;
+ try {
+ mIsShowing = true;
+ // activate the search manager and start it up!
+ mService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch, mSearchManagerCallback);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "startSearch() failed: " + ex);
}
-
- // activate the search manager and start it up!
- mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
- globalSearch);
-
- mSearchDialog.setOnCancelListener(this);
- mSearchDialog.setOnDismissListener(this);
}
/**
@@ -1482,9 +1615,16 @@ public class SearchManager
*
* @see #startSearch
*/
- public void stopSearch() {
- if (mSearchDialog != null) {
- mSearchDialog.cancel();
+ public void stopSearch() {
+ if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ mService.stopSearch();
+ // onDismiss will also clear this, but we do it here too since onDismiss() is
+ // called asynchronously.
+ mIsShowing = false;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "stopSearch() failed: " + ex);
}
}
@@ -1497,33 +1637,33 @@ public class SearchManager
*
* @hide
*/
- public boolean isVisible() {
- if (mSearchDialog != null) {
- return mSearchDialog.isShowing();
- }
- return false;
+ public boolean isVisible() {
+ if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing);
+ return mIsShowing;
}
-
+
/**
- * See {@link #setOnDismissListener} for configuring your activity to monitor search UI state.
+ * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
+ * search UI state.
*/
public interface OnDismissListener {
/**
- * This method will be called when the search UI is dismissed. To make use if it, you must
- * implement this method in your activity, and call {@link #setOnDismissListener} to
- * register it.
+ * This method will be called when the search UI is dismissed. To make use of it, you must
+ * implement this method in your activity, and call
+ * {@link SearchManager#setOnDismissListener} to register it.
*/
public void onDismiss();
}
/**
- * See {@link #setOnCancelListener} for configuring your activity to monitor search UI state.
+ * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
+ * search UI state.
*/
public interface OnCancelListener {
/**
* This method will be called when the search UI is canceled. To make use if it, you must
- * implement this method in your activity, and call {@link #setOnCancelListener} to
- * register it.
+ * implement this method in your activity, and call
+ * {@link SearchManager#setOnCancelListener} to register it.
*/
public void onCancel();
}
@@ -1536,84 +1676,112 @@ public class SearchManager
public void setOnDismissListener(final OnDismissListener listener) {
mDismissListener = listener;
}
-
- /**
- * The callback from the search dialog when dismissed
- * @hide
- */
- public void onDismiss(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss();
- }
- }
- }
/**
* Set or clear the callback that will be invoked whenever the search UI is canceled.
*
* @param listener The {@link OnCancelListener} to use, or null.
*/
- public void setOnCancelListener(final OnCancelListener listener) {
+ public void setOnCancelListener(OnCancelListener listener) {
mCancelListener = listener;
}
-
-
- /**
- * The callback from the search dialog when canceled
- * @hide
- */
- public void onCancel(DialogInterface dialog) {
- if (dialog == mSearchDialog) {
- if (mCancelListener != null) {
- mCancelListener.onCancel();
+
+ private class SearchManagerCallback extends ISearchManagerCallback.Stub {
+
+ private final Runnable mFireOnDismiss = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnDismiss");
+ mIsShowing = false;
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss();
+ }
+ }
+ };
+
+ private final Runnable mFireOnCancel = new Runnable() {
+ public void run() {
+ if (DBG) debug("mFireOnCancel");
+ // doesn't need to clear mIsShowing since onDismiss() always gets called too
+ if (mCancelListener != null) {
+ mCancelListener.onCancel();
+ }
}
+ };
+
+ public void onDismiss() {
+ if (DBG) debug("onDismiss()");
+ mHandler.post(mFireOnDismiss);
+ }
+
+ public void onCancel() {
+ if (DBG) debug("onCancel()");
+ mHandler.post(mFireOnCancel);
}
+
+ }
+
+ // TODO: remove the DialogInterface interfaces from SearchManager.
+ // This changes the public API, so I'll do it in a separate change.
+ public void onCancel(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
+ }
+ public void onDismiss(DialogInterface dialog) {
+ throw new UnsupportedOperationException();
}
/**
- * Save instance state so we can recreate after a rotation.
- *
+ * Saves the state of the search UI.
+ *
+ * @return A Bundle containing the state of the search dialog, or {@code null}
+ * if the search UI is not visible.
+ *
* @hide
*/
- void saveSearchDialog(Bundle outState, String key) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- Bundle searchDialogState = mSearchDialog.onSaveInstanceState();
- outState.putBundle(key, searchDialogState);
+ public Bundle saveSearchDialog() {
+ if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return null;
+ try {
+ return mService.onSaveInstanceState();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onSaveInstanceState() failed: " + ex);
+ return null;
}
}
/**
- * Restore instance state after a rotation.
- *
+ * Restores the state of the search dialog.
+ *
+ * @param searchDialogState Bundle to read the state from.
+ *
* @hide
*/
- void restoreSearchDialog(Bundle inState, String key) {
- Bundle searchDialogState = inState.getBundle(key);
- if (searchDialogState != null) {
- if (mSearchDialog == null) {
- mSearchDialog = new SearchDialog(mContext);
- }
- mSearchDialog.onRestoreInstanceState(searchDialogState);
+ public void restoreSearchDialog(Bundle searchDialogState) {
+ if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")");
+ if (searchDialogState == null) return;
+ try {
+ mService.onRestoreInstanceState(searchDialogState);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onRestoreInstanceState() failed: " + ex);
}
}
-
+
/**
- * Hook for updating layout on a rotation
- *
+ * Update the search dialog after a configuration change.
+ *
+ * @param newConfig The new configuration.
+ *
* @hide
*/
- void onConfigurationChanged(Configuration newConfig) {
- if (mSearchDialog != null && mSearchDialog.isShowing()) {
- mSearchDialog.onConfigurationChanged(newConfig);
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing);
+ if (!mIsShowing) return;
+ try {
+ mService.onConfigurationChanged(newConfig);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onConfigurationChanged() failed:" + ex);
}
}
-
- private static ISearchManager getSearchManagerService() {
- return ISearchManager.Stub.asInterface(
- ServiceManager.getService(Context.SEARCH_SERVICE));
- }
-
+
/**
* Gets information about a searchable activity. This method is static so that it can
* be used from non-Activity contexts.
@@ -1625,11 +1793,12 @@ public class SearchManager
*
* @hide because SearchableInfo is not part of the API.
*/
- public static SearchableInfo getSearchableInfo(ComponentName componentName,
+ public SearchableInfo getSearchableInfo(ComponentName componentName,
boolean globalSearch) {
try {
- return sService.getSearchableInfo(componentName, globalSearch);
- } catch (RemoteException e) {
+ return mService.getSearchableInfo(componentName, globalSearch);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSearchableInfo() failed: " + ex);
return null;
}
}
@@ -1639,23 +1808,22 @@ public class SearchManager
*
* @hide because SearchableInfo is not part of the API.
*/
- public static boolean isDefaultSearchable(SearchableInfo searchable) {
- SearchableInfo defaultSearchable = SearchManager.getSearchableInfo(null, true);
+ public boolean isDefaultSearchable(SearchableInfo searchable) {
+ SearchableInfo defaultSearchable = getSearchableInfo(null, true);
return defaultSearchable != null
&& defaultSearchable.getSearchActivity().equals(searchable.getSearchActivity());
}
-
+
/**
- * Gets a cursor with search suggestions. This method is static so that it can
- * be used from non-Activity context.
+ * Gets a cursor with search suggestions.
*
* @param searchable Information about how to get the suggestions.
* @param query The search text entered (so far).
- * @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
- *
+ * @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
+ *
* @hide because SearchableInfo is not part of the API.
*/
- public static Cursor getSuggestions(Context context, SearchableInfo searchable, String query) {
+ public Cursor getSuggestions(SearchableInfo searchable, String query) {
if (searchable == null) {
return null;
}
@@ -1694,7 +1862,7 @@ public class SearchManager
.build();
// finally, make the query
- return context.getContentResolver().query(uri, null, selection, selArgs, null);
+ return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
}
/**
@@ -1706,11 +1874,65 @@ public class SearchManager
*
* @hide because SearchableInfo is not part of the API.
*/
- public static List<SearchableInfo> getSearchablesInGlobalSearch() {
+ public List<SearchableInfo> getSearchablesInGlobalSearch() {
try {
- return sService.getSearchablesInGlobalSearch();
+ return mService.getSearchablesInGlobalSearch();
} catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e);
return null;
}
}
+
+ /**
+ * Returns a list of the searchable activities that handle web searches.
+ *
+ * @return a list of all searchable activities that handle
+ * {@link android.content.Intent#ACTION_WEB_SEARCH}.
+ *
+ * @hide because SearchableInfo is not part of the API.
+ */
+ public List<SearchableInfo> getSearchablesForWebSearch() {
+ try {
+ return mService.getSearchablesForWebSearch();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getSearchablesForWebSearch() failed: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the default searchable activity for web searches.
+ *
+ * @return searchable information for the activity handling web searches by default.
+ *
+ * @hide because SearchableInfo is not part of the API.
+ */
+ public SearchableInfo getDefaultSearchableForWebSearch() {
+ try {
+ return mService.getDefaultSearchableForWebSearch();
+ } catch (RemoteException e) {
+ Log.e(TAG, "getDefaultSearchableForWebSearch() failed: " + e);
+ return null;
+ }
+ }
+
+ /**
+ * Sets the default searchable activity for web searches.
+ *
+ * @param component Name of the component to set as default activity for web searches.
+ *
+ * @hide
+ */
+ public void setDefaultWebSearch(ComponentName component) {
+ try {
+ mService.setDefaultWebSearch(component);
+ } catch (RemoteException e) {
+ Log.e(TAG, "setDefaultWebSearch() failed: " + e);
+ }
+ }
+
+ private static void debug(String msg) {
+ Thread thread = Thread.currentThread();
+ Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ }
}
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 6a02fc9..49c94d1 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -20,62 +20,103 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources.NotFoundException;
import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.server.search.SearchableInfo;
import android.text.Html;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CursorAdapter;
+import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ResourceCursorAdapter;
import android.widget.TextView;
+import static android.app.SearchManager.DialogCursorProtocol;
+
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.WeakHashMap;
/**
* Provides the contents for the suggestion drop-down list.in {@link SearchDialog}.
- *
+ *
* @hide
*/
class SuggestionsAdapter extends ResourceCursorAdapter {
+
private static final boolean DBG = false;
private static final String LOG_TAG = "SuggestionsAdapter";
-
+
+ private SearchManager mSearchManager;
+ private SearchDialog mSearchDialog;
private SearchableInfo mSearchable;
private Context mProviderContext;
private WeakHashMap<String, Drawable> mOutsideDrawablesCache;
+ private boolean mGlobalSearchMode;
- // Cached column indexes, updated when the cursor changes.
+ // Cached column indexes, updated when the cursor changes.
private int mFormatCol;
private int mText1Col;
private int mText2Col;
private int mIconName1Col;
private int mIconName2Col;
- private int mIconBitmap1Col;
- private int mIconBitmap2Col;
-
- public SuggestionsAdapter(Context context, SearchableInfo searchable,
- WeakHashMap<String, Drawable> outsideDrawablesCache) {
+ private int mBackgroundColorCol;
+
+ // This value is stored in SuggestionsAdapter by the SearchDialog to indicate whether
+ // a particular list item should be selected upon the next call to notifyDataSetChanged.
+ // This is used to indicate the index of the "More results..." list item so that when
+ // the data set changes after a click of "More results...", we can correctly tell the
+ // ListView to scroll to the right line item. It gets reset to NONE every time it
+ // is consumed.
+ private int mListItemToSelect = NONE;
+ static final int NONE = -1;
+
+ // holds the maximum position that has been displayed to the user
+ int mMaxDisplayed = NONE;
+
+ // holds the position that, when displayed, should result in notifying the cursor
+ int mDisplayNotifyPos = NONE;
+
+ private final Runnable mStartSpinnerRunnable;
+ private final Runnable mStopSpinnerRunnable;
+
+ public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable,
+ WeakHashMap<String, Drawable> outsideDrawablesCache, boolean globalSearchMode) {
super(context,
com.android.internal.R.layout.search_dropdown_item_icons_2line,
null, // no initial cursor
true); // auto-requery
+ mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+ mSearchDialog = searchDialog;
mSearchable = searchable;
-
+
// set up provider resources (gives us icons, etc.)
Context activityContext = mSearchable.getActivityContext(mContext);
mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
-
+
mOutsideDrawablesCache = outsideDrawablesCache;
+ mGlobalSearchMode = globalSearchMode;
+
+ mStartSpinnerRunnable = new Runnable() {
+ public void run() {
+ mSearchDialog.setWorking(true);
+ }
+ };
+
+ mStopSpinnerRunnable = new Runnable() {
+ public void run() {
+ mSearchDialog.setWorking(false);
+ }
+ };
}
-
+
/**
* Overridden to always return <code>false</code>, since we cannot be sure that
* suggestion sources return stable IDs.
@@ -94,20 +135,41 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
String query = (constraint == null) ? "" : constraint.toString();
+ if (!mGlobalSearchMode) {
+ /**
+ * for in app search we show the progress spinner until the cursor is returned with
+ * the results. for global search we manage the progress bar using
+ * {@link DialogCursorProtocol#POST_REFRESH_RECEIVE_ISPENDING}.
+ */
+ mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
+ }
try {
- return SearchManager.getSuggestions(mContext, mSearchable, query);
+ final Cursor cursor = mSearchManager.getSuggestions(mSearchable, query);
+ // trigger fill window so the spinner stays up until the results are copied over and
+ // closer to being ready
+ if (!mGlobalSearchMode && cursor != null) cursor.getCount();
+ return cursor;
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
return null;
+ } finally {
+ if (!mGlobalSearchMode) {
+ mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
+ }
}
}
-
+
/**
* Cache columns.
*/
@Override
public void changeCursor(Cursor c) {
if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
+
+ if (mCursor != null) {
+ callCursorPreClose(mCursor);
+ }
+
super.changeCursor(c);
if (c != null) {
mFormatCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FORMAT);
@@ -115,21 +177,86 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
- mIconBitmap1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1_BITMAP);
- mIconBitmap2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2_BITMAP);
+ mBackgroundColorCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_BACKGROUND_COLOR);
+ }
+ }
+
+ /**
+ * Handle sending and receiving information associated with
+ * {@link DialogCursorProtocol#PRE_CLOSE}.
+ *
+ * @param cursor The cursor to call.
+ */
+ private void callCursorPreClose(Cursor cursor) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.PRE_CLOSE);
+ request.putInt(DialogCursorProtocol.PRE_CLOSE_SEND_MAX_DISPLAY_POS, mMaxDisplayed);
+ final Bundle response = cursor.respond(request);
+
+ mMaxDisplayed = -1;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
+ super.notifyDataSetChanged();
+
+ callCursorPostRefresh(mCursor);
+
+ // look out for the pending item we are supposed to scroll to
+ if (mListItemToSelect != NONE) {
+ mSearchDialog.setListSelection(mListItemToSelect);
+ mListItemToSelect = NONE;
}
}
-
+
+ /**
+ * Handle sending and receiving information associated with
+ * {@link DialogCursorProtocol#POST_REFRESH}.
+ *
+ * @param cursor The cursor to call.
+ */
+ private void callCursorPostRefresh(Cursor cursor) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.POST_REFRESH);
+ final Bundle response = cursor.respond(request);
+
+ mSearchDialog.setWorking(
+ response.getBoolean(DialogCursorProtocol.POST_REFRESH_RECEIVE_ISPENDING, false));
+
+ mDisplayNotifyPos =
+ response.getInt(DialogCursorProtocol.POST_REFRESH_RECEIVE_DISPLAY_NOTIFY, -1);
+ }
+
+ /**
+ * Tell the cursor which position was clicked, handling sending and receiving information
+ * associated with {@link DialogCursorProtocol#CLICK}.
+ *
+ * @param cursor The cursor
+ * @param position The position that was clicked.
+ */
+ void callCursorOnClick(Cursor cursor, int position) {
+ if (!mGlobalSearchMode) return;
+ final Bundle request = new Bundle(1);
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.CLICK);
+ request.putInt(DialogCursorProtocol.CLICK_SEND_POSITION, position);
+ final Bundle response = cursor.respond(request);
+ mListItemToSelect = response.getInt(
+ DialogCursorProtocol.CLICK_RECEIVE_SELECTED_POS, SuggestionsAdapter.NONE);
+ }
+
/**
* Tags the view with cached child view look-ups.
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View v = super.newView(context, cursor, parent);
+ View v = new SuggestionItemView(context, cursor);
v.setTag(new ChildViewCache(v));
return v;
}
-
+
/**
* Cache of the child views of drop-drown list items, to avoid looking up the children
* each time the contents of a list item are changed.
@@ -139,7 +266,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
public final TextView mText2;
public final ImageView mIcon1;
public final ImageView mIcon2;
-
+
public ChildViewCache(View v) {
mText1 = (TextView) v.findViewById(com.android.internal.R.id.text1);
mText2 = (TextView) v.findViewById(com.android.internal.R.id.text2);
@@ -147,21 +274,38 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
mIcon2 = (ImageView) v.findViewById(com.android.internal.R.id.icon2);
}
}
-
+
@Override
public void bindView(View view, Context context, Cursor cursor) {
ChildViewCache views = (ChildViewCache) view.getTag();
- boolean isHtml = false;
- if (mFormatCol >= 0) {
- String format = cursor.getString(mFormatCol);
- isHtml = "html".equals(format);
+ final int pos = cursor.getPosition();
+
+ // update the maximum position displayed since last refresh
+ if (pos > mMaxDisplayed) {
+ mMaxDisplayed = pos;
}
+
+ // if the cursor wishes to be notified about this position, send it
+ if (mGlobalSearchMode && mDisplayNotifyPos != NONE && pos == mDisplayNotifyPos) {
+ final Bundle request = new Bundle();
+ request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.THRESH_HIT);
+ mCursor.respond(request);
+ mDisplayNotifyPos = NONE; // only notify the first time
+ }
+
+ int backgroundColor = 0;
+ if (mBackgroundColorCol != -1) {
+ backgroundColor = cursor.getInt(mBackgroundColorCol);
+ }
+ ((SuggestionItemView)view).setColor(backgroundColor);
+
+ final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol));
setViewText(cursor, views.mText1, mText1Col, isHtml);
setViewText(cursor, views.mText2, mText2Col, isHtml);
- setViewIcon(cursor, views.mIcon1, mIconBitmap1Col, mIconName1Col);
- setViewIcon(cursor, views.mIcon2, mIconBitmap2Col, mIconName2Col);
+ setViewIcon(cursor, views.mIcon1, mIconName1Col);
+ setViewIcon(cursor, views.mIcon2, mIconName2Col);
}
-
+
private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) {
if (v == null) {
return;
@@ -173,49 +317,46 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
}
// Set the text even if it's null, since we need to clear any previous text.
v.setText(text);
-
+
if (TextUtils.isEmpty(text)) {
v.setVisibility(View.GONE);
} else {
v.setVisibility(View.VISIBLE);
}
}
-
- private void setViewIcon(Cursor cursor, ImageView v, int iconBitmapCol, int iconNameCol) {
+
+ private void setViewIcon(Cursor cursor, ImageView v, int iconNameCol) {
if (v == null) {
return;
}
- Drawable drawable = null;
- // First try the bitmap column
- if (iconBitmapCol >= 0) {
- byte[] data = cursor.getBlob(iconBitmapCol);
- if (data != null) {
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- if (bitmap != null) {
- drawable = new BitmapDrawable(bitmap);
- }
- }
- }
- // If there was no bitmap, try the icon resource column.
- if (drawable == null && iconNameCol >= 0) {
- String value = cursor.getString(iconNameCol);
- drawable = getDrawableFromResourceValue(value);
+ if (iconNameCol < 0) {
+ return;
}
+ String value = cursor.getString(iconNameCol);
+ Drawable drawable = getDrawableFromResourceValue(value);
// Set the icon even if the drawable is null, since we need to clear any
// previous icon.
v.setImageDrawable(drawable);
-
+
if (drawable == null) {
v.setVisibility(View.GONE);
} else {
v.setVisibility(View.VISIBLE);
+
+ // This is a hack to get any animated drawables (like a 'working' spinner)
+ // to animate. You have to setVisible true on an AnimationDrawable to get
+ // it to start animating, but it must first have been false or else the
+ // call to setVisible will be ineffective. We need to clear up the story
+ // about animated drawables in the future, see http://b/1878430.
+ drawable.setVisible(false, false);
+ drawable.setVisible(true, false);
}
}
-
+
/**
* Gets the text to show in the query field when a suggestion is selected.
- *
- * @param cursor The Cursor to read the suggestion data from. The Cursor should already
+ *
+ * @param cursor The Cursor to read the suggestion data from. The Cursor should already
* be moved to the suggestion that is to be read from.
* @return The text to show, or <code>null</code> if the query should not be
* changed when selecting this suggestion.
@@ -225,36 +366,36 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
if (cursor == null) {
return null;
}
-
+
String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
if (query != null) {
return query;
}
-
+
if (mSearchable.shouldRewriteQueryFromData()) {
String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
if (data != null) {
return data;
}
}
-
+
if (mSearchable.shouldRewriteQueryFromText()) {
String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1);
if (text1 != null) {
return text1;
}
}
-
+
return null;
}
-
+
/**
* This method is overridden purely to provide a bit of protection against
* flaky content providers.
- *
+ *
* @see android.widget.ListAdapter#getView(int, View, ViewGroup)
*/
- @Override
+ @Override
public View getView(int position, View convertView, ViewGroup parent) {
try {
return super.getView(position, convertView, parent);
@@ -263,28 +404,28 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
// Put exception string in item title
View v = newView(mContext, mCursor, parent);
if (v != null) {
- ChildViewCache views = (ChildViewCache) v.getTag();
+ ChildViewCache views = (ChildViewCache) v.getTag();
TextView tv = views.mText1;
tv.setText(e.toString());
}
return v;
}
}
-
+
/**
* Gets a drawable given a value provided by a suggestion provider.
- *
+ *
* This value could be just the string value of a resource id
* (e.g., "2130837524"), in which case we will try to retrieve a drawable from
* the provider's resources. If the value is not an integer, it is
- * treated as a Uri and opened with
+ * treated as a Uri and opened with
* {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
*
* All resources and URIs are read using the suggestion provider's context.
*
* If the string is not formatted as expected, or no drawable can be found for
* the provided value, this method returns null.
- *
+ *
* @param drawableId a string like "2130837524",
* "android.resource://com.android.alarmclock/2130837524",
* or "content://contacts/photos/253".
@@ -294,43 +435,58 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
return null;
}
-
+
// First, check the cache.
Drawable drawable = mOutsideDrawablesCache.get(drawableId);
- if (drawable != null) return drawable;
+ if (drawable != null) {
+ if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + drawableId);
+ return drawable;
+ }
try {
// Not cached, try using it as a plain resource ID in the provider's context.
int resourceId = Integer.parseInt(drawableId);
drawable = mProviderContext.getResources().getDrawable(resourceId);
+ if (DBG) Log.d(LOG_TAG, "Found icon by resource ID: " + drawableId);
} catch (NumberFormatException nfe) {
// The id was not an integer resource id.
// Let the ContentResolver handle content, android.resource and file URIs.
try {
Uri uri = Uri.parse(drawableId);
- drawable = Drawable.createFromStream(
- mProviderContext.getContentResolver().openInputStream(uri),
- null);
+ InputStream stream = mProviderContext.getContentResolver().openInputStream(uri);
+ if (stream != null) {
+ try {
+ drawable = Drawable.createFromStream(stream, null);
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException ex) {
+ Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex);
+ }
+ }
+ }
+ if (DBG) Log.d(LOG_TAG, "Opened icon input stream: " + drawableId);
} catch (FileNotFoundException fnfe) {
+ if (DBG) Log.d(LOG_TAG, "Icon stream not found: " + drawableId);
// drawable = null;
}
-
+
// If we got a drawable for this resource id, then stick it in the
// map so we don't do this lookup again.
if (drawable != null) {
mOutsideDrawablesCache.put(drawableId, drawable);
}
} catch (NotFoundException nfe) {
- // Resource could not be found
+ if (DBG) Log.d(LOG_TAG, "Icon resource not found: " + drawableId);
// drawable = null;
}
-
+
return drawable;
}
-
+
/**
* Gets the value of a string column by name.
- *
+ *
* @param cursor Cursor to read the value from.
* @param columnName The name of the column to read.
* @return The value of the given column, or <code>null</null>
@@ -338,10 +494,75 @@ class SuggestionsAdapter extends ResourceCursorAdapter {
*/
public static String getColumnString(Cursor cursor, String columnName) {
int col = cursor.getColumnIndex(columnName);
- if (col == -1) {
+ if (col == NONE) {
return null;
}
return cursor.getString(col);
}
+ /**
+ * A parent viewgroup class which holds the actual suggestion item as a child.
+ *
+ * The sole purpose of this class is to draw the given background color when the item is in
+ * normal state and not draw the background color when it is pressed, so that when pressed the
+ * list view's selection highlight will be displayed properly (if we draw our background it
+ * draws on top of the list view selection highlight).
+ */
+ private class SuggestionItemView extends ViewGroup {
+ private int mBackgroundColor; // the background color to draw in normal state.
+ private View mView; // the suggestion item's view.
+
+ protected SuggestionItemView(Context context, Cursor cursor) {
+ // Initialize ourselves
+ super(context);
+ mBackgroundColor = 0; // transparent by default.
+
+ // For our layout use the default list item height from the current theme.
+ TypedValue lineHeight = new TypedValue();
+ context.getTheme().resolveAttribute(
+ com.android.internal.R.attr.searchResultListItemHeight, lineHeight, true);
+ DisplayMetrics metrics = new DisplayMetrics();
+ metrics.setToDefaults();
+ AbsListView.LayoutParams layout = new AbsListView.LayoutParams(
+ AbsListView.LayoutParams.FILL_PARENT,
+ (int)lineHeight.getDimension(metrics));
+
+ setLayoutParams(layout);
+
+ // Initialize the child view
+ mView = SuggestionsAdapter.super.newView(context, cursor, this);
+ if (mView != null) {
+ addView(mView, layout.width, layout.height);
+ mView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ public void setColor(int backgroundColor) {
+ mBackgroundColor = backgroundColor;
+ }
+
+ @Override
+ public void dispatchDraw(Canvas canvas) {
+ if (mBackgroundColor != 0 && !isPressed() && !isSelected()) {
+ canvas.drawColor(mBackgroundColor);
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mView != null) {
+ mView.measure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (mView != null) {
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+ }
+ }
+ }
+
}
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 10c2b02..03e8623 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -26,7 +26,6 @@ import android.widget.RemoteViews;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
@@ -40,7 +39,7 @@ public class AppWidgetHost {
static final int HANDLE_UPDATE = 1;
static final int HANDLE_PROVIDER_CHANGED = 2;
- static Object sServiceLock = new Object();
+ final static Object sServiceLock = new Object();
static IAppWidgetService sService;
Context mContext;
@@ -85,7 +84,7 @@ public class AppWidgetHost {
int mHostId;
Callbacks mCallbacks = new Callbacks();
- HashMap<Integer,AppWidgetHostView> mViews = new HashMap();
+ final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
public AppWidgetHost(Context context, int hostId) {
mContext = context;
@@ -104,8 +103,8 @@ public class AppWidgetHost {
* becomes visible, i.e. from onStart() in your Activity.
*/
public void startListening() {
- int[] updatedIds = null;
- ArrayList<RemoteViews> updatedViews = new ArrayList();
+ int[] updatedIds;
+ ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
try {
if (mPackageName == null) {
@@ -209,7 +208,7 @@ public class AppWidgetHost {
synchronized (mViews) {
mViews.put(appWidgetId, view);
}
- RemoteViews views = null;
+ RemoteViews views;
try {
views = sService.getAppWidgetViews(appWidgetId);
} catch (RemoteException e) {
@@ -231,6 +230,7 @@ public class AppWidgetHost {
/**
* Called when the AppWidget provider for a AppWidget has been upgraded to a new apk.
*/
+ @SuppressWarnings({"UnusedDeclaration"})
protected void onProviderChanged(int appWidgetId, AppWidgetProviderInfo appWidget) {
}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index be0f96e..62d9267 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -22,16 +22,12 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.os.Handler;
-import android.os.Message;
import android.os.SystemClock;
-import android.util.Config;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Animation;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -86,6 +82,7 @@ public class AppWidgetHostView extends FrameLayout {
* @param animationIn Resource ID of in animation to use
* @param animationOut Resource ID of out animation to use
*/
+ @SuppressWarnings({"UnusedDeclaration"})
public AppWidgetHostView(Context context, int animationIn, int animationOut) {
super(context);
mContext = context;
@@ -272,7 +269,7 @@ public class AppWidgetHostView extends FrameLayout {
try {
if (mInfo != null) {
Context theirContext = mContext.createPackageContext(
- mInfo.provider.getPackageName(), 0 /* no flags */);
+ mInfo.provider.getPackageName(), Context.CONTEXT_RESTRICTED);
LayoutInflater inflater = (LayoutInflater)
theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(theirContext);
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eca04b3..3660001 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -21,7 +21,9 @@ import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetService;
@@ -187,6 +189,8 @@ public class AppWidgetManager {
Context mContext;
+ private DisplayMetrics mDisplayMetrics;
+
/**
* Get the AppWidgetManager instance to use for the supplied {@link android.content.Context
* Context} object.
@@ -213,6 +217,7 @@ public class AppWidgetManager {
private AppWidgetManager(Context context) {
mContext = context;
+ mDisplayMetrics = context.getResources().getDisplayMetrics();
}
/**
@@ -292,7 +297,15 @@ public class AppWidgetManager {
*/
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
try {
- return sService.getAppWidgetInfo(appWidgetId);
+ AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId);
+ if (info != null) {
+ // Converting complex to dp.
+ info.minWidth =
+ TypedValue.complexToDimensionPixelSize(info.minWidth, mDisplayMetrics);
+ info.minHeight =
+ TypedValue.complexToDimensionPixelSize(info.minHeight, mDisplayMetrics);
+ }
+ return info;
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/backup/AbsoluteFileBackupHelper.java b/core/java/android/backup/AbsoluteFileBackupHelper.java
new file mode 100644
index 0000000..ab24675
--- /dev/null
+++ b/core/java/android/backup/AbsoluteFileBackupHelper.java
@@ -0,0 +1,66 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileDescriptor;
+
+/**
+ * Like FileBackupHelper, but takes absolute paths for the files instead of
+ * subpaths of getFilesDir()
+ *
+ * @hide
+ */
+public class AbsoluteFileBackupHelper extends FileBackupHelperBase implements BackupHelper {
+ private static final String TAG = "AbsoluteFileBackupHelper";
+
+ Context mContext;
+ String[] mFiles;
+
+ public AbsoluteFileBackupHelper(Context context, String... files) {
+ super(context);
+
+ mContext = context;
+ mFiles = files;
+ }
+
+ /**
+ * Based on oldState, determine which of the files from the application's data directory
+ * need to be backed up, write them to the data stream, and fill in newState with the
+ * state as it exists now.
+ */
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ // use the file paths as the keys, too
+ performBackup_checked(oldState, data, newState, mFiles, mFiles);
+ }
+
+ public void restoreEntity(BackupDataInputStream data) {
+ // TODO: turn this off before ship
+ Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
+ String key = data.getKey();
+ if (isKeyInList(key, mFiles)) {
+ File f = new File(key);
+ writeFile(f, data);
+ }
+ }
+}
+
diff --git a/core/java/android/backup/BackupDataInput.java b/core/java/android/backup/BackupDataInput.java
new file mode 100644
index 0000000..69c206c
--- /dev/null
+++ b/core/java/android/backup/BackupDataInput.java
@@ -0,0 +1,117 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/** @hide */
+public class BackupDataInput {
+ int mBackupReader;
+
+ private EntityHeader mHeader = new EntityHeader();
+ private boolean mHeaderReady;
+
+ private static class EntityHeader {
+ String key;
+ int dataSize;
+ }
+
+ public BackupDataInput(FileDescriptor fd) {
+ if (fd == null) throw new NullPointerException();
+ mBackupReader = ctor(fd);
+ if (mBackupReader == 0) {
+ throw new RuntimeException("Native initialization failed with fd=" + fd);
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mBackupReader);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ public boolean readNextHeader() throws IOException {
+ int result = readNextHeader_native(mBackupReader, mHeader);
+ if (result == 0) {
+ // read successfully
+ mHeaderReady = true;
+ return true;
+ } else if (result > 0) {
+ // done
+ mHeaderReady = false;
+ return false;
+ } else {
+ // error
+ mHeaderReady = false;
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ public String getKey() {
+ if (mHeaderReady) {
+ return mHeader.key;
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public int getDataSize() {
+ if (mHeaderReady) {
+ return mHeader.dataSize;
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public int readEntityData(byte[] data, int offset, int size) throws IOException {
+ if (mHeaderReady) {
+ int result = readEntityData_native(mBackupReader, data, offset, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ public void skipEntityData() throws IOException {
+ if (mHeaderReady) {
+ int result = skipEntityData_native(mBackupReader);
+ if (result >= 0) {
+ return;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ } else {
+ throw new IllegalStateException("mHeaderReady=false");
+ }
+ }
+
+ private native static int ctor(FileDescriptor fd);
+ private native static void dtor(int mBackupReader);
+
+ private native int readNextHeader_native(int mBackupReader, EntityHeader entity);
+ private native int readEntityData_native(int mBackupReader, byte[] data, int offset, int size);
+ private native int skipEntityData_native(int mBackupReader);
+}
diff --git a/core/java/android/backup/BackupDataInputStream.java b/core/java/android/backup/BackupDataInputStream.java
new file mode 100644
index 0000000..b705c4c
--- /dev/null
+++ b/core/java/android/backup/BackupDataInputStream.java
@@ -0,0 +1,63 @@
+/*
+ * 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.backup;
+
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+/** @hide */
+public class BackupDataInputStream extends InputStream {
+
+ String key;
+ int dataSize;
+
+ BackupDataInput mData;
+ byte[] mOneByte;
+
+ BackupDataInputStream(BackupDataInput data) {
+ mData = data;
+ }
+
+ public int read() throws IOException {
+ byte[] one = mOneByte;
+ if (mOneByte == null) {
+ one = mOneByte = new byte[1];
+ }
+ mData.readEntityData(one, 0, 1);
+ return one[0];
+ }
+
+ public int read(byte[] b, int offset, int size) throws IOException {
+ return mData.readEntityData(b, offset, size);
+ }
+
+ public int read(byte[] b) throws IOException {
+ return mData.readEntityData(b, 0, b.length);
+ }
+
+ public String getKey() {
+ return this.key;
+ }
+
+ public int size() {
+ return this.dataSize;
+ }
+}
+
+
diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java
index 555494e..d29c5ba 100644
--- a/core/java/android/backup/BackupDataOutput.java
+++ b/core/java/android/backup/BackupDataOutput.java
@@ -19,27 +19,59 @@ package android.backup;
import android.content.Context;
import java.io.FileDescriptor;
+import java.io.IOException;
/** @hide */
public class BackupDataOutput {
- /* package */ FileDescriptor fd;
+ int mBackupWriter;
public static final int OP_UPDATE = 1;
public static final int OP_DELETE = 2;
- public BackupDataOutput(Context context, FileDescriptor fd) {
- this.fd = fd;
+ public BackupDataOutput(FileDescriptor fd) {
+ if (fd == null) throw new NullPointerException();
+ mBackupWriter = ctor(fd);
+ if (mBackupWriter == 0) {
+ throw new RuntimeException("Native initialization failed with fd=" + fd);
+ }
}
- public void close() {
- // do we close the fd?
+ // A dataSize of -1 indicates that the record under this key should be deleted
+ public int writeEntityHeader(String key, int dataSize) throws IOException {
+ int result = writeEntityHeader_native(mBackupWriter, key, dataSize);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
}
- public native void flush();
- public native void write(byte[] buffer);
- public native void write(int oneByte);
- public native void write(byte[] buffer, int offset, int count);
- public native void writeOperation(int op);
- public native void writeKey(String key);
+ public int writeEntityData(byte[] data, int size) throws IOException {
+ int result = writeEntityData_native(mBackupWriter, data, size);
+ if (result >= 0) {
+ return result;
+ } else {
+ throw new IOException("result=0x" + Integer.toHexString(result));
+ }
+ }
+
+ public void setKeyPrefix(String keyPrefix) {
+ setKeyPrefix_native(mBackupWriter, keyPrefix);
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mBackupWriter);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private native static int ctor(FileDescriptor fd);
+ private native static void dtor(int mBackupWriter);
+
+ private native static int writeEntityHeader_native(int mBackupWriter, String key, int dataSize);
+ private native static int writeEntityData_native(int mBackupWriter, byte[] data, int size);
+ private native static void setKeyPrefix_native(int mBackupWriter, String keyPrefix);
}
diff --git a/core/java/android/backup/BackupHelper.java b/core/java/android/backup/BackupHelper.java
new file mode 100644
index 0000000..3983e28
--- /dev/null
+++ b/core/java/android/backup/BackupHelper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.backup;
+
+import android.os.ParcelFileDescriptor;
+
+import java.io.InputStream;
+
+/** @hide */
+public interface BackupHelper {
+ /**
+ * Based on oldState, determine which of the files from the application's data directory
+ * need to be backed up, write them to the data stream, and fill in newState with the
+ * state as it exists now.
+ */
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState);
+
+ /**
+ * Called by BackupHelperDispatcher to dispatch one entity of data.
+ * <p class=note>
+ * Do not close the <code>data</code> stream. Do not read more than
+ * <code>dataSize</code> bytes from <code>data</code>.
+ */
+ public void restoreEntity(BackupDataInputStream data);
+
+ /**
+ *
+ */
+ public void writeRestoreSnapshot(ParcelFileDescriptor fd);
+}
+
diff --git a/core/java/android/backup/BackupHelperAgent.java b/core/java/android/backup/BackupHelperAgent.java
new file mode 100644
index 0000000..5d0c4a2
--- /dev/null
+++ b/core/java/android/backup/BackupHelperAgent.java
@@ -0,0 +1,56 @@
+/*
+ * 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 android.backup;
+
+import android.app.BackupAgent;
+import android.backup.BackupHelper;
+import android.backup.BackupHelperDispatcher;
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.IOException;
+
+/** @hide */
+public class BackupHelperAgent extends BackupAgent {
+ static final String TAG = "BackupHelperAgent";
+
+ BackupHelperDispatcher mDispatcher = new BackupHelperDispatcher();
+
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ mDispatcher.performBackup(oldState, data, newState);
+ }
+
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ mDispatcher.performRestore(data, appVersionCode, newState);
+ }
+
+ public BackupHelperDispatcher getDispatcher() {
+ return mDispatcher;
+ }
+
+ public void addHelper(String keyPrefix, BackupHelper helper) {
+ mDispatcher.addHelper(keyPrefix, helper);
+ }
+}
+
+
diff --git a/core/java/android/backup/BackupHelperDispatcher.java b/core/java/android/backup/BackupHelperDispatcher.java
new file mode 100644
index 0000000..6ccb83e
--- /dev/null
+++ b/core/java/android/backup/BackupHelperDispatcher.java
@@ -0,0 +1,151 @@
+/*
+ * 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.backup;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.FileDescriptor;
+import java.util.TreeMap;
+import java.util.Map;
+
+/** @hide */
+public class BackupHelperDispatcher {
+ private static final String TAG = "BackupHelperDispatcher";
+
+ private static class Header {
+ int chunkSize; // not including the header
+ String keyPrefix;
+ }
+
+ TreeMap<String,BackupHelper> mHelpers = new TreeMap<String,BackupHelper>();
+
+ public BackupHelperDispatcher() {
+ }
+
+ public void addHelper(String keyPrefix, BackupHelper helper) {
+ mHelpers.put(keyPrefix, helper);
+ }
+
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ // First, do the helpers that we've already done, since they're already in the state
+ // file.
+ int err;
+ Header header = new Header();
+ TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone();
+ FileDescriptor oldStateFD = null;
+ FileDescriptor newStateFD = newState.getFileDescriptor();
+
+ if (oldState != null) {
+ oldStateFD = oldState.getFileDescriptor();
+ while ((err = readHeader_native(header, oldStateFD)) >= 0) {
+ if (err == 0) {
+ BackupHelper helper = helpers.get(header.keyPrefix);
+ Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper);
+ if (helper != null) {
+ doOneBackup(oldState, data, newState, header, helper);
+ helpers.remove(header.keyPrefix);
+ } else {
+ skipChunk_native(oldStateFD, header.chunkSize);
+ }
+ }
+ }
+ }
+
+ // Then go through and do the rest that we haven't done.
+ for (Map.Entry<String,BackupHelper> entry: helpers.entrySet()) {
+ header.keyPrefix = entry.getKey();
+ Log.d(TAG, "handling new helper '" + header.keyPrefix + "'");
+ BackupHelper helper = entry.getValue();
+ doOneBackup(oldState, data, newState, header, helper);
+ }
+ }
+
+ private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState, Header header, BackupHelper helper)
+ throws IOException {
+ int err;
+ FileDescriptor newStateFD = newState.getFileDescriptor();
+
+ // allocate space for the header in the file
+ int pos = allocateHeader_native(header, newStateFD);
+ if (pos < 0) {
+ throw new IOException("allocateHeader_native failed (error " + pos + ")");
+ }
+
+ data.setKeyPrefix(header.keyPrefix);
+
+ // do the backup
+ helper.performBackup(oldState, data, newState);
+
+ // fill in the header (seeking back to pos). The file pointer will be returned to
+ // where it was at the end of performBackup. Header.chunkSize will not be filled in.
+ err = writeHeader_native(header, newStateFD, pos);
+ if (err != 0) {
+ throw new IOException("writeHeader_native failed (error " + err + ")");
+ }
+ }
+
+ public void performRestore(BackupDataInput input, int appVersionCode,
+ ParcelFileDescriptor newState)
+ throws IOException {
+ boolean alreadyComplained = false;
+
+ BackupDataInputStream stream = new BackupDataInputStream(input);
+ while (input.readNextHeader()) {
+
+ String rawKey = input.getKey();
+ int pos = rawKey.indexOf(':');
+ if (pos > 0) {
+ String prefix = rawKey.substring(0, pos);
+ BackupHelper helper = mHelpers.get(prefix);
+ if (helper != null) {
+ stream.dataSize = input.getDataSize();
+ stream.key = rawKey.substring(pos+1);
+ helper.restoreEntity(stream);
+ } else {
+ if (!alreadyComplained) {
+ Log.w(TAG, "Couldn't find helper for: '" + rawKey + "'");
+ alreadyComplained = true;
+ }
+ }
+ } else {
+ if (!alreadyComplained) {
+ Log.w(TAG, "Entity with no prefix: '" + rawKey + "'");
+ alreadyComplained = true;
+ }
+ }
+ input.skipEntityData(); // In case they didn't consume the data.
+ }
+
+ // Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
+ for (BackupHelper helper: mHelpers.values()) {
+ helper.writeRestoreSnapshot(newState);
+ }
+ }
+
+ private static native int readHeader_native(Header h, FileDescriptor fd);
+ private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip);
+
+ private static native int allocateHeader_native(Header h, FileDescriptor fd);
+ private static native int writeHeader_native(Header h, FileDescriptor fd, int pos);
+}
+
diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java
index 6f0b2ee..34a1a0c 100644
--- a/core/java/android/backup/BackupManager.java
+++ b/core/java/android/backup/BackupManager.java
@@ -19,6 +19,7 @@ package android.backup;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.util.Log;
/**
* BackupManager is the interface to the system's backup service.
@@ -32,14 +33,24 @@ import android.os.ServiceManager;
* until the backup operation actually occurs.
*
* <p>The backup operation itself begins with the system launching the
- * {@link BackupService} subclass declared in your manifest. See the documentation
- * for {@link BackupService} for a detailed description of how the backup then proceeds.
+ * {@link android.app.BackupAgent} subclass declared in your manifest. See the
+ * documentation for {@link android.app.BackupAgent} for a detailed description
+ * of how the backup then proceeds.
*
* @hide pending API solidification
*/
public class BackupManager {
+ private static final String TAG = "BackupManager";
+
private Context mContext;
- private IBackupManager mService;
+ private static IBackupManager sService;
+
+ private static void checkServiceBinder() {
+ if (sService == null) {
+ sService = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ }
+ }
/**
* Constructs a BackupManager object through which the application can
@@ -51,19 +62,60 @@ public class BackupManager {
*/
public BackupManager(Context context) {
mContext = context;
- mService = IBackupManager.Stub.asInterface(
- ServiceManager.getService(Context.BACKUP_SERVICE));
}
/**
* Notifies the Android backup system that your application wishes to back up
* new changes to its data. A backup operation using your application's
- * {@link BackupService} subclass will be scheduled when you call this method.
+ * {@link android.app.BackupAgent} subclass will be scheduled when you call this method.
*/
public void dataChanged() {
- try {
- mService.dataChanged(mContext.getPackageName());
- } catch (RemoteException e) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChanged(mContext.getPackageName());
+ } catch (RemoteException e) {
+ Log.d(TAG, "dataChanged() couldn't connect");
+ }
+ }
+ }
+
+ /**
+ * Convenience method for callers who need to indicate that some other package
+ * needs a backup pass. This can be relevant in the case of groups of packages
+ * that share a uid, for example.
+ *
+ * This method requires that the application hold the "android.permission.BACKUP"
+ * permission if the package named in the argument is not the caller's own.
+ */
+ public static void dataChanged(String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChanged(packageName);
+ } catch (RemoteException e) {
+ Log.d(TAG, "dataChanged(pkg) couldn't connect");
+ }
+ }
+ }
+
+ /**
+ * Begin the process of restoring system data from backup. This method requires
+ * that the application hold the "android.permission.BACKUP" permission, and is
+ * not public.
+ *
+ * {@hide}
+ */
+ public IRestoreSession beginRestoreSession(String transport) {
+ IRestoreSession binder = null;
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ binder = sService.beginRestoreSession(transport);
+ } catch (RemoteException e) {
+ Log.d(TAG, "beginRestoreSession() couldn't connect");
+ }
}
+ return binder;
}
}
diff --git a/core/java/android/backup/FileBackupHelper.java b/core/java/android/backup/FileBackupHelper.java
index 05159dc..4058497 100644
--- a/core/java/android/backup/FileBackupHelper.java
+++ b/core/java/android/backup/FileBackupHelper.java
@@ -20,54 +20,53 @@ import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import java.io.File;
import java.io.FileDescriptor;
/** @hide */
-public class FileBackupHelper {
+public class FileBackupHelper extends FileBackupHelperBase implements BackupHelper {
private static final String TAG = "FileBackupHelper";
+ Context mContext;
+ File mFilesDir;
+ String[] mFiles;
+
+ public FileBackupHelper(Context context, String... files) {
+ super(context);
+
+ mContext = context;
+ mFilesDir = context.getFilesDir();
+ mFiles = files;
+ }
+
/**
* Based on oldState, determine which of the files from the application's data directory
* need to be backed up, write them to the data stream, and fill in newState with the
* state as it exists now.
*/
- public static void performBackup(Context context,
- ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState, String[] files) {
- String basePath = context.getFilesDir().getAbsolutePath();
- performBackup_checked(basePath, oldState, data, newState, files);
- }
-
- /**
- * Check the parameters so the native code doens't have to throw all the exceptions
- * since it's easier to do that from java.
- */
- static void performBackup_checked(String basePath,
- ParcelFileDescriptor oldState, BackupDataOutput data,
- ParcelFileDescriptor newState, String[] files) {
- if (files.length == 0) {
- return;
- }
- if (basePath == null) {
- throw new NullPointerException();
- }
- // oldStateFd can be null
- FileDescriptor oldStateFd = oldState != null ? oldState.getFileDescriptor() : null;
- if (data.fd == null) {
- throw new NullPointerException();
- }
- FileDescriptor newStateFd = newState.getFileDescriptor();
- if (newStateFd == null) {
- throw new NullPointerException();
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ // file names
+ String[] files = mFiles;
+ File base = mContext.getFilesDir();
+ final int N = files.length;
+ String[] fullPaths = new String[N];
+ for (int i=0; i<N; i++) {
+ fullPaths[i] = (new File(base, files[i])).getAbsolutePath();
}
- int err = performBackup_native(basePath, oldStateFd, data.fd, newStateFd, files);
+ // go
+ performBackup_checked(oldState, data, newState, fullPaths, files);
+ }
- if (err != 0) {
- throw new RuntimeException("Backup failed"); // TODO: more here
+ public void restoreEntity(BackupDataInputStream data) {
+ // TODO: turn this off before ship
+ Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
+ String key = data.getKey();
+ if (isKeyInList(key, mFiles)) {
+ File f = new File(mFilesDir, key);
+ writeFile(f, data);
}
}
-
- native private static int performBackup_native(String basePath, FileDescriptor oldState,
- FileDescriptor data, FileDescriptor newState, String[] files);
}
+
diff --git a/core/java/android/backup/FileBackupHelperBase.java b/core/java/android/backup/FileBackupHelperBase.java
new file mode 100644
index 0000000..03ae476
--- /dev/null
+++ b/core/java/android/backup/FileBackupHelperBase.java
@@ -0,0 +1,130 @@
+/*
+ * 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.backup;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.InputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+
+class FileBackupHelperBase {
+ private static final String TAG = "RestoreHelperBase";
+
+ int mPtr;
+ Context mContext;
+ boolean mExceptionLogged;
+
+ FileBackupHelperBase(Context context) {
+ mPtr = ctor();
+ mContext = context;
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ dtor(mPtr);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Check the parameters so the native code doens't have to throw all the exceptions
+ * since it's easier to do that from java.
+ */
+ static void performBackup_checked(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState, String[] files, String[] keys) {
+ if (files.length == 0) {
+ return;
+ }
+ // files must be all absolute paths
+ for (String f: files) {
+ if (f.charAt(0) != '/') {
+ throw new RuntimeException("files must have all absolute paths: " + f);
+ }
+ }
+ // the length of files and keys must be the same
+ if (files.length != keys.length) {
+ throw new RuntimeException("files.length=" + files.length
+ + " keys.length=" + keys.length);
+ }
+ // oldStateFd can be null
+ FileDescriptor oldStateFd = oldState != null ? oldState.getFileDescriptor() : null;
+ FileDescriptor newStateFd = newState.getFileDescriptor();
+ if (newStateFd == null) {
+ throw new NullPointerException();
+ }
+
+ int err = performBackup_native(oldStateFd, data.mBackupWriter, newStateFd, files, keys);
+
+ if (err != 0) {
+ // TODO: more here
+ throw new RuntimeException("Backup failed 0x" + Integer.toHexString(err));
+ }
+ }
+
+ void writeFile(File f, InputStream in) {
+ if (!(in instanceof BackupDataInputStream)) {
+ throw new IllegalStateException("input stream must be a BackupDataInputStream");
+ }
+ int result = -1;
+
+ // Create the enclosing directory.
+ File parent = f.getParentFile();
+ parent.mkdirs();
+
+ result = writeFile_native(mPtr, f.getAbsolutePath(),
+ ((BackupDataInputStream)in).mData.mBackupReader);
+ if (result != 0) {
+ // Bail on this entity. Only log one failure per helper object.
+ if (!mExceptionLogged) {
+ Log.e(TAG, "Failed restoring file '" + f + "' for app '"
+ + mContext.getPackageName() + "\' result=0x"
+ + Integer.toHexString(result));
+ mExceptionLogged = true;
+ }
+ }
+ }
+
+ public void writeRestoreSnapshot(ParcelFileDescriptor fd) {
+ int result = writeSnapshot_native(mPtr, fd.getFileDescriptor());
+ // TODO: Do something with the error.
+ }
+
+ boolean isKeyInList(String key, String[] list) {
+ for (String s: list) {
+ if (s.equals(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static native int ctor();
+ private static native void dtor(int ptr);
+
+ native private static int performBackup_native(FileDescriptor oldState,
+ int data, FileDescriptor newState, String[] files, String[] keys);
+
+ private static native int writeFile_native(int ptr, String filename, int backupReader);
+ private static native int writeSnapshot_native(int ptr, FileDescriptor fd);
+}
+
+
diff --git a/core/java/android/backup/IBackupManager.aidl b/core/java/android/backup/IBackupManager.aidl
index cf22798..9d181be 100644
--- a/core/java/android/backup/IBackupManager.aidl
+++ b/core/java/android/backup/IBackupManager.aidl
@@ -16,6 +16,8 @@
package android.backup;
+import android.backup.IRestoreSession;
+
/**
* Direct interface to the Backup Manager Service that applications invoke on. The only
* operation currently needed is a simple notification that the app has made changes to
@@ -30,12 +32,102 @@ interface IBackupManager {
/**
* Tell the system service that the caller has made changes to its
* data, and therefore needs to undergo an incremental backup pass.
+ *
+ * Any application can invoke this method for its own package, but
+ * only callers who hold the android.permission.BACKUP permission
+ * may invoke it for arbitrary packages.
+ */
+ void dataChanged(String packageName);
+
+ /**
+ * Erase all backed-up data for the given package from the storage
+ * destination.
+ *
+ * Any application can invoke this method for its own package, but
+ * only callers who hold the android.permission.BACKUP permission
+ * may invoke it for arbitrary packages.
+ */
+ void clearBackupData(String packageName);
+
+ /**
+ * Notifies the Backup Manager Service that an agent has become available. This
+ * method is only invoked by the Activity Manager.
+ */
+ void agentConnected(String packageName, IBinder agent);
+
+ /**
+ * Notify the Backup Manager Service that an agent has unexpectedly gone away.
+ * This method is only invoked by the Activity Manager.
+ */
+ void agentDisconnected(String packageName);
+
+ /**
+ * Enable/disable the backup service entirely. When disabled, no backup
+ * or restore operations will take place. Data-changed notifications will
+ * still be observed and collected, however, so that changes made while the
+ * mechanism was disabled will still be backed up properly if it is enabled
+ * at some point in the future.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void setBackupEnabled(boolean isEnabled);
+
+ /**
+ * Indicate that any necessary one-time provisioning has occurred.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void setBackupProvisioned(boolean isProvisioned);
+
+ /**
+ * Report whether the backup mechanism is currently enabled.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ boolean isBackupEnabled();
+
+ /**
+ * Schedule an immediate backup attempt for all pending updates. This is
+ * primarily intended for transports to use when they detect a suitable
+ * opportunity for doing a backup pass. If there are no pending updates to
+ * be sent, no action will be taken. Even if some updates are pending, the
+ * transport will still be asked to confirm via the usual requestBackupTime()
+ * method.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ */
+ void backupNow();
+
+ /**
+ * Identify the currently selected transport. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
+ */
+ String getCurrentTransport();
+
+ /**
+ * Request a list of all available backup transports' names. Callers must
+ * hold the android.permission.BACKUP permission to use this method.
+ */
+ String[] listAllTransports();
+
+ /**
+ * Specify the current backup transport. Callers must hold the
+ * android.permission.BACKUP permission to use this method.
+ *
+ * @param transport The name of the transport to select. This should be one
+ * of {@link BackupManager.TRANSPORT_GOOGLE} or {@link BackupManager.TRANSPORT_ADB}.
+ * @return The name of the previously selected transport. If the given transport
+ * name is not one of the currently available transports, no change is made to
+ * the current transport setting and the method returns null.
*/
- oneway void dataChanged(String packageName);
+ String selectBackupTransport(String transport);
/**
- * Schedule a full backup of the given package.
- * !!! TODO: protect with a signature-or-system permission?
+ * Begin a restore session with the given transport (which may differ from the
+ * currently-active backup transport).
+ *
+ * @param transport The name of the transport to use for the restore operation.
+ * @return An interface to the restore session, or null on error.
*/
- oneway void scheduleFullBackup(String packageName);
+ IRestoreSession beginRestoreSession(String transportID);
}
diff --git a/core/java/android/backup/IRestoreObserver.aidl b/core/java/android/backup/IRestoreObserver.aidl
new file mode 100644
index 0000000..59e59fc
--- /dev/null
+++ b/core/java/android/backup/IRestoreObserver.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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.backup;
+
+/**
+ * Callback class for receiving progress reports during a restore operation.
+ *
+ * @hide
+ */
+interface IRestoreObserver {
+ /**
+ * The restore operation has begun.
+ *
+ * @param numPackages The total number of packages being processed in
+ * this restore operation.
+ */
+ void restoreStarting(int numPackages);
+
+ /**
+ * An indication of which package is being restored currently, out of the
+ * total number provided in the restoreStarting() callback. This method
+ * is not guaranteed to be called.
+ *
+ * @param nowBeingRestored The index, between 1 and the numPackages parameter
+ * to the restoreStarting() callback, of the package now being restored.
+ */
+ void onUpdate(int nowBeingRestored);
+
+ /**
+ * The restore operation has completed.
+ *
+ * @param error Zero on success; a nonzero error code if the restore operation
+ * as a whole failed.
+ */
+ void restoreFinished(int error);
+}
diff --git a/core/java/android/backup/IRestoreSession.aidl b/core/java/android/backup/IRestoreSession.aidl
new file mode 100644
index 0000000..2a1fbc1
--- /dev/null
+++ b/core/java/android/backup/IRestoreSession.aidl
@@ -0,0 +1,55 @@
+/*
+ * 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.backup;
+
+import android.backup.RestoreSet;
+import android.backup.IRestoreObserver;
+
+/**
+ * Binder interface used by clients who wish to manage a restore operation. Every
+ * method in this interface requires the android.permission.BACKUP permission.
+ *
+ * {@hide}
+ */
+interface IRestoreSession {
+ /**
+ * Ask the current transport what the available restore sets are.
+ *
+ * @return A bundle containing two elements: an int array under the key
+ * "tokens" whose entries are a transport-private identifier for each backup set;
+ * and a String array under the key "names" whose entries are the user-meaningful
+ * text corresponding to the backup sets at each index in the tokens array.
+ */
+ RestoreSet[] getAvailableRestoreSets();
+
+ /**
+ * Restore the given set onto the device, replacing the current data of any app
+ * contained in the restore set with the data previously backed up.
+ *
+ * @param token The token from {@link getAvailableRestoreSets()} corresponding to
+ * the restore set that should be used.
+ * @param observer If non-null, this binder points to an object that will receive
+ * progress callbacks during the restore operation.
+ */
+ int performRestore(long token, IRestoreObserver observer);
+
+ /**
+ * End this restore session. After this method is called, the IRestoreSession binder
+ * is no longer valid.
+ */
+ void endRestoreSession();
+}
diff --git a/core/java/android/backup/RestoreSet.aidl b/core/java/android/backup/RestoreSet.aidl
new file mode 100644
index 0000000..42e77bf
--- /dev/null
+++ b/core/java/android/backup/RestoreSet.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.backup;
+
+parcelable RestoreSet; \ No newline at end of file
diff --git a/core/java/android/backup/RestoreSet.java b/core/java/android/backup/RestoreSet.java
new file mode 100644
index 0000000..eeca148
--- /dev/null
+++ b/core/java/android/backup/RestoreSet.java
@@ -0,0 +1,87 @@
+/*
+ * 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.backup;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Descriptive information about a set of backed-up app data available for restore.
+ * Used by IRestoreSession clients.
+ *
+ * @hide
+ */
+public class RestoreSet implements Parcelable {
+ /**
+ * Name of this restore set. May be user generated, may simply be the name
+ * of the handset model, e.g. "T-Mobile G1".
+ */
+ public String name;
+
+ /**
+ * Identifier of the device whose data this is. This will be as unique as
+ * is practically possible; for example, it might be an IMEI.
+ */
+ public String device;
+
+ /**
+ * Token that identifies this backup set unambiguously to the backup/restore
+ * transport. This is guaranteed to be valid for the duration of a restore
+ * session, but is meaningless once the session has ended.
+ */
+ public long token;
+
+
+ public RestoreSet() {
+ // Leave everything zero / null
+ }
+
+ public RestoreSet(String _name, String _dev, long _token) {
+ name = _name;
+ device = _dev;
+ token = _token;
+ }
+
+
+ // Parcelable implementation
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(name);
+ out.writeString(device);
+ out.writeLong(token);
+ }
+
+ public static final Parcelable.Creator<RestoreSet> CREATOR
+ = new Parcelable.Creator<RestoreSet>() {
+ public RestoreSet createFromParcel(Parcel in) {
+ return new RestoreSet(in);
+ }
+
+ public RestoreSet[] newArray(int size) {
+ return new RestoreSet[size];
+ }
+ };
+
+ private RestoreSet(Parcel in) {
+ name = in.readString();
+ device = in.readString();
+ token = in.readLong();
+ }
+}
diff --git a/core/java/android/backup/SharedPreferencesBackupHelper.java b/core/java/android/backup/SharedPreferencesBackupHelper.java
index 8627f08..4a7b399 100644
--- a/core/java/android/backup/SharedPreferencesBackupHelper.java
+++ b/core/java/android/backup/SharedPreferencesBackupHelper.java
@@ -18,24 +18,51 @@ package android.backup;
import android.content.Context;
import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import java.io.File;
import java.io.FileDescriptor;
/** @hide */
-public class SharedPreferencesBackupHelper {
- public static void performBackup(Context context,
- ParcelFileDescriptor oldSnapshot, ParcelFileDescriptor newSnapshot,
- BackupDataOutput data, String[] prefGroups) {
- String basePath = "/xxx"; //context.getPreferencesDir();
+public class SharedPreferencesBackupHelper extends FileBackupHelperBase implements BackupHelper {
+ private static final String TAG = "SharedPreferencesBackupHelper";
+ private Context mContext;
+ private String[] mPrefGroups;
+
+ public SharedPreferencesBackupHelper(Context context, String... prefGroups) {
+ super(context);
+
+ mContext = context;
+ mPrefGroups = prefGroups;
+ }
+
+ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) {
+ Context context = mContext;
+
// make filenames for the prefGroups
+ String[] prefGroups = mPrefGroups;
final int N = prefGroups.length;
String[] files = new String[N];
for (int i=0; i<N; i++) {
- files[i] = prefGroups[i] + ".xml";
+ files[i] = context.getSharedPrefsFile(prefGroups[i]).getAbsolutePath();
}
- FileBackupHelper.performBackup_checked(basePath, oldSnapshot, data, newSnapshot, files);
+ // go
+ performBackup_checked(oldState, data, newState, files, prefGroups);
+ }
+
+ public void restoreEntity(BackupDataInputStream data) {
+ Context context = mContext;
+
+ // TODO: turn this off before ship
+ Log.d(TAG, "got entity '" + data.getKey() + "' size=" + data.size());
+ String key = data.getKey();
+ if (isKeyInList(key, mPrefGroups)) {
+ File f = context.getSharedPrefsFile(key).getAbsoluteFile();
+ writeFile(f, data);
+ }
}
}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index e198435..fe1e09a 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -332,6 +332,31 @@ public class BluetoothHeadset {
}
/**
+ * Get battery usage hint for Bluetooth Headset service.
+ * This is a monotonically increasing integer. Wraps to 0 at
+ * Integer.MAX_INT, and at boot.
+ * Current implementation returns the number of AT commands handled since
+ * boot. This is a good indicator for spammy headset/handsfree units that
+ * can keep the device awake by polling for cellular status updates. As a
+ * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms
+ * @return monotonically increasing battery usage hint, or a negative error
+ * code on error
+ * @hide
+ */
+ public int getBatteryUsageHint() {
+ if (DBG) log("getBatteryUsageHint()");
+ if (mService != null) {
+ try {
+ return mService.getBatteryUsageHint();
+ } 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 -1;
+ }
+
+ /**
* Check class bits for possible HSP or HFP support.
* This is a simple heuristic that tries to guess if a device with the
* given class bits might support HSP or HFP. It is not accurate for all
diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java
index f31e7a2..f987ffd 100644
--- a/core/java/android/bluetooth/HeadsetBase.java
+++ b/core/java/android/bluetooth/HeadsetBase.java
@@ -40,6 +40,8 @@ public class HeadsetBase {
public static final int DIRECTION_INCOMING = 1;
public static final int DIRECTION_OUTGOING = 2;
+ private static int sAtInputCount = 0; /* TODO: Consider not using a static variable */
+
private final BluetoothDevice mBluetooth;
private final String mAddress;
private final int mRfcommChannel;
@@ -109,6 +111,14 @@ public class HeadsetBase {
acquireWakeLock();
long timestamp;
+ synchronized(HeadsetBase.class) {
+ if (sAtInputCount == Integer.MAX_VALUE) {
+ sAtInputCount = 0;
+ } else {
+ sAtInputCount++;
+ }
+ }
+
if (DBG) timestamp = System.currentTimeMillis();
AtCommandResult result = mAtParser.process(input);
if (DBG) Log.d(TAG, "Processing " + input + " took " +
@@ -279,7 +289,11 @@ public class HeadsetBase {
}
}
- private void log(String msg) {
+ public static int getAtInputCount() {
+ return sAtInputCount;
+ }
+
+ private static void log(String msg) {
Log.d(TAG, msg);
}
}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
index 582d4e3..5f42fd6 100644
--- a/core/java/android/bluetooth/IBluetoothHeadset.aidl
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -31,4 +31,5 @@ interface IBluetoothHeadset {
boolean stopVoiceRecognition();
boolean setPriority(in String address, int priority);
int getPriority(in String address);
+ int getBatteryUsageHint();
}
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
index ce6501c..249d9ba 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -147,7 +147,8 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
@Override
public boolean onCreate() {
if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
- mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(), mDatabaseName);
+ mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(),
+ mDatabaseName);
mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
AccountMonitorListener listener = new AccountMonitorListener() {
@@ -235,76 +236,147 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
return Collections.emptyList();
}
- @Override
- public final int update(final Uri url, final ContentValues values,
- final String selection, final String[] selectionArgs) {
+ /**
+ * <p>
+ * Call mOpenHelper.getWritableDatabase() and mDb.beginTransaction().
+ * {@link #endTransaction} MUST be called after calling this method.
+ * Those methods should be used like this:
+ * </p>
+ *
+ * <pre class="prettyprint">
+ * boolean successful = false;
+ * beginTransaction();
+ * try {
+ * // Do something related to mDb
+ * successful = true;
+ * return ret;
+ * } finally {
+ * endTransaction(successful);
+ * }
+ * </pre>
+ *
+ * @hide This method is dangerous from the view of database manipulation, though using
+ * this makes batch insertion/update/delete much faster.
+ */
+ public final void beginTransaction() {
mDb = mOpenHelper.getWritableDatabase();
mDb.beginTransaction();
+ }
+
+ /**
+ * <p>
+ * Call mDb.endTransaction(). If successful is true, try to call
+ * mDb.setTransactionSuccessful() before calling mDb.endTransaction().
+ * This method MUST be used with {@link #beginTransaction()}.
+ * </p>
+ *
+ * @hide This method is dangerous from the view of database manipulation, though using
+ * this makes batch insertion/update/delete much faster.
+ */
+ public final void endTransaction(boolean successful) {
try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().update(
- url, values, selection, selectionArgs);
+ if (successful) {
+ // setTransactionSuccessful() must be called just once during opening the
+ // transaction.
mDb.setTransactionSuccessful();
- return numRows;
}
+ } finally {
+ mDb.endTransaction();
+ }
+ }
- int result = updateInternal(url, values, selection, selectionArgs);
- mDb.setTransactionSuccessful();
+ @Override
+ public final int update(final Uri uri, final ContentValues values,
+ final String selection, final String[] selectionArgs) {
+ boolean successful = false;
+ beginTransaction();
+ try {
+ int ret = nonTransactionalUpdate(uri, values, selection, selectionArgs);
+ successful = true;
+ return ret;
+ } finally {
+ endTransaction(successful);
+ }
+ }
- if (!isTemporary() && result > 0) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
+ /**
+ * @hide
+ */
+ public final int nonTransactionalUpdate(final Uri uri, final ContentValues values,
+ final String selection, final String[] selectionArgs) {
+ if (isTemporary() && mSyncState.matches(uri)) {
+ int numRows = mSyncState.asContentProvider().update(
+ uri, values, selection, selectionArgs);
+ return numRows;
+ }
- return result;
- } finally {
- mDb.endTransaction();
+ int result = updateInternal(uri, values, selection, selectionArgs);
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
}
+
+ return result;
}
@Override
- public final int delete(final Uri url, final String selection,
+ public final int delete(final Uri uri, final String selection,
final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
+ boolean successful = false;
+ beginTransaction();
try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
- mDb.setTransactionSuccessful();
- return numRows;
- }
- int result = deleteInternal(url, selection, selectionArgs);
- mDb.setTransactionSuccessful();
- if (!isTemporary() && result > 0) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- return result;
+ int ret = nonTransactionalDelete(uri, selection, selectionArgs);
+ successful = true;
+ return ret;
} finally {
- mDb.endTransaction();
+ endTransaction(successful);
}
}
+ /**
+ * @hide
+ */
+ public final int nonTransactionalDelete(final Uri uri, final String selection,
+ final String[] selectionArgs) {
+ if (isTemporary() && mSyncState.matches(uri)) {
+ int numRows = mSyncState.asContentProvider().delete(uri, selection, selectionArgs);
+ return numRows;
+ }
+ int result = deleteInternal(uri, selection, selectionArgs);
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
+ }
+ return result;
+ }
+
@Override
- public final Uri insert(final Uri url, final ContentValues values) {
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
+ public final Uri insert(final Uri uri, final ContentValues values) {
+ boolean successful = false;
+ beginTransaction();
try {
- if (isTemporary() && mSyncState.matches(url)) {
- Uri result = mSyncState.asContentProvider().insert(url, values);
- mDb.setTransactionSuccessful();
- return result;
- }
- Uri result = insertInternal(url, values);
- mDb.setTransactionSuccessful();
- if (!isTemporary() && result != null) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- return result;
+ Uri ret = nonTransactionalInsert(uri, values);
+ successful = true;
+ return ret;
} finally {
- mDb.endTransaction();
+ endTransaction(successful);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public final Uri nonTransactionalInsert(final Uri uri, final ContentValues values) {
+ if (isTemporary() && mSyncState.matches(uri)) {
+ Uri result = mSyncState.asContentProvider().insert(uri, values);
+ return result;
+ }
+ Uri result = insertInternal(uri, values);
+ if (!isTemporary() && result != null) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
}
+ return result;
}
@Override
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
index 700f1d8..9c760d9 100644
--- a/core/java/android/content/AbstractTableMerger.java
+++ b/core/java/android/content/AbstractTableMerger.java
@@ -61,8 +61,10 @@ public abstract class AbstractTableMerger
_SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=?";
private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
- private static final String SELECT_UNSYNCED = ""
- + _SYNC_DIRTY + " > 0 and (" + _SYNC_ACCOUNT + "=? or " + _SYNC_ACCOUNT + " is null)";
+ private static final String SELECT_UNSYNCED =
+ "(" + _SYNC_ACCOUNT + " IS NULL OR " + _SYNC_ACCOUNT + "=?) AND "
+ + "(" + _SYNC_ID + " IS NULL OR (" + _SYNC_DIRTY + " > 0 AND "
+ + _SYNC_VERSION + " IS NOT NULL))";
public AbstractTableMerger(SQLiteDatabase database,
String table, Uri tableURL, String deletedTable,
@@ -365,26 +367,32 @@ public abstract class AbstractTableMerger
if (!TextUtils.isEmpty(localSyncID)) {
// An existing server item has changed
- boolean recordChanged = (localSyncVersion == null) ||
- !serverSyncVersion.equals(localSyncVersion);
- if (recordChanged) {
- if (localSyncDirty) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record " + serverSyncId
- + " conflicts with local _sync_id " + localSyncID
- + ", local _id " + localRowId);
+ // If serverSyncVersion is null, there is no edit URL;
+ // server won't let this change be written.
+ // Just hold onto it, I guess, in case the server permissions
+ // change later.
+ if (serverSyncVersion != null) {
+ boolean recordChanged = (localSyncVersion == null) ||
+ !serverSyncVersion.equals(localSyncVersion);
+ if (recordChanged) {
+ if (localSyncDirty) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "remote record " + serverSyncId
+ + " conflicts with local _sync_id " + localSyncID
+ + ", local _id " + localRowId);
+ }
+ conflict = true;
+ } else {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "remote record " +
+ serverSyncId +
+ " updates local _sync_id " +
+ localSyncID + ", local _id " +
+ localRowId);
+ }
+ update = true;
}
- conflict = true;
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "remote record " +
- serverSyncId +
- " updates local _sync_id " +
- localSyncID + ", local _id " +
- localRowId);
- }
- update = true;
}
}
} else {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f2ad248..9e37ae4 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
@@ -233,6 +234,9 @@ public abstract class Context {
/** Return the name of this application's package. */
public abstract String getPackageName();
+ /** Return the full application info for this context's package. */
+ public abstract ApplicationInfo getApplicationInfo();
+
/**
* {@hide}
* Return the full path to this context's resource files. This is the ZIP files
@@ -254,12 +258,20 @@ public abstract class Context {
* <p>Note: this is not generally useful for applications, since they should
* not be directly accessing the file system.
*
- *
* @return String Path to the code and assets.
*/
public abstract String getPackageCodePath();
/**
+ * {@hide}
+ * Return the full path to the shared prefs file for the given prefs group name.
+ *
+ * <p>Note: this is not generally useful for applications, since they should
+ * not be directly accessing the file system.
+ */
+ public abstract File getSharedPrefsFile(String name);
+
+ /**
* Retrieve and hold the contents of the preferences file 'name', returning
* a SharedPreferences through which you can retrieve and modify its
* values. Only one instance of the SharedPreferences object is returned
@@ -527,16 +539,6 @@ public abstract class Context {
public abstract int getWallpaperDesiredMinimumHeight();
/**
- * Returns the scale in which the application will be drawn on the
- * screen. This is usually 1.0f if the application supports the device's
- * resolution/density. This will be 1.5f, for example, if the application
- * that supports only 160 density runs on 240 density screen.
- *
- * @hide
- */
- public abstract float getApplicationScale();
-
- /**
* Change the current system wallpaper to a bitmap. The given bitmap is
* converted to a PNG and stored as the wallpaper. On success, the intent
* {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast.
@@ -1135,6 +1137,15 @@ public abstract class Context {
public static final String NOTIFICATION_SERVICE = "notification";
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.accessibility.AccessibilityManager} for giving the user
+ * feedback for UI events through the registered event listeners.
+ *
+ * @see #getSystemService
+ * @see android.view.accessibility.AccessibilityManager
+ */
+ public static final String ACCESSIBILITY_SERVICE = "accessibility";
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for controlling keyguard.
*
* @see #getSystemService
@@ -1643,6 +1654,13 @@ public abstract class Context {
* with extreme care!
*/
public static final int CONTEXT_IGNORE_SECURITY = 0x00000002;
+
+ /**
+ * Flag for use with {@link #createPackageContext}: a restricted context may
+ * disable specific features. For instance, a View associated with a restricted
+ * context would ignore particular XML attributes.
+ */
+ public static final int CONTEXT_RESTRICTED = 0x00000004;
/**
* Return a new Context object for the given application name. This
@@ -1671,4 +1689,15 @@ public abstract class Context {
*/
public abstract Context createPackageContext(String packageName,
int flags) throws PackageManager.NameNotFoundException;
+
+ /**
+ * Indicates whether this Context is restricted.
+ *
+ * @return True if this Context is restricted, false otherwise.
+ *
+ * @see #CONTEXT_RESTRICTED
+ */
+ public boolean isRestricted() {
+ return false;
+ }
}
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 25b2cae..45a082a 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -16,6 +16,7 @@
package android.content;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
@@ -120,6 +121,11 @@ public class ContextWrapper extends Context {
}
@Override
+ public ApplicationInfo getApplicationInfo() {
+ return mBase.getApplicationInfo();
+ }
+
+ @Override
public String getPackageResourcePath() {
return mBase.getPackageResourcePath();
}
@@ -130,6 +136,11 @@ public class ContextWrapper extends Context {
}
@Override
+ public File getSharedPrefsFile(String name) {
+ return mBase.getSharedPrefsFile(name);
+ }
+
+ @Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
@@ -420,11 +431,8 @@ public class ContextWrapper extends Context {
return mBase.createPackageContext(packageName, flags);
}
- /**
- * @hide
- */
@Override
- public float getApplicationScale() {
- return mBase.getApplicationScale();
+ public boolean isRestricted() {
+ return mBase.isRestricted();
}
}
diff --git a/core/java/android/content/IIntentReceiver.aidl b/core/java/android/content/IIntentReceiver.aidl
new file mode 100755
index 0000000..443db2d
--- /dev/null
+++ b/core/java/android/content/IIntentReceiver.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * System private API for dispatching intent broadcasts. This is given to the
+ * activity manager as part of registering for an intent broadcasts, and is
+ * called when it receives intents.
+ *
+ * {@hide}
+ */
+oneway interface IIntentReceiver {
+ void performReceive(in Intent intent, int resultCode,
+ String data, in Bundle extras, boolean ordered);
+}
+
diff --git a/core/java/android/content/IIntentSender.aidl b/core/java/android/content/IIntentSender.aidl
new file mode 100644
index 0000000..b7da472
--- /dev/null
+++ b/core/java/android/content/IIntentSender.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.IIntentReceiver;
+import android.content.Intent;
+
+/** @hide */
+interface IIntentSender {
+ int send(int code, in Intent intent, String resolvedType,
+ IIntentReceiver finishedReceiver);
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 24262f5..263f927 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -240,35 +240,35 @@ import java.util.Set;
*
* &lt;activity class=".NotesList" android:label="@string/title_notes_list"&gt;
* &lt;intent-filter&gt;
- * &lt;action android:value="android.intent.action.MAIN" /&gt;
- * &lt;category android:value="android.intent.category.LAUNCHER" /&gt;
+ * &lt;action android:name="android.intent.action.MAIN" /&gt;
+ * &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
* &lt;/intent-filter&gt;
* &lt;intent-filter&gt;
- * &lt;action android:value="android.intent.action.VIEW" /&gt;
- * &lt;action android:value="android.intent.action.EDIT" /&gt;
- * &lt;action android:value="android.intent.action.PICK" /&gt;
- * &lt;category android:value="android.intent.category.DEFAULT" /&gt;
- * &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="android.intent.action.VIEW" /&gt;
+ * &lt;action android:name="android.intent.action.EDIT" /&gt;
+ * &lt;action android:name="android.intent.action.PICK" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;
* &lt;intent-filter&gt;
- * &lt;action android:value="android.intent.action.GET_CONTENT" /&gt;
- * &lt;category android:value="android.intent.category.DEFAULT" /&gt;
- * &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="android.intent.action.GET_CONTENT" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;
* &lt;/activity&gt;
*
* &lt;activity class=".NoteEditor" android:label="@string/title_note"&gt;
* &lt;intent-filter android:label="@string/resolve_edit"&gt;
- * &lt;action android:value="android.intent.action.VIEW" /&gt;
- * &lt;action android:value="android.intent.action.EDIT" /&gt;
- * &lt;category android:value="android.intent.category.DEFAULT" /&gt;
- * &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="android.intent.action.VIEW" /&gt;
+ * &lt;action android:name="android.intent.action.EDIT" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;
*
* &lt;intent-filter&gt;
- * &lt;action android:value="android.intent.action.INSERT" /&gt;
- * &lt;category android:value="android.intent.category.DEFAULT" /&gt;
- * &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="android.intent.action.INSERT" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;
*
* &lt;/activity&gt;
@@ -276,11 +276,11 @@ import java.util.Set;
* &lt;activity class=".TitleEditor" android:label="@string/title_edit_title"
* android:theme="@android:style/Theme.Dialog"&gt;
* &lt;intent-filter android:label="@string/resolve_title"&gt;
- * &lt;action android:value="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
- * &lt;category android:value="android.intent.category.DEFAULT" /&gt;
- * &lt;category android:value="android.intent.category.ALTERNATIVE" /&gt;
- * &lt;category android:value="android.intent.category.SELECTED_ALTERNATIVE" /&gt;
- * &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ * &lt;category android:name="android.intent.category.DEFAULT" /&gt;
+ * &lt;category android:name="android.intent.category.ALTERNATIVE" /&gt;
+ * &lt;category android:name="android.intent.category.SELECTED_ALTERNATIVE" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;
* &lt;/activity&gt;
*
@@ -294,8 +294,8 @@ import java.util.Set;
* <ol>
* <li><pre>
* &lt;intent-filter&gt;
- * &lt;action android:value="{@link #ACTION_MAIN android.intent.action.MAIN}" /&gt;
- * &lt;category android:value="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" /&gt;
+ * &lt;action android:name="{@link #ACTION_MAIN android.intent.action.MAIN}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_LAUNCHER android.intent.category.LAUNCHER}" /&gt;
* &lt;/intent-filter&gt;</pre>
* <p>This provides a top-level entry into the NotePad application: the standard
* MAIN action is a main entry point (not requiring any other information in
@@ -303,11 +303,11 @@ import java.util.Set;
* listed in the application launcher.</p>
* <li><pre>
* &lt;intent-filter&gt;
- * &lt;action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
- * &lt;action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
- * &lt;action android:value="{@link #ACTION_PICK android.intent.action.PICK}" /&gt;
- * &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
- * &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ * &lt;action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ * &lt;action android:name="{@link #ACTION_PICK android.intent.action.PICK}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data mimeType:name="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;</pre>
* <p>This declares the things that the activity can do on a directory of
* notes. The type being supported is given with the &lt;type&gt; tag, where
@@ -322,9 +322,9 @@ import java.util.Set;
* activity when its component name is not explicitly specified.</p>
* <li><pre>
* &lt;intent-filter&gt;
- * &lt;action android:value="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" /&gt;
- * &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
- * &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="{@link #ACTION_GET_CONTENT android.intent.action.GET_CONTENT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;</pre>
* <p>This filter describes the ability return to the caller a note selected by
* the user without needing to know where it came from. The data type
@@ -371,10 +371,10 @@ import java.util.Set;
* <ol>
* <li><pre>
* &lt;intent-filter android:label="@string/resolve_edit"&gt;
- * &lt;action android:value="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
- * &lt;action android:value="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
- * &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
- * &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="{@link #ACTION_VIEW android.intent.action.VIEW}" /&gt;
+ * &lt;action android:name="{@link #ACTION_EDIT android.intent.action.EDIT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;</pre>
* <p>The first, primary, purpose of this activity is to let the user interact
* with a single note, as decribed by the MIME type
@@ -384,9 +384,9 @@ import java.util.Set;
* specifying its component.</p>
* <li><pre>
* &lt;intent-filter&gt;
- * &lt;action android:value="{@link #ACTION_INSERT android.intent.action.INSERT}" /&gt;
- * &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
- * &lt;type android:value="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="{@link #ACTION_INSERT android.intent.action.INSERT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.dir/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;</pre>
* <p>The secondary use of this activity is to insert a new note entry into
* an existing directory of notes. This is used when the user creates a new
@@ -422,11 +422,11 @@ import java.util.Set;
*
* <pre>
* &lt;intent-filter android:label="@string/resolve_title"&gt;
- * &lt;action android:value="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
- * &lt;category android:value="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
- * &lt;category android:value="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" /&gt;
- * &lt;category android:value="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" /&gt;
- * &lt;type android:value="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
+ * &lt;action android:name="<i>com.android.notepad.action.EDIT_TITLE</i>" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_DEFAULT android.intent.category.DEFAULT}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_ALTERNATIVE android.intent.category.ALTERNATIVE}" /&gt;
+ * &lt;category android:name="{@link #CATEGORY_SELECTED_ALTERNATIVE android.intent.category.SELECTED_ALTERNATIVE}" /&gt;
+ * &lt;data android:mimeType="vnd.android.cursor.item/<i>vnd.google.note</i>" /&gt;
* &lt;/intent-filter&gt;</pre>
*
* <p>In the single intent template here, we
@@ -509,8 +509,8 @@ import java.util.Set;
* <li> {@link #ACTION_UID_REMOVED}
* <li> {@link #ACTION_BATTERY_CHANGED}
* <li> {@link #ACTION_POWER_CONNECTED}
- * <li> {@link #ACTION_POWER_DISCONNECTED}
- * <li> {@link #ACTION_SHUTDOWN}
+ * <li> {@link #ACTION_POWER_DISCONNECTED}
+ * <li> {@link #ACTION_SHUTDOWN}
* </ul>
*
* <h3>Standard Categories</h3>
@@ -915,6 +915,23 @@ public class Intent implements Parcelable {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SEND = "android.intent.action.SEND";
/**
+ * Activity Action: Deliver multiple data to someone else.
+ * <p>
+ * Like ACTION_SEND, except the data is multiple.
+ * <p>
+ * Input: {@link #getType} is the MIME type of the data being sent.
+ * get*ArrayListExtra can have either a {@link #EXTRA_TEXT} or {@link
+ * #EXTRA_STREAM} field, containing the data to be sent.
+ * <p>
+ * Optional standard extras, which may be interpreted by some recipients as
+ * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
+ * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
+ * <p>
+ * Output: nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE";
+ /**
* Activity Action: Handle an incoming phone call.
* <p>Input: nothing.
* <p>Output: nothing.
@@ -1059,6 +1076,36 @@ public class Intent implements Parcelable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
+
+ /**
+ * Activity Action: Show power usage information to the user.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY";
+
+ /**
+ * Activity Action: Setup wizard to launch after a platform update. This
+ * activity should have a string meta-data field associated with it,
+ * {@link #METADATA_SETUP_VERSION}, which defines the current version of
+ * the platform for setup. The activity will be launched only if
+ * {@link android.provider.Settings.Secure#LAST_SETUP_SHOWN} is not the
+ * same value.
+ * <p>Input: Nothing.
+ * <p>Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
+
+ /**
+ * A string associated with a {@link #ACTION_UPGRADE_SETUP} activity
+ * describing the last run version of the platform that was setup.
+ * @hide
+ */
+ public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent broadcast actions (see action variable).
@@ -1264,6 +1311,13 @@ public class Intent implements Parcelable {
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
/**
+ * Broadcast Action: Indicates the battery is now okay after being low.
+ * This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has
+ * gone back up to an okay state.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY";
+ /**
* Broadcast Action: External power has been connected to the device.
* This is intended for applications that wish to register specifically to this notification.
* Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
@@ -1277,10 +1331,10 @@ public class Intent implements Parcelable {
* This is intended for applications that wish to register specifically to this notification.
* Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to
* stay active to receive this notification. This action can be used to implement actions
- * that wait until power is available to trigger.
+ * that wait until power is available to trigger.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";
+ public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED";
/**
* Broadcast Action: Device is shutting down.
* This is broadcast when the device is being shut down (completely turned
@@ -1289,7 +1343,7 @@ public class Intent implements Parcelable {
* to handle this, since the forground activity will be paused as well.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
+ public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN";
/**
* Broadcast Action: Indicates low memory condition on the device
*/
@@ -1552,6 +1606,16 @@ public class Intent implements Parcelable {
public static final String ACTION_REBOOT =
"android.intent.action.REBOOT";
+ /**
+ * @hide
+ * TODO: This will be unhidden in a later CL.
+ * Broadcast Action: The TextToSpeech synthesizer has completed processing
+ * all of the text in the speech queue.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
+ "android.intent.action.TTS_QUEUE_PROCESSING_COMPLETED";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -1791,23 +1855,23 @@ public class Intent implements Parcelable {
* delivered.
*/
public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT";
-
+
/**
* Used as a parcelable extra field in {@link #ACTION_APP_ERROR}, containing
* the bug report.
- *
+ *
* @hide
*/
public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
/**
- * Used as a string extra field when sending an intent to PackageInstaller to install a
+ * Used as a string extra field when sending an intent to PackageInstaller to install a
* package. Specifies the installer package name; this package will receive the
* {@link #ACTION_APP_ERROR} intent.
- *
+ *
* @hide
*/
- public static final String EXTRA_INSTALLER_PACKAGE_NAME
+ public static final String EXTRA_INSTALLER_PACKAGE_NAME
= "android.intent.extra.INSTALLER_PACKAGE_NAME";
// ---------------------------------------------------------------------
@@ -2040,10 +2104,25 @@ public class Intent implements Parcelable {
public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000;
// ---------------------------------------------------------------------
+ // ---------------------------------------------------------------------
+ // toUri() and parseUri() options.
+
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string
+ * always has the "intent:" scheme. This syntax can be used when you want
+ * to later disambiguate between URIs that are intended to describe an
+ * Intent vs. all others that should be treated as raw URIs. When used
+ * with {@link #parseUri}, any other scheme will result in a generic
+ * VIEW action for that raw URI.
+ */
+ public static final int URI_INTENT_SCHEME = 1<<0;
+
+ // ---------------------------------------------------------------------
private String mAction;
private Uri mData;
private String mType;
+ private String mPackage;
private ComponentName mComponent;
private int mFlags;
private HashSet<String> mCategories;
@@ -2064,6 +2143,7 @@ public class Intent implements Parcelable {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
+ this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
if (o.mCategories != null) {
@@ -2083,6 +2163,7 @@ public class Intent implements Parcelable {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
+ this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new HashSet<String>(o.mCategories);
@@ -2183,23 +2264,50 @@ public class Intent implements Parcelable {
}
/**
+ * Call {@link #parseUri} with 0 flags.
+ * @deprecated Use {@link #parseUri} instead.
+ */
+ @Deprecated
+ public static Intent getIntent(String uri) throws URISyntaxException {
+ return parseUri(uri, 0);
+ }
+
+ /**
* Create an intent from a URI. This URI may encode the action,
- * category, and other intent fields, if it was returned by toURI(). If
- * the Intent was not generate by toURI(), its data will be the entire URI
- * and its action will be ACTION_VIEW.
+ * category, and other intent fields, if it was returned by
+ * {@link #toUri}.. If the Intent was not generate by toUri(), its data
+ * will be the entire URI and its action will be ACTION_VIEW.
*
* <p>The URI given here must not be relative -- that is, it must include
* the scheme and full path.
*
* @param uri The URI to turn into an Intent.
+ * @param flags Additional processing flags. Either 0 or
*
* @return Intent The newly created Intent object.
*
- * @see #toURI
+ * @throws URISyntaxException Throws URISyntaxError if the basic URI syntax
+ * it bad (as parsed by the Uri class) or the Intent data within the
+ * URI is invalid.
+ *
+ * @see #toUri
*/
- public static Intent getIntent(String uri) throws URISyntaxException {
+ public static Intent parseUri(String uri, int flags) throws URISyntaxException {
int i = 0;
try {
+ // Validate intent scheme for if requested.
+ if ((flags&URI_INTENT_SCHEME) != 0) {
+ if (!uri.startsWith("intent:")) {
+ Intent intent = new Intent(ACTION_VIEW);
+ try {
+ intent.setData(Uri.parse(uri));
+ } catch (IllegalArgumentException e) {
+ throw new URISyntaxException(uri, e.getMessage());
+ }
+ return intent;
+ }
+ }
+
// simple case
i = uri.lastIndexOf("#");
if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
@@ -2211,16 +2319,15 @@ public class Intent implements Parcelable {
Intent intent = new Intent(ACTION_VIEW);
// fetch data part, if present
- if (i > 0) {
- intent.mData = Uri.parse(uri.substring(0, i));
- }
+ String data = i >= 0 ? uri.substring(0, i) : null;
+ String scheme = null;
i += "#Intent;".length();
// loop over contents of Intent, all name=value;
while (!uri.startsWith("end", i)) {
int eq = uri.indexOf('=', i);
int semi = uri.indexOf(';', eq);
- String value = uri.substring(eq + 1, semi);
+ String value = Uri.decode(uri.substring(eq + 1, semi));
// action
if (uri.startsWith("action=", i)) {
@@ -2242,15 +2349,24 @@ public class Intent implements Parcelable {
intent.mFlags = Integer.decode(value).intValue();
}
+ // package
+ else if (uri.startsWith("package=", i)) {
+ intent.mPackage = value;
+ }
+
// component
else if (uri.startsWith("component=", i)) {
intent.mComponent = ComponentName.unflattenFromString(value);
}
+ // scheme
+ else if (uri.startsWith("scheme=", i)) {
+ scheme = value;
+ }
+
// extra
else {
String key = Uri.decode(uri.substring(i + 2, eq));
- value = Uri.decode(value);
// create Bundle if it doesn't already exist
if (intent.mExtras == null) intent.mExtras = new Bundle();
Bundle b = intent.mExtras;
@@ -2271,6 +2387,23 @@ public class Intent implements Parcelable {
i = semi + 1;
}
+ if (data != null) {
+ if (data.startsWith("intent:")) {
+ data = data.substring(7);
+ if (scheme != null) {
+ data = scheme + ':' + data;
+ }
+ }
+
+ if (data.length() > 0) {
+ try {
+ intent.mData = Uri.parse(data);
+ } catch (IllegalArgumentException e) {
+ throw new URISyntaxException(uri, e.getMessage());
+ }
+ }
+ }
+
return intent;
} catch (IndexOutOfBoundsException e) {
@@ -3084,6 +3217,20 @@ public class Intent implements Parcelable {
}
/**
+ * Retrieve the application package name this Intent is limited to. When
+ * resolving an Intent, if non-null this limits the resolution to only
+ * components in the given application package.
+ *
+ * @return The name of the application package for the Intent.
+ *
+ * @see #resolveActivity
+ * @see #setPackage
+ */
+ public String getPackage() {
+ return mPackage;
+ }
+
+ /**
* Retrieve the concrete component associated with the intent. When receiving
* an intent, this is the component that was found to best handle it (that is,
* yourself) and will always be non-null; in all other cases it will be
@@ -3118,6 +3265,9 @@ public class Intent implements Parcelable {
* <p>If {@link #addCategory} has added any categories, the activity must
* handle ALL of the categories specified.
*
+ * <p>If {@link #getPackage} is non-NULL, only activity components in
+ * that application package will be considered.
+ *
* <p>If there are no activities that satisfy all of these conditions, a
* null string is returned.
*
@@ -3239,7 +3389,7 @@ public class Intent implements Parcelable {
* only specify a type and not data, for example to indicate the type of
* data to return. This method automatically clears any data that was
* previously set by {@link #setData}.
- *
+ *
* <p><em>Note: MIME type matching in the Android framework is
* case-sensitive, unlike formal RFC MIME types. As a result,
* you should always write your MIME types with lower case letters,
@@ -4089,6 +4239,27 @@ public class Intent implements Parcelable {
}
/**
+ * (Usually optional) Set an explicit application package name that limits
+ * the components this Intent will resolve to. If left to the default
+ * value of null, all components in all applications will considered.
+ * If non-null, the Intent can only match the components in the given
+ * application package.
+ *
+ * @param packageName The name of the application package to handle the
+ * intent, or null to allow any application package.
+ *
+ * @return Returns the same Intent object, for chaining multiple calls
+ * into a single statement.
+ *
+ * @see #getPackage
+ * @see #resolveActivity
+ */
+ public Intent setPackage(String packageName) {
+ mPackage = packageName;
+ return this;
+ }
+
+ /**
* (Usually optional) Explicitly set the component to handle the intent.
* If left with the default value of null, the system will determine the
* appropriate class to use based on the other fields (action, data,
@@ -4200,6 +4371,12 @@ public class Intent implements Parcelable {
public static final int FILL_IN_COMPONENT = 1<<3;
/**
+ * Use with {@link #fillIn} to allow the current package value to be
+ * overwritten, even if it is already set.
+ */
+ public static final int FILL_IN_PACKAGE = 1<<4;
+
+ /**
* Copy the contents of <var>other</var> in to this object, but only
* where fields are not defined by this object. For purposes of a field
* being defined, the following pieces of data in the Intent are
@@ -4210,14 +4387,15 @@ public class Intent implements Parcelable {
* <li> data URI and MIME type, as set by {@link #setData(Uri)},
* {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}.
* <li> categories, as set by {@link #addCategory}.
+ * <li> package, as set by {@link #setPackage}.
* <li> component, as set by {@link #setComponent(ComponentName)} or
* related methods.
* <li> each top-level name in the associated extras.
* </ul>
*
* <p>In addition, you can use the {@link #FILL_IN_ACTION},
- * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
- * {@link #FILL_IN_COMPONENT} to override the restriction where the
+ * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
+ * and {@link #FILL_IN_COMPONENT} to override the restriction where the
* corresponding field will not be replaced if it is already set.
*
* <p>For example, consider Intent A with {data="foo", categories="bar"}
@@ -4233,32 +4411,39 @@ public class Intent implements Parcelable {
* @param flags Options to control which fields can be filled in.
*
* @return Returns a bit mask of {@link #FILL_IN_ACTION},
- * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and
- * {@link #FILL_IN_COMPONENT} indicating which fields were changed.
+ * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE},
+ * and {@link #FILL_IN_COMPONENT} indicating which fields were changed.
*/
public int fillIn(Intent other, int flags) {
int changes = 0;
- if ((mAction == null && other.mAction == null)
- || (flags&FILL_IN_ACTION) != 0) {
+ if (other.mAction != null
+ && (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
mAction = other.mAction;
changes |= FILL_IN_ACTION;
}
- if ((mData == null && mType == null &&
- (other.mData != null || other.mType != null))
- || (flags&FILL_IN_DATA) != 0) {
+ if ((other.mData != null || other.mType != null)
+ && ((mData == null && mType == null)
+ || (flags&FILL_IN_DATA) != 0)) {
mData = other.mData;
mType = other.mType;
changes |= FILL_IN_DATA;
}
- if ((mCategories == null && other.mCategories == null)
- || (flags&FILL_IN_CATEGORIES) != 0) {
+ if (other.mCategories != null
+ && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
if (other.mCategories != null) {
mCategories = new HashSet<String>(other.mCategories);
}
changes |= FILL_IN_CATEGORIES;
}
- if ((mComponent == null && other.mComponent == null)
- || (flags&FILL_IN_COMPONENT) != 0) {
+ if (other.mPackage != null
+ && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
+ mPackage = other.mPackage;
+ changes |= FILL_IN_PACKAGE;
+ }
+ // Component is special: it can -only- be set if explicitly allowed,
+ // since otherwise the sender could force the intent somewhere the
+ // originator didn't intend.
+ if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
mComponent = other.mComponent;
changes |= FILL_IN_COMPONENT;
}
@@ -4373,6 +4558,17 @@ public class Intent implements Parcelable {
}
}
}
+ if (mPackage != other.mPackage) {
+ if (mPackage != null) {
+ if (!mPackage.equals(other.mPackage)) {
+ return false;
+ }
+ } else {
+ if (!other.mPackage.equals(mPackage)) {
+ return false;
+ }
+ }
+ }
if (mComponent != other.mComponent) {
if (mComponent != null) {
if (!mComponent.equals(other.mComponent)) {
@@ -4418,6 +4614,9 @@ public class Intent implements Parcelable {
if (mType != null) {
code += mType.hashCode();
}
+ if (mPackage != null) {
+ code += mPackage.hashCode();
+ }
if (mComponent != null) {
code += mComponent.hashCode();
}
@@ -4444,7 +4643,7 @@ public class Intent implements Parcelable {
toShortString(b, comp, extras);
return b.toString();
}
-
+
/** @hide */
public void toShortString(StringBuilder b, boolean comp, boolean extras) {
boolean first = true;
@@ -4488,6 +4687,13 @@ public class Intent implements Parcelable {
first = false;
b.append("flg=0x").append(Integer.toHexString(mFlags));
}
+ if (mPackage != null) {
+ if (!first) {
+ b.append(' ');
+ }
+ first = false;
+ b.append("pkg=").append(mPackage);
+ }
if (comp && mComponent != null) {
if (!first) {
b.append(' ');
@@ -4504,28 +4710,87 @@ public class Intent implements Parcelable {
}
}
+ /**
+ * Call {@link #toUri} with 0 flags.
+ * @deprecated Use {@link #toUri} instead.
+ */
+ @Deprecated
public String toURI() {
+ return toUri(0);
+ }
+
+ /**
+ * Convert this Intent into a String holding a URI representation of it.
+ * The returned URI string has been properly URI encoded, so it can be
+ * used with {@link Uri#parse Uri.parse(String)}. The URI contains the
+ * Intent's data as the base URI, with an additional fragment describing
+ * the action, categories, type, flags, package, component, and extras.
+ *
+ * <p>You can convert the returned string back to an Intent with
+ * {@link #getIntent}.
+ *
+ * @param flags Additional operating flags. Either 0 or
+ * {@link #URI_INTENT_SCHEME}.
+ *
+ * @return Returns a URI encoding URI string describing the entire contents
+ * of the Intent.
+ */
+ public String toUri(int flags) {
StringBuilder uri = new StringBuilder(128);
- if (mData != null) uri.append(mData.toString());
+ String scheme = null;
+ if (mData != null) {
+ String data = mData.toString();
+ if ((flags&URI_INTENT_SCHEME) != 0) {
+ final int N = data.length();
+ for (int i=0; i<N; i++) {
+ char c = data.charAt(i);
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || c == '.' || c == '-') {
+ continue;
+ }
+ if (c == ':' && i > 0) {
+ // Valid scheme.
+ scheme = data.substring(0, i);
+ uri.append("intent:");
+ data = data.substring(i+1);
+ break;
+ }
+
+ // No scheme.
+ break;
+ }
+ }
+ uri.append(data);
+
+ } else if ((flags&URI_INTENT_SCHEME) != 0) {
+ uri.append("intent:");
+ }
uri.append("#Intent;");
+ if (scheme != null) {
+ uri.append("scheme=").append(scheme).append(';');
+ }
if (mAction != null) {
- uri.append("action=").append(mAction).append(';');
+ uri.append("action=").append(Uri.encode(mAction)).append(';');
}
if (mCategories != null) {
for (String category : mCategories) {
- uri.append("category=").append(category).append(';');
+ uri.append("category=").append(Uri.encode(category)).append(';');
}
}
if (mType != null) {
- uri.append("type=").append(mType).append(';');
+ uri.append("type=").append(Uri.encode(mType, "/")).append(';');
}
if (mFlags != 0) {
uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
}
+ if (mPackage != null) {
+ uri.append("package=").append(Uri.encode(mPackage)).append(';');
+ }
if (mComponent != null) {
- uri.append("component=").append(mComponent.flattenToShortString()).append(';');
+ uri.append("component=").append(Uri.encode(
+ mComponent.flattenToShortString(), "/")).append(';');
}
if (mExtras != null) {
for (String key : mExtras.keySet()) {
@@ -4567,6 +4832,7 @@ public class Intent implements Parcelable {
Uri.writeToParcel(out, mData);
out.writeString(mType);
out.writeInt(mFlags);
+ out.writeString(mPackage);
ComponentName.writeToParcel(mComponent, out);
if (mCategories != null) {
@@ -4600,6 +4866,7 @@ public class Intent implements Parcelable {
mData = Uri.CREATOR.createFromParcel(in);
mType = in.readString();
mFlags = in.readInt();
+ mPackage = in.readString();
mComponent = ComponentName.readFromParcel(in);
int N = in.readInt();
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index e5c5dc8..365f269 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -366,6 +366,7 @@ public class IntentFilter implements Parcelable {
throws MalformedMimeTypeException {
mPriority = 0;
mActions = new ArrayList<String>();
+ addAction(action);
addDataType(dataType);
}
diff --git a/core/java/android/content/IntentSender.aidl b/core/java/android/content/IntentSender.aidl
new file mode 100644
index 0000000..741bc8c
--- /dev/null
+++ b/core/java/android/content/IntentSender.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.content;
+
+parcelable IntentSender;
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
new file mode 100644
index 0000000..4da49d9
--- /dev/null
+++ b/core/java/android/content/IntentSender.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IIntentSender;
+import android.content.IIntentReceiver;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AndroidException;
+
+
+/**
+ * A description of an Intent and target action to perform with it.
+ * The returned object can be
+ * handed to other applications so that they can perform the action you
+ * described on your behalf at a later time.
+ *
+ * <p>By giving a IntentSender to another application,
+ * you are granting it the right to perform the operation you have specified
+ * as if the other application was yourself (with the same permissions and
+ * identity). As such, you should be careful about how you build the IntentSender:
+ * often, for example, the base Intent you supply will have the component
+ * name explicitly set to one of your own components, to ensure it is ultimately
+ * sent there and nowhere else.
+ *
+ * <p>A IntentSender itself is simply a reference to a token maintained by
+ * the system describing the original data used to retrieve it. This means
+ * that, even if its owning application's process is killed, the
+ * IntentSender itself will remain usable from other processes that
+ * have been given it. If the creating application later re-retrieves the
+ * same kind of IntentSender (same operation, same Intent action, data,
+ * categories, and components, and same flags), it will receive a IntentSender
+ * representing the same token if that is still valid.
+ *
+ */
+public class IntentSender implements Parcelable {
+ private final IIntentSender mTarget;
+
+ /**
+ * Exception thrown when trying to send through a PendingIntent that
+ * has been canceled or is otherwise no longer able to execute the request.
+ */
+ public static class SendIntentException extends AndroidException {
+ public SendIntentException() {
+ }
+
+ public SendIntentException(String name) {
+ super(name);
+ }
+
+ public SendIntentException(Exception cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * Callback interface for discovering when a send operation has
+ * completed. Primarily for use with a IntentSender that is
+ * performing a broadcast, this provides the same information as
+ * calling {@link Context#sendOrderedBroadcast(Intent, String,
+ * android.content.BroadcastReceiver, Handler, int, String, Bundle)
+ * Context.sendBroadcast()} with a final BroadcastReceiver.
+ */
+ public interface OnFinished {
+ /**
+ * Called when a send operation as completed.
+ *
+ * @param IntentSender The IntentSender this operation was sent through.
+ * @param intent The original Intent that was sent.
+ * @param resultCode The final result code determined by the send.
+ * @param resultData The final data collected by a broadcast.
+ * @param resultExtras The final extras collected by a broadcast.
+ */
+ void onSendFinished(IntentSender IntentSender, Intent intent,
+ int resultCode, String resultData, Bundle resultExtras);
+ }
+
+ private static class FinishedDispatcher extends IIntentReceiver.Stub
+ implements Runnable {
+ private final IntentSender mIntentSender;
+ private final OnFinished mWho;
+ private final Handler mHandler;
+ private Intent mIntent;
+ private int mResultCode;
+ private String mResultData;
+ private Bundle mResultExtras;
+ FinishedDispatcher(IntentSender pi, OnFinished who, Handler handler) {
+ mIntentSender = pi;
+ mWho = who;
+ mHandler = handler;
+ }
+ public void performReceive(Intent intent, int resultCode,
+ String data, Bundle extras, boolean serialized) {
+ mIntent = intent;
+ mResultCode = resultCode;
+ mResultData = data;
+ mResultExtras = extras;
+ if (mHandler == null) {
+ run();
+ } else {
+ mHandler.post(this);
+ }
+ }
+ public void run() {
+ mWho.onSendFinished(mIntentSender, mIntent, mResultCode,
+ mResultData, mResultExtras);
+ }
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be null if
+ * <var>intent</var> is also null.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use null to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or null for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If null, the callback will happen from the thread
+ * pool of the process.
+ *
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ */
+ public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler) throws SendIntentException {
+ try {
+ String resolvedType = intent != null ?
+ intent.resolveTypeIfNeeded(context.getContentResolver())
+ : null;
+ int res = mTarget.send(code, intent, resolvedType,
+ onFinished != null
+ ? new FinishedDispatcher(this, onFinished, handler)
+ : null);
+ if (res < 0) {
+ throw new SendIntentException();
+ }
+ } catch (RemoteException e) {
+ throw new SendIntentException();
+ }
+ }
+
+ /**
+ * Comparison operator on two IntentSender objects, such that true
+ * is returned then they both represent the same operation from the
+ * same package.
+ */
+ @Override
+ public boolean equals(Object otherObj) {
+ if (otherObj instanceof IntentSender) {
+ return mTarget.asBinder().equals(((IntentSender)otherObj)
+ .mTarget.asBinder());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mTarget.asBinder().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("IntentSender{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(": ");
+ sb.append(mTarget != null ? mTarget.asBinder() : null);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mTarget.asBinder());
+ }
+
+ public static final Parcelable.Creator<IntentSender> CREATOR
+ = new Parcelable.Creator<IntentSender>() {
+ public IntentSender createFromParcel(Parcel in) {
+ IBinder target = in.readStrongBinder();
+ return target != null ? new IntentSender(target) : null;
+ }
+
+ public IntentSender[] newArray(int size) {
+ return new IntentSender[size];
+ }
+ };
+
+ /**
+ * Convenience function for writing either a IntentSender or null pointer to
+ * a Parcel. You must use this with {@link #readIntentSenderOrNullFromParcel}
+ * for later reading it.
+ *
+ * @param sender The IntentSender to write, or null.
+ * @param out Where to write the IntentSender.
+ */
+ public static void writeIntentSenderOrNullToParcel(IntentSender sender,
+ Parcel out) {
+ out.writeStrongBinder(sender != null ? sender.mTarget.asBinder()
+ : null);
+ }
+
+ /**
+ * Convenience function for reading either a Messenger or null pointer from
+ * a Parcel. You must have previously written the Messenger with
+ * {@link #writeIntentSenderOrNullToParcel}.
+ *
+ * @param in The Parcel containing the written Messenger.
+ *
+ * @return Returns the Messenger read from the Parcel, or null if null had
+ * been written.
+ */
+ public static IntentSender readIntentSenderOrNullFromParcel(Parcel in) {
+ IBinder b = in.readStrongBinder();
+ return b != null ? new IntentSender(b) : null;
+ }
+
+ protected IntentSender(IIntentSender target) {
+ mTarget = target;
+ }
+
+ protected IntentSender(IBinder target) {
+ mTarget = IIntentSender.Stub.asInterface(target);
+ }
+}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 9c25e73..f781e0d 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.backup.IBackupManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -35,6 +36,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -351,8 +353,18 @@ public class SyncStorageEngine extends Handler {
}
}
}
+ // Inform the backup manager about a data change
+ IBackupManager ibm = IBackupManager.Stub.asInterface(
+ ServiceManager.getService(Context.BACKUP_SERVICE));
+ if (ibm != null) {
+ try {
+ ibm.dataChanged("com.android.providers.settings");
+ } catch (RemoteException e) {
+ // Try again later
+ }
+ }
}
-
+
public boolean getSyncProviderAutomatically(String account, String providerName) {
synchronized (mAuthorities) {
if (account != null) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 85d877a..27783ef 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -235,6 +235,12 @@ public class ActivityInfo extends ComponentInfo
public static final int CONFIG_ORIENTATION = 0x0080;
/**
* Bit in {@link #configChanges} that indicates that the activity
+ * can itself handle changes to the screen layout. Set from the
+ * {@link android.R.attr#configChanges} attribute.
+ */
+ public static final int CONFIG_SCREEN_LAYOUT = 0x0100;
+ /**
+ * Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the font scaling factor. Set from the
* {@link android.R.attr#configChanges} attribute. This is
* not a core resource configutation, but a higher-level value, so its
@@ -248,8 +254,8 @@ public class ActivityInfo extends ComponentInfo
* Contains any combination of {@link #CONFIG_FONT_SCALE},
* {@link #CONFIG_MCC}, {@link #CONFIG_MNC},
* {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
- * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION}, and
- * {@link #CONFIG_ORIENTATION}. Set from the
+ * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
+ * {@link #CONFIG_ORIENTATION}, and {@link #CONFIG_SCREEN_LAYOUT}. Set from the
* {@link android.R.attr#configChanges} attribute.
*/
public int configChanges;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 88ac04c..bcf95b6 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -58,11 +58,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* Class implementing the Application's manage space
* functionality. From the "manageSpaceActivity"
* attribute. This is an optional attribute and will be null if
- * application's dont specify it in their manifest
+ * applications don't specify it in their manifest
*/
public String manageSpaceActivityName;
/**
+ * Class implementing the Application's backup functionality. From
+ * the "backupAgent" attribute. This is an optional attribute and
+ * will be null if the application does not specify it in its manifest.
+ *
+ * <p>If android:allowBackup is set to false, this attribute is ignored.
+ *
+ * {@hide}
+ */
+ public String backupAgentName;
+
+ /**
* Value for {@link #flags}: if set, this application is installed in the
* device's system image.
*/
@@ -93,7 +104,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int FLAG_PERSISTENT = 1<<3;
/**
- * Value for {@link #flags}: set to true iif this application holds the
+ * Value for {@link #flags}: set to true if this application holds the
* {@link android.Manifest.permission#FACTORY_TEST} permission and the
* device is running in factory test mode.
*/
@@ -123,13 +134,46 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* Value for {@link #flags}: this is set of the application has set
* its android:targetSdkVersion to something >= the current SDK version.
*/
- public static final int FLAG_TARGETS_SDK = 1<<8;
+ public static final int FLAG_TEST_ONLY = 1<<8;
/**
- * Value for {@link #flags}: this is set of the application has set
- * its android:targetSdkVersion to something >= the current SDK version.
+ * Value for {@link #flags}: true when the application's window can be
+ * reduced in size for smaller screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_smallScreens
+ * android:smallScreens}.
*/
- public static final int FLAG_TEST_ONLY = 1<<9;
+ public static final int FLAG_SUPPORTS_SMALL_SCREENS = 1<<9;
+
+ /**
+ * Value for {@link #flags}: true when the application's window can be
+ * displayed on normal screens. Corresponds to
+ * {@link android.R.styleable#AndroidManifestSupportsScreens_normalScreens
+ * android:normalScreens}.
+ */
+ public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1<<10;
+
+ /**
+ * 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}.
+ */
+ public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11;
+
+ /**
+ * Value for {@link #flags}: this is false if the application has set
+ * its android:allowBackup to false, true otherwise.
+ *
+ * {@hide}
+ */
+ public static final int FLAG_ALLOW_BACKUP = 1<<12;
+
+ /**
+ * Indicates that the application supports any densities;
+ * {@hide}
+ */
+ public static final int ANY_DENSITY = -1;
+ private static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY };
/**
* Flags associated with the application. Any combination of
@@ -137,7 +181,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@link #FLAG_PERSISTENT}, {@link #FLAG_FACTORY_TEST}, and
* {@link #FLAG_ALLOW_TASK_REPARENTING}
* {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP},
- * {@link #FLAG_TARGETS_SDK}.
+ * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS},
+ * {@link #FLAG_SUPPORTS_NORMAL_SCREENS},
+ * {@link #FLAG_SUPPORTS_LARGE_SCREENS}.
*/
public int flags = 0;
@@ -173,7 +219,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public int uid;
-
/**
* The list of densities in DPI that application supprots. This
* field is only set if the {@link PackageManager#GET_SUPPORTS_DENSITIES} flag was
@@ -182,6 +227,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public int[] supportsDensities;
/**
+ * The minimum SDK version this application targets. It may run on earilier
+ * versions, but it knows how to work with any new behavior added at this
+ * version. Will be {@link android.os.Build.VERSION_CODES#CUR_DEVELOPMENT}
+ * if this is a development build and the app is targeting that. You should
+ * compare that this number is >= the SDK version number at which your
+ * behavior was introduced.
+ */
+ public int targetSdkVersion;
+
+ /**
* When false, indicates that all components within this application are
* considered disabled, regardless of their individually set enabled status.
*/
@@ -200,6 +255,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
pw.println(prefix + "publicSourceDir=" + publicSourceDir);
pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
pw.println(prefix + "dataDir=" + dataDir);
+ pw.println(prefix + "targetSdkVersion=" + targetSdkVersion);
pw.println(prefix + "enabled=" + enabled);
pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName);
pw.println(prefix + "description=0x"+Integer.toHexString(descriptionRes));
@@ -246,6 +302,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
sharedLibraryFiles = orig.sharedLibraryFiles;
dataDir = orig.dataDir;
uid = orig.uid;
+ targetSdkVersion = orig.targetSdkVersion;
enabled = orig.enabled;
manageSpaceActivityName = orig.manageSpaceActivityName;
descriptionRes = orig.descriptionRes;
@@ -276,8 +333,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeStringArray(sharedLibraryFiles);
dest.writeString(dataDir);
dest.writeInt(uid);
+ dest.writeInt(targetSdkVersion);
dest.writeInt(enabled ? 1 : 0);
dest.writeString(manageSpaceActivityName);
+ dest.writeString(backupAgentName);
dest.writeInt(descriptionRes);
dest.writeIntArray(supportsDensities);
}
@@ -305,8 +364,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
sharedLibraryFiles = source.readStringArray();
dataDir = source.readString();
uid = source.readInt();
+ targetSdkVersion = source.readInt();
enabled = source.readInt() != 0;
manageSpaceActivityName = source.readString();
+ backupAgentName = source.readString();
descriptionRes = source.readInt();
supportsDensities = source.createIntArray();
}
@@ -331,4 +392,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
return null;
}
+
+ /**
+ * Disable compatibility mode
+ *
+ * @hide
+ */
+ public void disableCompatibilityMode() {
+ flags |= FLAG_SUPPORTS_LARGE_SCREENS;
+ supportsDensities = ANY_DENSITIES_ARRAY;
+ }
}
diff --git a/core/java/android/content/pm/ConfigurationInfo.java b/core/java/android/content/pm/ConfigurationInfo.java
index dcc7463..fb7a47f 100755
--- a/core/java/android/content/pm/ConfigurationInfo.java
+++ b/core/java/android/content/pm/ConfigurationInfo.java
@@ -22,7 +22,7 @@ import android.os.Parcelable;
/**
* Information you can retrieve about hardware configuration preferences
* declared by an application. This corresponds to information collected from the
- * AndroidManifest.xml's &lt;uses-configuration&gt; tags.
+ * AndroidManifest.xml's &lt;uses-configuration&gt; and the &lt;uses-feature&gt;tags.
*/
public class ConfigurationInfo implements Parcelable {
/**
@@ -70,6 +70,16 @@ public class ConfigurationInfo implements Parcelable {
*/
public int reqInputFeatures = 0;
+ /**
+ * Default value for {@link #reqGlEsVersion};
+ */
+ public static final int GL_ES_VERSION_UNDEFINED = 0;
+ /**
+ * The GLES version used by an application. The upper order 16 bits represent the
+ * major version and the lower order 16 bits the minor version.
+ */
+ public int reqGlEsVersion;
+
public ConfigurationInfo() {
}
@@ -78,6 +88,7 @@ public class ConfigurationInfo implements Parcelable {
reqKeyboardType = orig.reqKeyboardType;
reqNavigation = orig.reqNavigation;
reqInputFeatures = orig.reqInputFeatures;
+ reqGlEsVersion = orig.reqGlEsVersion;
}
public String toString() {
@@ -86,7 +97,8 @@ public class ConfigurationInfo implements Parcelable {
+ ", touchscreen = " + reqTouchScreen + "}"
+ ", inputMethod = " + reqKeyboardType + "}"
+ ", navigation = " + reqNavigation + "}"
- + ", reqInputFeatures = " + reqInputFeatures + "}";
+ + ", reqInputFeatures = " + reqInputFeatures + "}"
+ + ", reqGlEsVersion = " + reqGlEsVersion + "}";
}
public int describeContents() {
@@ -98,6 +110,7 @@ public class ConfigurationInfo implements Parcelable {
dest.writeInt(reqKeyboardType);
dest.writeInt(reqNavigation);
dest.writeInt(reqInputFeatures);
+ dest.writeInt(reqGlEsVersion);
}
public static final Creator<ConfigurationInfo> CREATOR =
@@ -115,5 +128,18 @@ public class ConfigurationInfo implements Parcelable {
reqKeyboardType = source.readInt();
reqNavigation = source.readInt();
reqInputFeatures = source.readInt();
+ reqGlEsVersion = source.readInt();
+ }
+
+ /**
+ * This method extracts the major and minor version of reqGLEsVersion attribute
+ * and returns it as a string. Say reqGlEsVersion value of 0x00010002 is returned
+ * as 1.2
+ * @return String representation of the reqGlEsVersion attribute
+ */
+ public String getGlEsVersion() {
+ int major = ((reqGlEsVersion & 0xffff0000) >> 16);
+ int minor = reqGlEsVersion & 0x0000ffff;
+ return String.valueOf(major)+"."+String.valueOf(minor);
}
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c199619..bf2a895 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -34,7 +34,7 @@ import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.net.Uri;
-import android.app.PendingIntent;
+import android.content.IntentSender;
/**
* See {@link PackageManager} for documentation on most of the APIs
@@ -164,7 +164,12 @@ interface IPackageManager {
void addPreferredActivity(in IntentFilter filter, int match,
in ComponentName[] set, in ComponentName activity);
+
+ void replacePreferredActivity(in IntentFilter filter, int match,
+ in ComponentName[] set, in ComponentName activity);
+
void clearPackagePreferredActivities(String packageName);
+
int getPreferredActivities(out List<IntentFilter> outFilters,
out List<ComponentName> outActivities, String packageName);
@@ -229,12 +234,12 @@ interface IPackageManager {
* and the current free storage is YY,
* if XX is less than YY, just return. if not free XX-YY number
* of bytes if possible.
- * @param opFinishedIntent PendingIntent call back used to
+ * @param pi IntentSender call back used to
* notify when the operation is completed.May be null
* to indicate that no call back is desired.
*/
void freeStorage(in long freeStorageSize,
- in PendingIntent opFinishedIntent);
+ in IntentSender pi);
/**
* Delete all the cache files in an applications cache directory
@@ -271,4 +276,11 @@ interface IPackageManager {
boolean isSafeMode();
void systemReady();
boolean hasSystemUidErrors();
+
+ /**
+ * Ask the package manager to perform dex-opt (if needed) on the given
+ * package, if it already hasn't done mode. Only does this if running
+ * in the special development "no pre-dexopt" mode.
+ */
+ boolean performDexOpt(String packageName);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3a192f7..941ca9e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,12 +16,11 @@
package android.content.pm;
-
-import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
@@ -398,6 +397,15 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_TEST_ONLY = -15;
/**
+ * Installation return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if
+ * the package being installed contains native code, but none that is
+ * compatible with the the device's CPU_ABI.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_CPU_ABI_INCOMPATIBLE = -16;
+
+ /**
* Installation parse return code: this is passed to the {@link IPackageInstallObserver} by
* {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
* if the parser was given a path that is not a file, or does not end with the expected
@@ -563,9 +571,8 @@ public abstract class PackageManager {
* launch the main activity in the package, or null if the package does
* not contain such an activity.
*/
- public abstract Intent getLaunchIntentForPackage(String packageName)
- throws NameNotFoundException;
-
+ public abstract Intent getLaunchIntentForPackage(String packageName);
+
/**
* Return an array of all of the secondary group-ids that have been
* assigned to a package.
@@ -1491,7 +1498,7 @@ public abstract class PackageManager {
* @hide
*/
public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer);
-
+
/**
* Free storage by deleting LRU sorted list of cache files across
* all applications. If the currently available free storage
@@ -1509,13 +1516,13 @@ public abstract class PackageManager {
* and the current free storage is YY,
* if XX is less than YY, just return. if not free XX-YY number
* of bytes if possible.
- * @param opFinishedIntent PendingIntent call back used to
+ * @param pi IntentSender call back used to
* notify when the operation is completed.May be null
* to indicate that no call back is desired.
*
* @hide
*/
- public abstract void freeStorage(long freeStorageSize, PendingIntent opFinishedIntent);
+ public abstract void freeStorage(long freeStorageSize, IntentSender pi);
/**
* Retrieve the size information for a package.
@@ -1605,6 +1612,26 @@ public abstract class PackageManager {
ComponentName[] set, ComponentName activity);
/**
+ * Replaces an existing preferred activity mapping to the system, and if that were not present
+ * adds a new preferred activity. This will be used
+ * to automatically select the given activity component when
+ * {@link Context#startActivity(Intent) Context.startActivity()} finds
+ * multiple matching activities and also matches the given filter.
+ *
+ * @param filter The set of intents under which this activity will be
+ * made preferred.
+ * @param match The IntentFilter match category that this preference
+ * applies to.
+ * @param set The set of activities that the user was picking from when
+ * this preference was made.
+ * @param activity The component name of the activity that is to be
+ * preferred.
+ * @hide
+ */
+ public abstract void replacePreferredActivity(IntentFilter filter, int match,
+ ComponentName[] set, ComponentName activity);
+
+ /**
* Remove all preferred activity mappings, previously added with
* {@link #addPreferredActivity}, from the
* system whose activities are implemented in the given package name.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 88907c1..558b0c3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -55,6 +55,32 @@ import java.util.jar.JarFile;
* {@hide}
*/
public class PackageParser {
+ /** @hide */
+ public static class NewPermissionInfo {
+ public final String name;
+ public final int sdkVersion;
+ public final int fileVersion;
+
+ public NewPermissionInfo(String name, int sdkVersion, int fileVersion) {
+ this.name = name;
+ this.sdkVersion = sdkVersion;
+ this.fileVersion = fileVersion;
+ }
+ }
+
+ /**
+ * List of new permissions that have been added since 1.0.
+ * NOTE: These must be declared in SDK version order, with permissions
+ * added to older SDKs appearing before those added to newer SDKs.
+ * @hide
+ */
+ public static final PackageParser.NewPermissionInfo NEW_PERMISSIONS[] =
+ new PackageParser.NewPermissionInfo[] {
+ new PackageParser.NewPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ android.os.Build.VERSION_CODES.DONUT, 0),
+ new PackageParser.NewPermissionInfo(android.Manifest.permission.READ_PHONE_STATE,
+ android.os.Build.VERSION_CODES.DONUT, 0)
+ };
private String mArchiveSourcePath;
private String[] mSeparateProcesses;
@@ -616,7 +642,6 @@ public class PackageParser {
final Package pkg = new Package(pkgName);
boolean foundApp = false;
- boolean targetsSdk = false;
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifest);
@@ -643,6 +668,11 @@ public class PackageParser {
}
sa.recycle();
+ // Resource boolean are -1, so 1 means we don't know the value.
+ int supportsSmallScreens = 1;
+ int supportsNormalScreens = 1;
+ int supportsLargeScreens = 1;
+
int outerDepth = parser.getDepth();
while ((type=parser.next()) != parser.END_DOCUMENT
&& (type != parser.END_TAG || parser.getDepth() > outerDepth)) {
@@ -723,6 +753,18 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
+ } else if (tagName.equals("uses-feature")) {
+ ConfigurationInfo cPref = new ConfigurationInfo();
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestUsesFeature);
+ cPref.reqGlEsVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestUsesFeature_glEsVersion,
+ ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
+ sa.recycle();
+ pkg.configPreferences.add(cPref);
+
+ XmlUtils.skipCurrentTag(parser);
+
} else if (tagName.equals("uses-sdk")) {
if (mSdkVersion > 0) {
sa = res.obtainAttributes(attrs,
@@ -740,7 +782,7 @@ public class PackageParser {
targetCode = minCode = val.string.toString();
} else {
// If it's not a string, it's an integer.
- minVers = val.data;
+ targetVers = minVers = val.data;
}
}
@@ -761,6 +803,25 @@ public class PackageParser {
sa.recycle();
+ if (minCode != null) {
+ if (!minCode.equals(mSdkCodename)) {
+ if (mSdkCodename != null) {
+ outError[0] = "Requires development platform " + minCode
+ + " (current platform is " + mSdkCodename + ")";
+ } else {
+ outError[0] = "Requires development platform " + minCode
+ + " but this is a release platform.";
+ }
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
+ }
+ } else if (minVers > mSdkVersion) {
+ outError[0] = "Requires newer sdk version #" + minVers
+ + " (current version is #" + mSdkVersion + ")";
+ mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
+ return null;
+ }
+
if (targetCode != null) {
if (!targetCode.equals(mSdkCodename)) {
if (mSdkCodename != null) {
@@ -774,18 +835,10 @@ public class PackageParser {
return null;
}
// If the code matches, it definitely targets this SDK.
- targetsSdk = true;
- } else if (targetVers >= mSdkVersion) {
- // If they have explicitly targeted our current version
- // or something after it, then note this.
- targetsSdk = true;
- }
-
- if (minVers > mSdkVersion) {
- outError[0] = "Requires newer sdk version #" + minVers
- + " (current version is #" + mSdkVersion + ")";
- mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK;
- return null;
+ pkg.applicationInfo.targetSdkVersion
+ = android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
+ } else {
+ pkg.applicationInfo.targetSdkVersion = targetVers;
}
if (maxVers < mSdkVersion) {
@@ -811,6 +864,42 @@ public class PackageParser {
+ parser.getName();
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
+
+
+ } else if (tagName.equals("supports-density")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestSupportsDensity);
+
+ int density = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsDensity_density, -1);
+
+ sa.recycle();
+
+ if (density != -1 && !pkg.supportsDensityList.contains(density)) {
+ pkg.supportsDensityList.add(density);
+ }
+
+ XmlUtils.skipCurrentTag(parser);
+
+ } else if (tagName.equals("supports-screens")) {
+ sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens);
+
+ // This is a trick to get a boolean and still able to detect
+ // if a value was actually set.
+ supportsSmallScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_smallScreens,
+ supportsSmallScreens);
+ supportsNormalScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_normalScreens,
+ supportsNormalScreens);
+ supportsLargeScreens = sa.getInteger(
+ com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens,
+ supportsLargeScreens);
+
+ sa.recycle();
+
+ XmlUtils.skipCurrentTag(parser);
} else {
Log.w(TAG, "Bad element under <manifest>: "
+ parser.getName());
@@ -824,15 +913,39 @@ public class PackageParser {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY;
}
- if (targetsSdk) {
- pkg.applicationInfo.flags |= ApplicationInfo.FLAG_TARGETS_SDK;
+ final int NP = PackageParser.NEW_PERMISSIONS.length;
+ for (int ip=0; ip<NP; ip++) {
+ final PackageParser.NewPermissionInfo npi
+ = PackageParser.NEW_PERMISSIONS[ip];
+ if (pkg.applicationInfo.targetSdkVersion >= npi.sdkVersion) {
+ break;
+ }
+ if (!pkg.requestedPermissions.contains(npi.name)) {
+ Log.i(TAG, "Impliciting adding " + npi.name + " to old pkg "
+ + pkg.packageName);
+ pkg.requestedPermissions.add(npi.name);
+ }
}
if (pkg.usesLibraries.size() > 0) {
pkg.usesLibraryFiles = new String[pkg.usesLibraries.size()];
pkg.usesLibraries.toArray(pkg.usesLibraryFiles);
}
-
+
+ if (supportsSmallScreens < 0 || (supportsSmallScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS;
+ }
+ if (supportsNormalScreens != 0) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS;
+ }
+ if (supportsLargeScreens < 0 || (supportsLargeScreens > 0
+ && pkg.applicationInfo.targetSdkVersion
+ >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
+ pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
+ }
+
int size = pkg.supportsDensityList.size();
if (size > 0) {
int densities[] = pkg.supportsDensities = new int[size];
@@ -1142,6 +1255,19 @@ public class PackageParser {
outError);
}
+ boolean allowBackup = sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_allowBackup, true);
+ if (allowBackup) {
+ ai.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
+ String backupAgent = sa.getNonResourceString(
+ com.android.internal.R.styleable.AndroidManifestApplication_backupAgent);
+ if (backupAgent != null) {
+ ai.backupAgentName = buildClassName(pkgName, backupAgent, outError);
+ Log.v(TAG, "android:backupAgent = " + ai.backupAgentName
+ + " from " + pkgName + "+" + backupAgent);
+ }
+ }
+
TypedValue v = sa.peekValue(
com.android.internal.R.styleable.AndroidManifestApplication_label);
if (v != null && (ai.labelRes=v.resourceId) == 0) {
@@ -1298,21 +1424,6 @@ public class PackageParser {
XmlUtils.skipCurrentTag(parser);
- } else if (tagName.equals("supports-density")) {
- sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestSupportsDensity);
-
- int density = sa.getInteger(
- com.android.internal.R.styleable.AndroidManifestSupportsDensity_density, -1);
-
- sa.recycle();
-
- if (density != -1 && !owner.supportsDensityList.contains(density)) {
- owner.supportsDensityList.add(density);
- }
-
- XmlUtils.skipCurrentTag(parser);
-
} else {
if (!RIGID_PARSER) {
Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
@@ -2219,6 +2330,17 @@ public class PackageParser {
// preferred up order.
public int mPreferredOrder = 0;
+ // For use by package manager service to keep track of which apps
+ // have been installed with forward locking.
+ public boolean mForwardLocked;
+
+ // For use by the package manager to keep track of the path to the
+ // file an app came from.
+ public String mScanPath;
+
+ // For use by package manager to keep track of where it has done dexopt.
+ public boolean mDidDexOpt;
+
// Additional data supplied by callers.
public Object mExtras;
@@ -2368,7 +2490,7 @@ public class PackageParser {
return true;
}
if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0
- && p.supportsDensities != null) {
+ && p.supportsDensities != null) {
return true;
}
return false;
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index 231e3e2..a37e4e8 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.os.MemoryFile;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
@@ -24,6 +25,8 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.channels.FileChannel;
/**
* File descriptor of an entry in the AssetManager. This provides your own
@@ -124,6 +127,13 @@ public class AssetFileDescriptor implements Parcelable {
}
/**
+ * Checks whether this file descriptor is for a memory file.
+ */
+ private boolean isMemoryFile() throws IOException {
+ return MemoryFile.isMemoryFile(mFd.getFileDescriptor());
+ }
+
+ /**
* Create and return a new auto-close input stream for this asset. This
* will either return a full asset {@link AutoCloseInputStream}, or
* an underlying {@link ParcelFileDescriptor.AutoCloseInputStream
@@ -132,6 +142,12 @@ public class AssetFileDescriptor implements Parcelable {
* should only call this once for a particular asset.
*/
public FileInputStream createInputStream() throws IOException {
+ if (isMemoryFile()) {
+ if (mLength > Integer.MAX_VALUE) {
+ throw new IOException("File length too large for a memory file: " + mLength);
+ }
+ return new AutoCloseMemoryFileInputStream(mFd, (int)mLength);
+ }
if (mLength < 0) {
return new ParcelFileDescriptor.AutoCloseInputStream(mFd);
}
@@ -262,6 +278,66 @@ public class AssetFileDescriptor implements Parcelable {
}
/**
+ * An input stream that reads from a MemoryFile and closes it when the stream is closed.
+ * This extends FileInputStream just because {@link #createInputStream} returns
+ * a FileInputStream. All the FileInputStream methods are
+ * overridden to use the MemoryFile instead.
+ */
+ private static class AutoCloseMemoryFileInputStream extends FileInputStream {
+ private ParcelFileDescriptor mParcelFd;
+ private MemoryFile mFile;
+ private InputStream mStream;
+
+ public AutoCloseMemoryFileInputStream(ParcelFileDescriptor fd, int length)
+ throws IOException {
+ super(fd.getFileDescriptor());
+ mParcelFd = fd;
+ mFile = new MemoryFile(fd.getFileDescriptor(), length, "r");
+ mStream = mFile.getInputStream();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mStream.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ mParcelFd.close(); // must close ParcelFileDescriptor, not just the file descriptor,
+ // since it could be a subclass of ParcelFileDescriptor.
+ // E.g. ContentResolver.ParcelFileDescriptorInner.close() releases
+ // a content provider
+ mFile.close(); // to unmap the memory file from the address space.
+ mStream.close(); // doesn't actually do anything
+ }
+
+ @Override
+ public FileChannel getChannel() {
+ return null;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mStream.read();
+ }
+
+ @Override
+ public int read(byte[] buffer, int offset, int count) throws IOException {
+ return mStream.read(buffer, offset, count);
+ }
+
+ @Override
+ public int read(byte[] buffer) throws IOException {
+ return mStream.read(buffer);
+ }
+
+ @Override
+ public long skip(long count) throws IOException {
+ return mStream.skip(count);
+ }
+ }
+
+ /**
* 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.
@@ -345,4 +421,16 @@ public class AssetFileDescriptor implements Parcelable {
return new AssetFileDescriptor[size];
}
};
+
+ /**
+ * Creates an AssetFileDescriptor from a memory file.
+ *
+ * @hide
+ */
+ public static AssetFileDescriptor fromMemoryFile(MemoryFile memoryFile)
+ throws IOException {
+ ParcelFileDescriptor fd = memoryFile.getParcelFileDescriptor();
+ return new AssetFileDescriptor(fd, 0, memoryFile.length());
+ }
+
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 1c91736..5c7b01f 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -601,7 +601,7 @@ public final class AssetManager {
public native final void setConfiguration(int mcc, int mnc, String locale,
int orientation, int touchscreen, int density, int keyboard,
int keyboardHidden, int navigation, int screenWidth, int screenHeight,
- int majorVersion);
+ int screenLayout, int majorVersion);
/**
* Retrieve the resource identifier for the given resource name.
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
new file mode 100644
index 0000000..dfe304d
--- /dev/null
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2006 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.content.pm.ApplicationInfo;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+/**
+ * CompatibilityInfo class keeps the information about compatibility mode that the application is
+ * running under.
+ *
+ * {@hide}
+ */
+public class CompatibilityInfo {
+ private static final boolean DBG = false;
+ private static final String TAG = "CompatibilityInfo";
+
+ /** default compatibility info object for compatible applications */
+ public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo();
+
+ /**
+ * The default width of the screen in portrait mode.
+ */
+ public static final int DEFAULT_PORTRAIT_WIDTH = 320;
+
+ /**
+ * The default height of the screen in portrait mode.
+ */
+ public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
+
+ /**
+ * The x-shift mode that controls the position of the content or the window under
+ * compatibility mode.
+ * {@see getTranslator}
+ * {@see Translator#mShiftMode}
+ */
+ private static final int X_SHIFT_NONE = 0;
+ private static final int X_SHIFT_CONTENT = 1;
+ private static final int X_SHIFT_AND_CLIP_CONTENT = 2;
+ private static final int X_SHIFT_WINDOW = 3;
+
+
+ /**
+ * A compatibility flags
+ */
+ private int mCompatibilityFlags;
+
+ /**
+ * A flag mask to tell if the application needs scaling (when mApplicationScale != 1.0f)
+ * {@see compatibilityFlag}
+ */
+ private static final int SCALING_REQUIRED = 1;
+
+ /**
+ * A flag mask to indicates that the application can expand over the original size.
+ * The flag is set to true if
+ * 1) Application declares its expandable in manifest file using <expandable /> or
+ * 2) The screen size is same as (320 x 480) * density.
+ * {@see compatibilityFlag}
+ */
+ private static final int EXPANDABLE = 2;
+
+ /**
+ * A flag mask to tell if the application is configured to be expandable. This differs
+ * from EXPANDABLE in that the application that is not expandable will be
+ * marked as expandable if it runs in (320x 480) * density screen size.
+ */
+ private static final int CONFIGURED_EXPANDABLE = 4;
+
+ private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE;
+
+ /**
+ * Application's scale.
+ */
+ public final float applicationScale;
+
+ /**
+ * Application's inverted scale.
+ */
+ public final float applicationInvertedScale;
+
+ /**
+ * The flags from ApplicationInfo.
+ */
+ public final int appFlags;
+
+ /**
+ * Window size in Compatibility Mode, in real pixels. This is updated by
+ * {@link DisplayMetrics#updateMetrics}.
+ */
+ private int mWidth;
+ private int mHeight;
+
+ /**
+ * The x offset to center the window content. In X_SHIFT_WINDOW mode, the offset is added
+ * to the window's layout. In X_SHIFT_CONTENT/X_SHIFT_AND_CLIP_CONTENT mode, the offset
+ * is used to translate the Canvas.
+ */
+ private int mXOffset;
+
+ public CompatibilityInfo(ApplicationInfo appInfo) {
+ appFlags = appInfo.flags;
+
+ if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+ mCompatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE;
+ }
+
+ float packageDensityScale = -1.0f;
+ if (appInfo.supportsDensities != null) {
+ int minDiff = Integer.MAX_VALUE;
+ for (int density : appInfo.supportsDensities) {
+ if (density == ApplicationInfo.ANY_DENSITY) {
+ packageDensityScale = 1.0f;
+ break;
+ }
+ int tmpDiff = Math.abs(DisplayMetrics.DEVICE_DENSITY - density);
+ if (tmpDiff == 0) {
+ packageDensityScale = 1.0f;
+ break;
+ }
+ // prefer higher density (appScale>1.0), unless that's only option.
+ if (tmpDiff < minDiff && packageDensityScale < 1.0f) {
+ packageDensityScale = DisplayMetrics.DEVICE_DENSITY / (float) density;
+ minDiff = tmpDiff;
+ }
+ }
+ }
+ if (packageDensityScale > 0.0f) {
+ applicationScale = packageDensityScale;
+ } else {
+ applicationScale =
+ DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY;
+ }
+ applicationInvertedScale = 1.0f / applicationScale;
+ if (applicationScale != 1.0f) {
+ mCompatibilityFlags |= SCALING_REQUIRED;
+ }
+ }
+
+ private CompatibilityInfo(int appFlags, int compFlags, float scale, float invertedScale) {
+ this.appFlags = appFlags;
+ mCompatibilityFlags = compFlags;
+ applicationScale = scale;
+ applicationInvertedScale = invertedScale;
+ }
+
+ private CompatibilityInfo() {
+ this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
+ | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS
+ | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS,
+ EXPANDABLE | CONFIGURED_EXPANDABLE,
+ 1.0f,
+ 1.0f);
+ }
+
+ /**
+ * Returns the copy of this instance.
+ */
+ public CompatibilityInfo copy() {
+ CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
+ applicationScale, applicationInvertedScale);
+ info.setVisibleRect(mXOffset, mWidth, mHeight);
+ return info;
+ }
+
+ /**
+ * Sets the application's visible rect in compatibility mode.
+ * @param xOffset the application's x offset that is added to center the content.
+ * @param widthPixels the application's width in real pixels on the screen.
+ * @param heightPixels the application's height in real pixels on the screen.
+ */
+ public void setVisibleRect(int xOffset, int widthPixels, int heightPixels) {
+ this.mXOffset = xOffset;
+ mWidth = widthPixels;
+ mHeight = heightPixels;
+ }
+
+ /**
+ * Sets expandable bit in the compatibility flag.
+ */
+ public void setExpandable(boolean expandable) {
+ if (expandable) {
+ mCompatibilityFlags |= CompatibilityInfo.EXPANDABLE;
+ } else {
+ mCompatibilityFlags &= ~CompatibilityInfo.EXPANDABLE;
+ }
+ }
+
+ /**
+ * @return true if the application is configured to be expandable.
+ */
+ public boolean isConfiguredExpandable() {
+ return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_EXPANDABLE) != 0;
+ }
+
+ /**
+ * @return true if the scaling is required
+ */
+ public boolean isScalingRequired() {
+ return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
+ }
+
+ @Override
+ public String toString() {
+ return "CompatibilityInfo{scale=" + applicationScale +
+ ", compatibility flag=" + mCompatibilityFlags + "}";
+ }
+
+ /**
+ * Returns the translator which can translate the coordinates of the window.
+ * There are five different types of Translator.
+ *
+ * 1) {@link CompatibilityInfo#X_SHIFT_AND_CLIP_CONTENT}
+ * Shift and clip the content of the window at drawing time. Used for activities'
+ * main window (with no gravity).
+ * 2) {@link CompatibilityInfo#X_SHIFT_CONTENT}
+ * Shift the content of the window at drawing time. Used for windows that is created by
+ * an application and expected to be aligned with the application window.
+ * 3) {@link CompatibilityInfo#X_SHIFT_WINDOW}
+ * Create the window with adjusted x- coordinates. This is typically used
+ * in popup window, where it has to be placed relative to main window.
+ * 4) {@link CompatibilityInfo#X_SHIFT_NONE}
+ * No adjustment required, such as dialog.
+ * 5) Same as X_SHIFT_WINDOW, but no scaling. This is used by {@link SurfaceView}, which
+ * does not require scaling, but its window's location has to be adjusted.
+ *
+ * @param params the window's parameter
+ */
+ public Translator getTranslator(WindowManager.LayoutParams params) {
+ if ( (mCompatibilityFlags & CompatibilityInfo.SCALING_EXPANDABLE_MASK)
+ == CompatibilityInfo.EXPANDABLE) {
+ if (DBG) Log.d(TAG, "no translation required");
+ return null;
+ }
+
+ if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) {
+ if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) {
+ if (DBG) Log.d(TAG, "translation for surface view selected");
+ return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f);
+ } else {
+ int shiftMode;
+ if (params.gravity == Gravity.NO_GRAVITY) {
+ // For Regular Application window
+ shiftMode = X_SHIFT_AND_CLIP_CONTENT;
+ if (DBG) Log.d(TAG, "shift and clip translator");
+ } else if (params.width == WindowManager.LayoutParams.FILL_PARENT) {
+ // For Regular Application window
+ shiftMode = X_SHIFT_CONTENT;
+ if (DBG) Log.d(TAG, "shift content translator");
+ } else if ((params.gravity & Gravity.LEFT) != 0 && params.x > 0) {
+ shiftMode = X_SHIFT_WINDOW;
+ if (DBG) Log.d(TAG, "shift window translator");
+ } else {
+ shiftMode = X_SHIFT_NONE;
+ if (DBG) Log.d(TAG, "no content/window translator");
+ }
+ return new Translator(shiftMode);
+ }
+ } else if (isScalingRequired()) {
+ return new Translator();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * A helper object to translate the screen and window coordinates back and forth.
+ * @hide
+ */
+ public class Translator {
+ final private int mShiftMode;
+ final public boolean scalingRequired;
+ final public float applicationScale;
+ final public float applicationInvertedScale;
+
+ private Rect mContentInsetsBuffer = null;
+ private Rect mVisibleInsets = null;
+
+ Translator(int shiftMode, boolean scalingRequired, float applicationScale,
+ float applicationInvertedScale) {
+ mShiftMode = shiftMode;
+ this.scalingRequired = scalingRequired;
+ this.applicationScale = applicationScale;
+ this.applicationInvertedScale = applicationInvertedScale;
+ }
+
+ Translator(int shiftMode) {
+ this(shiftMode,
+ isScalingRequired(),
+ CompatibilityInfo.this.applicationScale,
+ CompatibilityInfo.this.applicationInvertedScale);
+ }
+
+ Translator() {
+ this(X_SHIFT_NONE);
+ }
+
+ /**
+ * Translate the screen rect to the application frame.
+ */
+ public void translateRectInScreenToAppWinFrame(Rect rect) {
+ if (rect.isEmpty()) return; // skip if the window size is empty.
+ switch (mShiftMode) {
+ case X_SHIFT_AND_CLIP_CONTENT:
+ rect.intersect(0, 0, mWidth, mHeight);
+ break;
+ case X_SHIFT_CONTENT:
+ rect.intersect(0, 0, mWidth + mXOffset, mHeight);
+ break;
+ case X_SHIFT_WINDOW:
+ case X_SHIFT_NONE:
+ break;
+ }
+ if (scalingRequired) {
+ rect.scale(applicationInvertedScale);
+ }
+ }
+
+ /**
+ * Translate the region in window to screen.
+ */
+ public void translateRegionInWindowToScreen(Region transparentRegion) {
+ switch (mShiftMode) {
+ case X_SHIFT_AND_CLIP_CONTENT:
+ case X_SHIFT_CONTENT:
+ transparentRegion.scale(applicationScale);
+ transparentRegion.translate(mXOffset, 0);
+ break;
+ case X_SHIFT_WINDOW:
+ case X_SHIFT_NONE:
+ transparentRegion.scale(applicationScale);
+ }
+ }
+
+ /**
+ * Apply translation to the canvas that is necessary to draw the content.
+ */
+ public void translateCanvas(Canvas canvas) {
+ if (mShiftMode == X_SHIFT_CONTENT ||
+ mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
+ // TODO: clear outside when rotation is changed.
+
+ // Translate x-offset only when the content is shifted.
+ canvas.translate(mXOffset, 0);
+ }
+ if (scalingRequired) {
+ canvas.scale(applicationScale, applicationScale);
+ }
+ }
+
+ /**
+ * Translate the motion event captured on screen to the application's window.
+ */
+ public void translateEventInScreenToAppWindow(MotionEvent event) {
+ if (mShiftMode == X_SHIFT_CONTENT ||
+ mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
+ event.translate(-mXOffset, 0);
+ }
+ if (scalingRequired) {
+ event.scale(applicationInvertedScale);
+ }
+ }
+
+ /**
+ * Translate the window's layout parameter, from application's view to
+ * Screen's view.
+ */
+ public void translateWindowLayout(WindowManager.LayoutParams params) {
+ switch (mShiftMode) {
+ case X_SHIFT_NONE:
+ case X_SHIFT_AND_CLIP_CONTENT:
+ case X_SHIFT_CONTENT:
+ params.scale(applicationScale);
+ break;
+ case X_SHIFT_WINDOW:
+ params.scale(applicationScale);
+ params.x += mXOffset;
+ break;
+ }
+ }
+
+ /**
+ * Translate a Rect in application's window to screen.
+ */
+ public void translateRectInAppWindowToScreen(Rect rect) {
+ // TODO Auto-generated method stub
+ if (scalingRequired) {
+ rect.scale(applicationScale);
+ }
+ switch(mShiftMode) {
+ case X_SHIFT_NONE:
+ case X_SHIFT_WINDOW:
+ break;
+ case X_SHIFT_CONTENT:
+ case X_SHIFT_AND_CLIP_CONTENT:
+ rect.offset(mXOffset, 0);
+ break;
+ }
+ }
+
+ /**
+ * Translate a Rect in screen coordinates into the app window's coordinates.
+ */
+ public void translateRectInScreenToAppWindow(Rect rect) {
+ switch (mShiftMode) {
+ case X_SHIFT_NONE:
+ case X_SHIFT_WINDOW:
+ break;
+ case X_SHIFT_CONTENT: {
+ rect.intersects(mXOffset, 0, rect.right, rect.bottom);
+ int dx = Math.min(mXOffset, rect.left);
+ rect.offset(-dx, 0);
+ break;
+ }
+ case X_SHIFT_AND_CLIP_CONTENT: {
+ rect.intersects(mXOffset, 0, mWidth + mXOffset, mHeight);
+ int dx = Math.min(mXOffset, rect.left);
+ rect.offset(-dx, 0);
+ break;
+ }
+ }
+ if (scalingRequired) {
+ rect.scale(applicationInvertedScale);
+ }
+ }
+
+ /**
+ * Translate the location of the sub window.
+ * @param params
+ */
+ public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
+ if (scalingRequired) {
+ params.scale(applicationScale);
+ }
+ switch (mShiftMode) {
+ // the window location on these mode does not require adjustmenet.
+ case X_SHIFT_NONE:
+ case X_SHIFT_WINDOW:
+ break;
+ case X_SHIFT_CONTENT:
+ case X_SHIFT_AND_CLIP_CONTENT:
+ params.x += mXOffset;
+ break;
+ }
+ }
+
+ /**
+ * Translate the content insets in application window to Screen. This uses
+ * the internal buffer for content insets to avoid extra object allocation.
+ */
+ public Rect getTranslatedContentInsets(Rect contentInsets) {
+ if (mContentInsetsBuffer == null) mContentInsetsBuffer = new Rect();
+ mContentInsetsBuffer.set(contentInsets);
+ translateRectInAppWindowToScreen(mContentInsetsBuffer);
+ return mContentInsetsBuffer;
+ }
+
+ /**
+ * Translate the visible insets in application window to Screen. This uses
+ * the internal buffer for content insets to avoid extra object allocation.
+ */
+ public Rect getTranslatedVisbileInsets(Rect visibleInsets) {
+ if (mVisibleInsets == null) mVisibleInsets = new Rect();
+ mVisibleInsets.set(visibleInsets);
+ translateRectInAppWindowToScreen(mVisibleInsets);
+ return mVisibleInsets;
+ }
+ }
+}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index bb3486c..577aa60 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -116,6 +116,18 @@ public final class Configuration implements Parcelable, Comparable<Configuration
*/
public int orientation;
+ public static final int SCREENLAYOUT_UNDEFINED = 0;
+ public static final int SCREENLAYOUT_SMALL = 1;
+ public static final int SCREENLAYOUT_NORMAL = 2;
+ public static final int SCREENLAYOUT_LARGE = 3;
+
+ /**
+ * Overall layout of the screen. May be one of
+ * {@link #SCREENLAYOUT_SMALL}, {@link #SCREENLAYOUT_NORMAL},
+ * or {@link #SCREENLAYOUT_LARGE}.
+ */
+ public int screenLayout;
+
/**
* Construct an invalid Configuration. You must call {@link #setToDefaults}
* for this object to be valid. {@more}
@@ -141,6 +153,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
hardKeyboardHidden = o.hardKeyboardHidden;
navigation = o.navigation;
orientation = o.orientation;
+ screenLayout = o.screenLayout;
}
public String toString() {
@@ -165,6 +178,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
sb.append(navigation);
sb.append(" orien=");
sb.append(orientation);
+ sb.append(" layout=");
+ sb.append(screenLayout);
sb.append('}');
return sb.toString();
}
@@ -183,6 +198,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED;
navigation = NAVIGATION_UNDEFINED;
orientation = ORIENTATION_UNDEFINED;
+ screenLayout = SCREENLAYOUT_UNDEFINED;
}
/** {@hide} */
@@ -253,6 +269,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
changed |= ActivityInfo.CONFIG_ORIENTATION;
orientation = delta.orientation;
}
+ if (delta.screenLayout != SCREENLAYOUT_UNDEFINED
+ && screenLayout != delta.screenLayout) {
+ changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ screenLayout = delta.screenLayout;
+ }
return changed;
}
@@ -276,9 +297,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* {@link android.content.pm.ActivityInfo#CONFIG_KEYBOARD
* PackageManager.ActivityInfo.CONFIG_KEYBOARD},
* {@link android.content.pm.ActivityInfo#CONFIG_NAVIGATION
- * PackageManager.ActivityInfo.CONFIG_NAVIGATION}, or
+ * PackageManager.ActivityInfo.CONFIG_NAVIGATION},
* {@link android.content.pm.ActivityInfo#CONFIG_ORIENTATION
- * PackageManager.ActivityInfo.CONFIG_ORIENTATION}.
+ * PackageManager.ActivityInfo.CONFIG_ORIENTATION}, or
+ * {@link android.content.pm.ActivityInfo#CONFIG_SCREEN_LAYOUT
+ * PackageManager.ActivityInfo.CONFIG_SCREEN_LAYOUT}.
*/
public int diff(Configuration delta) {
int changed = 0;
@@ -319,6 +342,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& orientation != delta.orientation) {
changed |= ActivityInfo.CONFIG_ORIENTATION;
}
+ if (delta.screenLayout != SCREENLAYOUT_UNDEFINED
+ && screenLayout != delta.screenLayout) {
+ changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT;
+ }
return changed;
}
@@ -368,6 +395,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(hardKeyboardHidden);
dest.writeInt(navigation);
dest.writeInt(orientation);
+ dest.writeInt(screenLayout);
}
public static final Parcelable.Creator<Configuration> CREATOR
@@ -399,6 +427,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
hardKeyboardHidden = source.readInt();
navigation = source.readInt();
orientation = source.readInt();
+ screenLayout = source.readInt();
}
public int compareTo(Configuration that) {
@@ -428,6 +457,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
n = this.navigation - that.navigation;
if (n != 0) return n;
n = this.orientation - that.orientation;
+ if (n != 0) return n;
+ n = this.screenLayout - that.screenLayout;
//if (n != 0) return n;
return n;
}
@@ -450,6 +481,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration
return ((int)this.fontScale) + this.mcc + this.mnc
+ this.locale.hashCode() + this.touchscreen
+ this.keyboard + this.keyboardHidden + this.hardKeyboardHidden
- + this.navigation + this.orientation;
+ + this.navigation + this.orientation + this.screenLayout;
}
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 665e40c..49ad656 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -22,9 +22,9 @@ import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.content.pm.ApplicationInfo;
import android.graphics.Movie;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.SystemProperties;
@@ -33,6 +33,7 @@ import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
+import android.util.LongSparseArray;
import java.io.IOException;
import java.io.InputStream;
@@ -57,19 +58,19 @@ public class Resources {
// Information about preloaded resources. Note that they are not
// protected by a lock, because while preloading in zygote we are all
// single-threaded, and after that these are immutable.
- private static final SparseArray<Drawable.ConstantState> sPreloadedDrawables
- = new SparseArray<Drawable.ConstantState>();
+ private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables
+ = new LongSparseArray<Drawable.ConstantState>();
private static final SparseArray<ColorStateList> mPreloadedColorStateLists
= new SparseArray<ColorStateList>();
private static boolean mPreloaded;
- private final SparseArray<Drawable.ConstantState> mPreloadedDrawables;
+ private final LongSparseArray<Drawable.ConstantState> mPreloadedDrawables;
/*package*/ final TypedValue mTmpValue = new TypedValue();
// These are protected by the mTmpValue lock.
- private final SparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
- = new SparseArray<WeakReference<Drawable.ConstantState> >();
+ private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
+ = new LongSparseArray<WeakReference<Drawable.ConstantState> >();
private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache
= new SparseArray<WeakReference<ColorStateList> >();
private boolean mPreloading;
@@ -84,21 +85,23 @@ public class Resources {
private final Configuration mConfiguration = new Configuration();
/*package*/ final DisplayMetrics mMetrics = new DisplayMetrics();
PluralRules mPluralRule;
+
+ private final CompatibilityInfo mCompatibilityInfo;
- private static final SparseArray<Object> EMPTY_ARRAY = new SparseArray<Object>() {
+ private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>() {
@Override
- public void put(int k, Object o) {
+ public void put(long k, Object o) {
throw new UnsupportedOperationException();
}
@Override
- public void append(int k, Object o) {
+ public void append(long k, Object o) {
throw new UnsupportedOperationException();
}
};
@SuppressWarnings("unchecked")
- private static <T> SparseArray<T> emptySparseArray() {
- return (SparseArray<T>) EMPTY_ARRAY;
+ private static <T> LongSparseArray<T> emptySparseArray() {
+ return (LongSparseArray<T>) EMPTY_ARRAY;
}
/**
@@ -126,26 +129,59 @@ public class Resources {
*/
public Resources(AssetManager assets, DisplayMetrics metrics,
Configuration config) {
- this(assets, metrics, config, true);
+ this(assets, metrics, config, (ApplicationInfo) null);
}
/**
- * Create a resource with an additional flag for preloaded
- * drawable cache. Used by {@link ActivityThread}.
- *
+ * Creates a new Resources object with ApplicationInfo.
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
+ * selecting/computing resource values.
+ * @param config Desired device configuration to consider when
+ * selecting/computing resource values (optional).
+ * @param appInfo this resource's application info.
* @hide
*/
public Resources(AssetManager assets, DisplayMetrics metrics,
- Configuration config, boolean usePreloadedCache) {
+ Configuration config, ApplicationInfo appInfo) {
mAssets = assets;
mConfiguration.setToDefaults();
mMetrics.setToDefaults();
+ if (appInfo != null) {
+ mCompatibilityInfo = new CompatibilityInfo(appInfo);
+ if (DEBUG_CONFIG) {
+ Log.d(TAG, "compatibility for " + appInfo.packageName + " : " + mCompatibilityInfo);
+ }
+ } else {
+ mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ }
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
- if (usePreloadedCache) {
- mPreloadedDrawables = sPreloadedDrawables;
+ if (mCompatibilityInfo.isScalingRequired()) {
+ mPreloadedDrawables = emptySparseArray();
} else {
+ mPreloadedDrawables = sPreloadedDrawables;
+ }
+ }
+
+ /**
+ * Creates a new resources that uses the given compatibility info. Used to create
+ * a context for widgets using the container's compatibility info.
+ * {@see ApplicationContext#createPackageCotnext}.
+ * @hide
+ */
+ public Resources(AssetManager assets, DisplayMetrics metrics,
+ Configuration config, CompatibilityInfo info) {
+ mAssets = assets;
+ mMetrics.setToDefaults();
+ mCompatibilityInfo = info;
+ updateConfiguration(config, metrics);
+ assets.ensureStringBlocks();
+ if (mCompatibilityInfo.isScalingRequired()) {
mPreloadedDrawables = emptySparseArray();
+ } else {
+ mPreloadedDrawables = sPreloadedDrawables;
}
}
@@ -1238,7 +1274,7 @@ public class Resources {
return array;
}
-
+
/**
* Store the newly updated configuration.
*/
@@ -1251,6 +1287,8 @@ public class Resources {
}
if (metrics != null) {
mMetrics.setTo(metrics);
+ mMetrics.updateMetrics(mCompatibilityInfo,
+ mConfiguration.orientation, mConfiguration.screenLayout);
}
mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
@@ -1282,7 +1320,7 @@ public class Resources {
mConfiguration.touchscreen,
(int)(mMetrics.density*160), mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
- sSdkVersion);
+ mConfiguration.screenLayout, sSdkVersion);
int N = mDrawableCache.size();
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
@@ -1297,14 +1335,14 @@ public class Resources {
configChanges, cs.getChangingConfigurations())) {
if (DEBUG_CONFIG) {
Log.d(TAG, "FLUSHING #0x"
- + Integer.toHexString(mDrawableCache.keyAt(i))
+ + Long.toHexString(mDrawableCache.keyAt(i))
+ " / " + cs + " with changes: 0x"
+ Integer.toHexString(cs.getChangingConfigurations()));
}
mDrawableCache.setValueAt(i, null);
} else if (DEBUG_CONFIG) {
Log.d(TAG, "(Keeping #0x"
- + Integer.toHexString(mDrawableCache.keyAt(i))
+ + Long.toHexString(mDrawableCache.keyAt(i))
+ " / " + cs + " with changes: 0x"
+ Integer.toHexString(cs.getChangingConfigurations())
+ ")");
@@ -1356,6 +1394,17 @@ public class Resources {
public Configuration getConfiguration() {
return mConfiguration;
}
+
+ /**
+ * Return the compatibility mode information for the application.
+ * The returned object should be treated as read-only.
+ *
+ * @return compatibility info. null if the app does not require compatibility mode.
+ * @hide
+ */
+ public CompatibilityInfo getCompatibilityInfo() {
+ return mCompatibilityInfo;
+ }
/**
* Return a resource identifier for the given resource name. A fully
@@ -1624,7 +1673,7 @@ public class Resources {
}
}
- final int key = (value.assetCookie << 24) | value.data;
+ final long key = (((long) value.assetCookie) << 32) | value.data;
Drawable dr = getCachedDrawable(key);
if (dr != null) {
@@ -1704,7 +1753,7 @@ public class Resources {
return dr;
}
- private Drawable getCachedDrawable(int key) {
+ private Drawable getCachedDrawable(long key) {
synchronized (mTmpValue) {
WeakReference<Drawable.ConstantState> wr = mDrawableCache.get(key);
if (wr != null) { // we have the key
@@ -1920,5 +1969,6 @@ public class Resources {
updateConfiguration(null, null);
mAssets.ensureStringBlocks();
mPreloadedDrawables = sPreloadedDrawables;
+ mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
}
}
diff --git a/core/java/android/database/BulkCursorToCursorAdaptor.java b/core/java/android/database/BulkCursorToCursorAdaptor.java
index c26810a..cf30dd9 100644
--- a/core/java/android/database/BulkCursorToCursorAdaptor.java
+++ b/core/java/android/database/BulkCursorToCursorAdaptor.java
@@ -247,9 +247,11 @@ public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
try {
return mBulkCursor.respond(extras);
} catch (RemoteException e) {
- // This should never happen because the system kills processes that are using remote
- // cursors when the provider process is killed.
- throw new RuntimeException(e);
+ // the system kills processes that are using remote cursors when the provider process
+ // is killed, but this can still happen if this is being called from the system process,
+ // so, better to log and return an empty bundle.
+ Log.w(TAG, "respond() threw RemoteException, returning an empty bundle.", e);
+ return Bundle.EMPTY;
}
}
}
diff --git a/core/java/android/database/sqlite/SQLiteContentHelper.java b/core/java/android/database/sqlite/SQLiteContentHelper.java
new file mode 100644
index 0000000..2800d86
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteContentHelper.java
@@ -0,0 +1,92 @@
+/*
+ * 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.database.sqlite;
+
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.os.MemoryFile;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Some helper functions for using SQLite database to implement content providers.
+ *
+ * @hide
+ */
+public class SQLiteContentHelper {
+
+ /**
+ * Runs an SQLite query and returns an AssetFileDescriptor for the
+ * blob in column 0 of the first row. If the first column does
+ * not contain a blob, an unspecified exception is thrown.
+ *
+ * @param db Handle to a readable database.
+ * @param sql SQL query, possibly with query arguments.
+ * @param selectionArgs Query argument values, or {@code null} for no argument.
+ * @return If no exception is thrown, a non-null AssetFileDescriptor is returned.
+ * @throws FileNotFoundException If the query returns no results or the
+ * value of column 0 is NULL, or if there is an error creating the
+ * asset file descriptor.
+ */
+ public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql,
+ String[] selectionArgs) throws FileNotFoundException {
+ try {
+ MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs);
+ if (file == null) {
+ throw new FileNotFoundException("No results.");
+ }
+ return AssetFileDescriptor.fromMemoryFile(file);
+ } catch (IOException ex) {
+ throw new FileNotFoundException(ex.toString());
+ }
+ }
+
+ /**
+ * Runs an SQLite query and returns a MemoryFile for the
+ * blob in column 0 of the first row. If the first column does
+ * not contain a blob, an unspecified exception is thrown.
+ *
+ * @return A memory file, or {@code null} if the query returns no results
+ * or the value column 0 is NULL.
+ * @throws IOException If there is an error creating the memory file.
+ */
+ // TODO: make this native and use the SQLite blob API to reduce copying
+ private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql,
+ String[] selectionArgs) throws IOException {
+ Cursor cursor = db.rawQuery(sql, selectionArgs);
+ if (cursor == null) {
+ return null;
+ }
+ try {
+ if (!cursor.moveToFirst()) {
+ return null;
+ }
+ byte[] bytes = cursor.getBlob(0);
+ if (bytes == null) {
+ return null;
+ }
+ MemoryFile file = new MemoryFile(null, bytes.length);
+ file.writeBytes(bytes, 0, 0, bytes.length);
+ file.deactivate();
+ return file;
+ } finally {
+ cursor.close();
+ }
+ }
+
+}
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index ab7c827..8a63919 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -18,16 +18,15 @@ package android.database.sqlite;
import android.database.Cursor;
import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
import android.provider.BaseColumns;
import android.text.TextUtils;
-import android.util.Config;
import android.util.Log;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
+import java.util.regex.Pattern;
/**
* This is a convience class that helps build SQL queries to be sent to
@@ -36,10 +35,12 @@ import java.util.Map.Entry;
public class SQLiteQueryBuilder
{
private static final String TAG = "SQLiteQueryBuilder";
+ private static final Pattern sLimitPattern =
+ Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
private Map<String, String> mProjectionMap = null;
private String mTables = "";
- private StringBuilder mWhereClause = new StringBuilder(64);
+ private final StringBuilder mWhereClause = new StringBuilder(64);
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
@@ -169,6 +170,9 @@ public class SQLiteQueryBuilder
throw new IllegalArgumentException(
"HAVING clauses are only permitted when using a groupBy clause");
}
+ if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
+ throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
+ }
StringBuilder query = new StringBuilder(120);
@@ -187,7 +191,7 @@ public class SQLiteQueryBuilder
appendClause(query, " GROUP BY ", groupBy);
appendClause(query, " HAVING ", having);
appendClause(query, " ORDER BY ", orderBy);
- appendClauseEscapeClause(query, " LIMIT ", limit);
+ appendClause(query, " LIMIT ", limit);
return query.toString();
}
diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java
new file mode 100755
index 0000000..2262477
--- /dev/null
+++ b/core/java/android/gesture/Gesture.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+
+/**
+ * A gesture can have a single or multiple strokes
+ */
+
+public class Gesture implements Parcelable {
+ private static final long GESTURE_ID_BASE = System.currentTimeMillis();
+
+ private static final int BITMAP_RENDERING_WIDTH = 2;
+
+ private static final boolean BITMAP_RENDERING_ANTIALIAS = true;
+ private static final boolean BITMAP_RENDERING_DITHER = true;
+
+ private static int sGestureCount = 0;
+
+ private final RectF mBoundingBox = new RectF();
+
+ // the same as its instance ID
+ private long mGestureID;
+
+ private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>();
+
+ public Gesture() {
+ mGestureID = GESTURE_ID_BASE + sGestureCount++;
+ }
+
+ void recycle() {
+ mStrokes.clear();
+ mBoundingBox.setEmpty();
+ }
+
+ /**
+ * @return all the strokes of the gesture
+ */
+ public ArrayList<GestureStroke> getStrokes() {
+ return mStrokes;
+ }
+
+ /**
+ * @return the number of strokes included by this gesture
+ */
+ public int getStrokesCount() {
+ return mStrokes.size();
+ }
+
+ /**
+ * Add a stroke to the gesture
+ *
+ * @param stroke
+ */
+ public void addStroke(GestureStroke stroke) {
+ mStrokes.add(stroke);
+ mBoundingBox.union(stroke.boundingBox);
+ }
+
+ /**
+ * Get the total length of the gesture. When there are multiple strokes in
+ * the gesture, this returns the sum of the lengths of all the strokes
+ *
+ * @return the length of the gesture
+ */
+ public float getLength() {
+ int len = 0;
+ final ArrayList<GestureStroke> strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ len += strokes.get(i).length;
+ }
+
+ return len;
+ }
+
+ /**
+ * @return the bounding box of the gesture
+ */
+ public RectF getBoundingBox() {
+ return mBoundingBox;
+ }
+
+ public Path toPath() {
+ return toPath(null);
+ }
+
+ public Path toPath(Path path) {
+ if (path == null) path = new Path();
+
+ final ArrayList<GestureStroke> strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ path.addPath(strokes.get(i).getPath());
+ }
+
+ return path;
+ }
+
+ public Path toPath(int width, int height, int edge, int numSample) {
+ return toPath(null, width, height, edge, numSample);
+ }
+
+ public Path toPath(Path path, int width, int height, int edge, int numSample) {
+ if (path == null) path = new Path();
+
+ final ArrayList<GestureStroke> strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ path.addPath(strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample));
+ }
+
+ return path;
+ }
+
+ /**
+ * Set the id of the gesture
+ *
+ * @param id
+ */
+ void setID(long id) {
+ mGestureID = id;
+ }
+
+ /**
+ * @return the id of the gesture
+ */
+ public long getID() {
+ return mGestureID;
+ }
+
+ /**
+ * draw the gesture
+ *
+ * @param canvas
+ */
+ void draw(Canvas canvas, Paint paint) {
+ final ArrayList<GestureStroke> strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ strokes.get(i).draw(canvas, paint);
+ }
+ }
+
+ /**
+ * Create a bitmap of the gesture with a transparent background
+ *
+ * @param width width of the target bitmap
+ * @param height height of the target bitmap
+ * @param edge the edge
+ * @param numSample
+ * @param color
+ * @return the bitmap
+ */
+ public Bitmap toBitmap(int width, int height, int edge, int numSample, int color) {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ canvas.translate(edge, edge);
+
+ final Paint paint = new Paint();
+ paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
+ paint.setDither(BITMAP_RENDERING_DITHER);
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
+
+ final ArrayList<GestureStroke> strokes = mStrokes;
+ final int count = strokes.size();
+
+ for (int i = 0; i < count; i++) {
+ Path path = strokes.get(i).toPath(width - 2 * edge, height - 2 * edge, numSample);
+ canvas.drawPath(path, paint);
+ }
+
+ return bitmap;
+ }
+
+ /**
+ * Create a bitmap of the gesture with a transparent background
+ *
+ * @param width
+ * @param height
+ * @param inset
+ * @param color
+ * @return the bitmap
+ */
+ public Bitmap toBitmap(int width, int height, int inset, int color) {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height,
+ Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+
+ final Paint paint = new Paint();
+ paint.setAntiAlias(BITMAP_RENDERING_ANTIALIAS);
+ paint.setDither(BITMAP_RENDERING_DITHER);
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.STROKE);
+ paint.setStrokeJoin(Paint.Join.ROUND);
+ paint.setStrokeCap(Paint.Cap.ROUND);
+ paint.setStrokeWidth(BITMAP_RENDERING_WIDTH);
+
+ final Path path = toPath();
+ final RectF bounds = new RectF();
+ path.computeBounds(bounds, true);
+
+ final float sx = (width - 2 * inset) / bounds.width();
+ final float sy = (height - 2 * inset) / bounds.height();
+ final float scale = sx > sy ? sy : sx;
+ paint.setStrokeWidth(2.0f / scale);
+
+ path.offset(-bounds.left + (width - bounds.width() * scale) / 2.0f,
+ -bounds.top + (height - bounds.height() * scale) / 2.0f);
+
+ canvas.translate(inset, inset);
+ canvas.scale(scale, scale);
+
+ canvas.drawPath(path, paint);
+
+ return bitmap;
+ }
+
+ void serialize(DataOutputStream out) throws IOException {
+ final ArrayList<GestureStroke> strokes = mStrokes;
+ final int count = strokes.size();
+
+ // Write gesture ID
+ out.writeLong(mGestureID);
+ // Write number of strokes
+ out.writeInt(count);
+
+ for (int i = 0; i < count; i++) {
+ strokes.get(i).serialize(out);
+ }
+ }
+
+ static Gesture deserialize(DataInputStream in) throws IOException {
+ final Gesture gesture = new Gesture();
+
+ // Gesture ID
+ gesture.mGestureID = in.readLong();
+ // Number of strokes
+ final int count = in.readInt();
+
+ for (int i = 0; i < count; i++) {
+ gesture.addStroke(GestureStroke.deserialize(in));
+ }
+
+ return gesture;
+ }
+
+ public static final Parcelable.Creator<Gesture> CREATOR = new Parcelable.Creator<Gesture>() {
+ public Gesture createFromParcel(Parcel in) {
+ Gesture gesture = null;
+ final long gestureID = in.readLong();
+
+ final DataInputStream inStream = new DataInputStream(
+ new ByteArrayInputStream(in.createByteArray()));
+
+ try {
+ gesture = deserialize(inStream);
+ } catch (IOException e) {
+ Log.e(GestureConstants.LOG_TAG, "Error reading Gesture from parcel:", e);
+ } finally {
+ GestureUtilities.closeStream(inStream);
+ }
+
+ if (gesture != null) {
+ gesture.mGestureID = gestureID;
+ }
+
+ return gesture;
+ }
+
+ public Gesture[] newArray(int size) {
+ return new Gesture[size];
+ }
+ };
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mGestureID);
+
+ boolean result = false;
+ final ByteArrayOutputStream byteStream =
+ new ByteArrayOutputStream(GestureConstants.IO_BUFFER_SIZE);
+ final DataOutputStream outStream = new DataOutputStream(byteStream);
+
+ try {
+ serialize(outStream);
+ result = true;
+ } catch (IOException e) {
+ Log.e(GestureConstants.LOG_TAG, "Error writing Gesture to parcel:", e);
+ } finally {
+ GestureUtilities.closeStream(outStream);
+ GestureUtilities.closeStream(byteStream);
+ }
+
+ if (result) {
+ out.writeByteArray(byteStream.toByteArray());
+ }
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+}
+
diff --git a/core/java/android/gesture/GestureConstants.java b/core/java/android/gesture/GestureConstants.java
new file mode 100644
index 0000000..230db0c
--- /dev/null
+++ b/core/java/android/gesture/GestureConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gesture;
+
+interface GestureConstants {
+ static final int STROKE_STRING_BUFFER_SIZE = 1024;
+ static final int STROKE_POINT_BUFFER_SIZE = 100; // number of points
+
+ static final int IO_BUFFER_SIZE = 32 * 1024; // 32K
+
+ static final String LOG_TAG = "Gestures";
+}
diff --git a/core/java/android/gesture/GestureLibraries.java b/core/java/android/gesture/GestureLibraries.java
new file mode 100644
index 0000000..6d6c156
--- /dev/null
+++ b/core/java/android/gesture/GestureLibraries.java
@@ -0,0 +1,143 @@
+/*
+ * 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.gesture;
+
+import android.util.Log;
+import static android.gesture.GestureConstants.*;
+import android.content.Context;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.lang.ref.WeakReference;
+
+public final class GestureLibraries {
+ private GestureLibraries() {
+ }
+
+ public static GestureLibrary fromFile(String path) {
+ return fromFile(new File(path));
+ }
+
+ public static GestureLibrary fromFile(File path) {
+ return new FileGestureLibrary(path);
+ }
+
+ public static GestureLibrary fromPrivateFile(Context context, String name) {
+ return fromFile(context.getFileStreamPath(name));
+ }
+
+ public static GestureLibrary fromRawResource(Context context, int resourceId) {
+ return new ResourceGestureLibrary(context, resourceId);
+ }
+
+ private static class FileGestureLibrary extends GestureLibrary {
+ private final File mPath;
+
+ public FileGestureLibrary(File path) {
+ mPath = path;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return !mPath.canWrite();
+ }
+
+ public boolean save() {
+ if (!mStore.hasChanged()) return true;
+
+ final File file = mPath;
+
+ final File parentFile = file.getParentFile();
+ if (!parentFile.exists()) {
+ if (!parentFile.mkdirs()) {
+ return false;
+ }
+ }
+
+ boolean result = false;
+ try {
+ //noinspection ResultOfMethodCallIgnored
+ file.createNewFile();
+ mStore.save(new FileOutputStream(file), true);
+ result = true;
+ } catch (FileNotFoundException e) {
+ Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not save the gesture library in " + mPath, e);
+ }
+
+ return result;
+ }
+
+ public boolean load() {
+ boolean result = false;
+ final File file = mPath;
+ if (file.exists() && file.canRead()) {
+ try {
+ mStore.load(new FileInputStream(file), true);
+ result = true;
+ } catch (FileNotFoundException e) {
+ Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not load the gesture library from " + mPath, e);
+ }
+ }
+
+ return result;
+ }
+ }
+
+ private static class ResourceGestureLibrary extends GestureLibrary {
+ private final WeakReference<Context> mContext;
+ private final int mResourceId;
+
+ public ResourceGestureLibrary(Context context, int resourceId) {
+ mContext = new WeakReference<Context>(context);
+ mResourceId = resourceId;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ public boolean save() {
+ return false;
+ }
+
+ public boolean load() {
+ boolean result = false;
+ final Context context = mContext.get();
+ if (context != null) {
+ final InputStream in = context.getResources().openRawResource(mResourceId);
+ try {
+ mStore.load(in, true);
+ result = true;
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Could not load the gesture library from raw resource " +
+ context.getResources().getResourceName(mResourceId), e);
+ }
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/core/java/android/gesture/GestureLibrary.java b/core/java/android/gesture/GestureLibrary.java
new file mode 100644
index 0000000..a29c2c8
--- /dev/null
+++ b/core/java/android/gesture/GestureLibrary.java
@@ -0,0 +1,81 @@
+/*
+ * 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.gesture;
+
+import java.util.Set;
+import java.util.ArrayList;
+
+public abstract class GestureLibrary {
+ protected final GestureStore mStore;
+
+ protected GestureLibrary() {
+ mStore = new GestureStore();
+ }
+
+ public abstract boolean save();
+
+ public abstract boolean load();
+
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ public Learner getLearner() {
+ return mStore.getLearner();
+ }
+
+ public void setOrientationStyle(int style) {
+ mStore.setOrientationStyle(style);
+ }
+
+ public int getOrientationStyle() {
+ return mStore.getOrientationStyle();
+ }
+
+ public void setSequenceType(int type) {
+ mStore.setSequenceType(type);
+ }
+
+ public int getSequenceType() {
+ return mStore.getSequenceType();
+ }
+
+ public Set<String> getGestureEntries() {
+ return mStore.getGestureEntries();
+ }
+
+ public ArrayList<Prediction> recognize(Gesture gesture) {
+ return mStore.recognize(gesture);
+ }
+
+ public void addGesture(String entryName, Gesture gesture) {
+ mStore.addGesture(entryName, gesture);
+ }
+
+ public void removeGesture(String entryName, Gesture gesture) {
+ mStore.removeGesture(entryName, gesture);
+ }
+
+ public void removeEntry(String entryName) {
+ mStore.removeEntry(entryName);
+ }
+
+ public ArrayList<Gesture> getGestures(String entryName) {
+ return mStore.getGestures(entryName);
+ }
+}
diff --git a/core/java/android/gesture/GestureOverlayView.java b/core/java/android/gesture/GestureOverlayView.java
new file mode 100755
index 0000000..5bfdcc4
--- /dev/null
+++ b/core/java/android/gesture/GestureOverlayView.java
@@ -0,0 +1,793 @@
+/*
+ * 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.gesture;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.animation.AnimationUtils;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.os.SystemClock;
+import com.android.internal.R;
+
+import java.util.ArrayList;
+
+/**
+ * A transparent overlay for gesture input that can be placed on top of other
+ * widgets or contain other widgets.
+ *
+ * @attr ref android.R.styleable#GestureOverlayView_eventsInterceptionEnabled
+ * @attr ref android.R.styleable#GestureOverlayView_fadeDuration
+ * @attr ref android.R.styleable#GestureOverlayView_fadeOffset
+ * @attr ref android.R.styleable#GestureOverlayView_fadeEnabled
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeWidth
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeAngleThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeLengthThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeSquarenessThreshold
+ * @attr ref android.R.styleable#GestureOverlayView_gestureStrokeType
+ * @attr ref android.R.styleable#GestureOverlayView_gestureColor
+ * @attr ref android.R.styleable#GestureOverlayView_orientation
+ * @attr ref android.R.styleable#GestureOverlayView_uncertainGestureColor
+ */
+public class GestureOverlayView extends FrameLayout {
+ public static final int GESTURE_STROKE_TYPE_SINGLE = 0;
+ public static final int GESTURE_STROKE_TYPE_MULTIPLE = 1;
+
+ public static final int ORIENTATION_HORIZONTAL = 0;
+ public static final int ORIENTATION_VERTICAL = 1;
+
+ private static final int FADE_ANIMATION_RATE = 16;
+ private static final boolean GESTURE_RENDERING_ANTIALIAS = true;
+ private static final boolean DITHER_FLAG = true;
+
+ private final Paint mGesturePaint = new Paint();
+
+ private long mFadeDuration = 150;
+ private long mFadeOffset = 420;
+ private long mFadingStart;
+ private boolean mFadingHasStarted;
+ private boolean mFadeEnabled = true;
+
+ private int mCurrentColor;
+ private int mCertainGestureColor = 0xFFFFFF00;
+ private int mUncertainGestureColor = 0x48FFFF00;
+ private float mGestureStrokeWidth = 12.0f;
+ private int mInvalidateExtraBorder = 10;
+
+ private int mGestureStrokeType = GESTURE_STROKE_TYPE_SINGLE;
+ private float mGestureStrokeLengthThreshold = 50.0f;
+ private float mGestureStrokeSquarenessTreshold = 0.275f;
+ private float mGestureStrokeAngleThreshold = 40.0f;
+
+ private int mOrientation = ORIENTATION_VERTICAL;
+
+ private final Rect mInvalidRect = new Rect();
+ private final Path mPath = new Path();
+ private boolean mGestureVisible = true;
+
+ private float mX;
+ private float mY;
+
+ private float mCurveEndX;
+ private float mCurveEndY;
+
+ private float mTotalLength;
+ private boolean mIsGesturing = false;
+ private boolean mPreviousWasGesturing = false;
+ private boolean mInterceptEvents = true;
+ private boolean mIsListeningForGestures;
+ private boolean mResetGesture;
+
+ // current gesture
+ private Gesture mCurrentGesture;
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList<OnGestureListener> mOnGestureListeners =
+ new ArrayList<OnGestureListener>();
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList<OnGesturePerformedListener> mOnGesturePerformedListeners =
+ new ArrayList<OnGesturePerformedListener>();
+ // TODO: Make this a list of WeakReferences
+ private final ArrayList<OnGesturingListener> mOnGesturingListeners =
+ new ArrayList<OnGesturingListener>();
+
+ private boolean mHandleGestureActions;
+
+ // fading out effect
+ private boolean mIsFadingOut = false;
+ private float mFadingAlpha = 1.0f;
+ private final AccelerateDecelerateInterpolator mInterpolator =
+ new AccelerateDecelerateInterpolator();
+
+ private final FadeOutRunnable mFadingOut = new FadeOutRunnable();
+
+ public GestureOverlayView(Context context) {
+ super(context);
+ init();
+ }
+
+ public GestureOverlayView(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.gestureOverlayViewStyle);
+ }
+
+ public GestureOverlayView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.GestureOverlayView, defStyle, 0);
+
+ mGestureStrokeWidth = a.getFloat(R.styleable.GestureOverlayView_gestureStrokeWidth,
+ mGestureStrokeWidth);
+ mInvalidateExtraBorder = Math.max(1, ((int) mGestureStrokeWidth) - 1);
+ mCertainGestureColor = a.getColor(R.styleable.GestureOverlayView_gestureColor,
+ mCertainGestureColor);
+ mUncertainGestureColor = a.getColor(R.styleable.GestureOverlayView_uncertainGestureColor,
+ mUncertainGestureColor);
+ mFadeDuration = a.getInt(R.styleable.GestureOverlayView_fadeDuration, (int) mFadeDuration);
+ mFadeOffset = a.getInt(R.styleable.GestureOverlayView_fadeOffset, (int) mFadeOffset);
+ mGestureStrokeType = a.getInt(R.styleable.GestureOverlayView_gestureStrokeType,
+ mGestureStrokeType);
+ mGestureStrokeLengthThreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeLengthThreshold,
+ mGestureStrokeLengthThreshold);
+ mGestureStrokeAngleThreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeAngleThreshold,
+ mGestureStrokeAngleThreshold);
+ mGestureStrokeSquarenessTreshold = a.getFloat(
+ R.styleable.GestureOverlayView_gestureStrokeSquarenessThreshold,
+ mGestureStrokeSquarenessTreshold);
+ mInterceptEvents = a.getBoolean(R.styleable.GestureOverlayView_eventsInterceptionEnabled,
+ mInterceptEvents);
+ mFadeEnabled = a.getBoolean(R.styleable.GestureOverlayView_fadeEnabled,
+ mFadeEnabled);
+ mOrientation = a.getInt(R.styleable.GestureOverlayView_orientation, mOrientation);
+
+ a.recycle();
+
+ init();
+ }
+
+ private void init() {
+ setWillNotDraw(false);
+
+ final Paint gesturePaint = mGesturePaint;
+ gesturePaint.setAntiAlias(GESTURE_RENDERING_ANTIALIAS);
+ gesturePaint.setColor(mCertainGestureColor);
+ gesturePaint.setStyle(Paint.Style.STROKE);
+ gesturePaint.setStrokeJoin(Paint.Join.ROUND);
+ gesturePaint.setStrokeCap(Paint.Cap.ROUND);
+ gesturePaint.setStrokeWidth(mGestureStrokeWidth);
+ gesturePaint.setDither(DITHER_FLAG);
+
+ mCurrentColor = mCertainGestureColor;
+ setPaintAlpha(255);
+ }
+
+ public ArrayList<GesturePoint> getCurrentStroke() {
+ return mStrokeBuffer;
+ }
+
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ public void setGestureColor(int color) {
+ mCertainGestureColor = color;
+ }
+
+ public void setUncertainGestureColor(int color) {
+ mUncertainGestureColor = color;
+ }
+
+ public int getUncertainGestureColor() {
+ return mUncertainGestureColor;
+ }
+
+ public int getGestureColor() {
+ return mCertainGestureColor;
+ }
+
+ public float getGestureStrokeWidth() {
+ return mGestureStrokeWidth;
+ }
+
+ public void setGestureStrokeWidth(float gestureStrokeWidth) {
+ mGestureStrokeWidth = gestureStrokeWidth;
+ mInvalidateExtraBorder = Math.max(1, ((int) gestureStrokeWidth) - 1);
+ mGesturePaint.setStrokeWidth(gestureStrokeWidth);
+ }
+
+ public int getGestureStrokeType() {
+ return mGestureStrokeType;
+ }
+
+ public void setGestureStrokeType(int gestureStrokeType) {
+ mGestureStrokeType = gestureStrokeType;
+ }
+
+ public float getGestureStrokeLengthThreshold() {
+ return mGestureStrokeLengthThreshold;
+ }
+
+ public void setGestureStrokeLengthThreshold(float gestureStrokeLengthThreshold) {
+ mGestureStrokeLengthThreshold = gestureStrokeLengthThreshold;
+ }
+
+ public float getGestureStrokeSquarenessTreshold() {
+ return mGestureStrokeSquarenessTreshold;
+ }
+
+ public void setGestureStrokeSquarenessTreshold(float gestureStrokeSquarenessTreshold) {
+ mGestureStrokeSquarenessTreshold = gestureStrokeSquarenessTreshold;
+ }
+
+ public float getGestureStrokeAngleThreshold() {
+ return mGestureStrokeAngleThreshold;
+ }
+
+ public void setGestureStrokeAngleThreshold(float gestureStrokeAngleThreshold) {
+ mGestureStrokeAngleThreshold = gestureStrokeAngleThreshold;
+ }
+
+ public boolean isEventsInterceptionEnabled() {
+ return mInterceptEvents;
+ }
+
+ public void setEventsInterceptionEnabled(boolean enabled) {
+ mInterceptEvents = enabled;
+ }
+
+ public boolean isFadeEnabled() {
+ return mFadeEnabled;
+ }
+
+ public void setFadeEnabled(boolean fadeEnabled) {
+ mFadeEnabled = fadeEnabled;
+ }
+
+ public Gesture getGesture() {
+ return mCurrentGesture;
+ }
+
+ public void setGesture(Gesture gesture) {
+ if (mCurrentGesture != null) {
+ clear(false);
+ }
+
+ setCurrentColor(mCertainGestureColor);
+ mCurrentGesture = gesture;
+
+ final Path path = mCurrentGesture.toPath();
+ final RectF bounds = new RectF();
+ path.computeBounds(bounds, true);
+
+ // TODO: The path should also be scaled to fit inside this view
+ mPath.rewind();
+ mPath.addPath(path, -bounds.left + (getWidth() - bounds.width()) / 2.0f,
+ -bounds.top + (getHeight() - bounds.height()) / 2.0f);
+
+ mResetGesture = true;
+
+ invalidate();
+ }
+
+ public Path getGesturePath() {
+ return mPath;
+ }
+
+ public Path getGesturePath(Path path) {
+ path.set(mPath);
+ return path;
+ }
+
+ public boolean isGestureVisible() {
+ return mGestureVisible;
+ }
+
+ public void setGestureVisible(boolean visible) {
+ mGestureVisible = visible;
+ }
+
+ public long getFadeOffset() {
+ return mFadeOffset;
+ }
+
+ public void setFadeOffset(long fadeOffset) {
+ mFadeOffset = fadeOffset;
+ }
+
+ public void addOnGestureListener(OnGestureListener listener) {
+ mOnGestureListeners.add(listener);
+ }
+
+ public void removeOnGestureListener(OnGestureListener listener) {
+ mOnGestureListeners.remove(listener);
+ }
+
+ public void removeAllOnGestureListeners() {
+ mOnGestureListeners.clear();
+ }
+
+ public void addOnGesturePerformedListener(OnGesturePerformedListener listener) {
+ mOnGesturePerformedListeners.add(listener);
+ if (mOnGesturePerformedListeners.size() > 0) {
+ mHandleGestureActions = true;
+ }
+ }
+
+ public void removeOnGesturePerformedListener(OnGesturePerformedListener listener) {
+ mOnGesturePerformedListeners.remove(listener);
+ if (mOnGesturePerformedListeners.size() <= 0) {
+ mHandleGestureActions = false;
+ }
+ }
+
+ public void removeAllOnGesturePerformedListeners() {
+ mOnGesturePerformedListeners.clear();
+ mHandleGestureActions = false;
+ }
+
+ public void addOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.add(listener);
+ }
+
+ public void removeOnGesturingListener(OnGesturingListener listener) {
+ mOnGesturingListeners.remove(listener);
+ }
+
+ public void removeAllOnGesturingListeners() {
+ mOnGesturingListeners.clear();
+ }
+
+ public boolean isGesturing() {
+ return mIsGesturing;
+ }
+
+ private void setCurrentColor(int color) {
+ mCurrentColor = color;
+ if (mFadingHasStarted) {
+ setPaintAlpha((int) (255 * mFadingAlpha));
+ } else {
+ setPaintAlpha(255);
+ }
+ invalidate();
+ }
+
+ /**
+ * @hide
+ */
+ public Paint getGesturePaint() {
+ return mGesturePaint;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mCurrentGesture != null && mGestureVisible) {
+ canvas.drawPath(mPath, mGesturePaint);
+ }
+ }
+
+ private void setPaintAlpha(int alpha) {
+ alpha += alpha >> 7;
+ final int baseAlpha = mCurrentColor >>> 24;
+ final int useAlpha = baseAlpha * alpha >> 8;
+ mGesturePaint.setColor((mCurrentColor << 8 >>> 8) | (useAlpha << 24));
+ }
+
+ public void clear(boolean animated) {
+ clear(animated, false, true);
+ }
+
+ private void clear(boolean animated, boolean fireActionPerformed, boolean immediate) {
+ setPaintAlpha(255);
+ removeCallbacks(mFadingOut);
+ mResetGesture = false;
+ mFadingOut.fireActionPerformed = fireActionPerformed;
+ mFadingOut.resetMultipleStrokes = false;
+
+ if (animated && mCurrentGesture != null) {
+ mFadingAlpha = 1.0f;
+ mIsFadingOut = true;
+ mFadingHasStarted = false;
+ mFadingStart = AnimationUtils.currentAnimationTimeMillis() + mFadeOffset;
+
+ postDelayed(mFadingOut, mFadeOffset);
+ } else {
+ mFadingAlpha = 1.0f;
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+
+ if (immediate) {
+ mCurrentGesture = null;
+ mPath.rewind();
+ invalidate();
+ } else if (fireActionPerformed) {
+ postDelayed(mFadingOut, mFadeOffset);
+ } else if (mGestureStrokeType == GESTURE_STROKE_TYPE_MULTIPLE) {
+ mFadingOut.resetMultipleStrokes = true;
+ postDelayed(mFadingOut, mFadeOffset);
+ } else {
+ mCurrentGesture = null;
+ mPath.rewind();
+ invalidate();
+ }
+ }
+ }
+
+ public void cancelClearAnimation() {
+ setPaintAlpha(255);
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ removeCallbacks(mFadingOut);
+ mPath.rewind();
+ mCurrentGesture = null;
+ }
+
+ public void cancelGesture() {
+ mIsListeningForGestures = false;
+
+ // add the stroke to the current gesture
+ mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+ // pass the event to handlers
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent event = MotionEvent.obtain(now, now,
+ MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureCancelled(this, event);
+ }
+
+ event.recycle();
+
+ clear(false);
+ mIsGesturing = false;
+ mPreviousWasGesturing = false;
+ mStrokeBuffer.clear();
+
+ final ArrayList<OnGesturingListener> otherListeners = mOnGesturingListeners;
+ count = otherListeners.size();
+ for (int i = 0; i < count; i++) {
+ otherListeners.get(i).onGesturingEnded(this);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ cancelClearAnimation();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (isEnabled()) {
+ final boolean cancelDispatch = (mIsGesturing || (mCurrentGesture != null &&
+ mCurrentGesture.getStrokesCount() > 0 && mPreviousWasGesturing)) &&
+ mInterceptEvents;
+
+ processEvent(event);
+
+ if (cancelDispatch) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ }
+
+ super.dispatchTouchEvent(event);
+
+ return true;
+ }
+
+ return super.dispatchTouchEvent(event);
+ }
+
+ private boolean processEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ touchDown(event);
+ invalidate();
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ if (mIsListeningForGestures) {
+ Rect rect = touchMove(event);
+ if (rect != null) {
+ invalidate(rect);
+ }
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mIsListeningForGestures) {
+ touchUp(event, false);
+ invalidate();
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mIsListeningForGestures) {
+ touchUp(event, true);
+ invalidate();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void touchDown(MotionEvent event) {
+ mIsListeningForGestures = true;
+
+ float x = event.getX();
+ float y = event.getY();
+
+ mX = x;
+ mY = y;
+
+ mTotalLength = 0;
+ mIsGesturing = false;
+
+ if (mGestureStrokeType == GESTURE_STROKE_TYPE_SINGLE || mResetGesture) {
+ if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
+ mResetGesture = false;
+ mCurrentGesture = null;
+ mPath.rewind();
+ } else if (mCurrentGesture == null || mCurrentGesture.getStrokesCount() == 0) {
+ if (mHandleGestureActions) setCurrentColor(mUncertainGestureColor);
+ }
+
+ // if there is fading out going on, stop it.
+ if (mFadingHasStarted) {
+ cancelClearAnimation();
+ } else if (mIsFadingOut) {
+ setPaintAlpha(255);
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ removeCallbacks(mFadingOut);
+ }
+
+ if (mCurrentGesture == null) {
+ mCurrentGesture = new Gesture();
+ }
+
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ mPath.moveTo(x, y);
+
+ final int border = mInvalidateExtraBorder;
+ mInvalidRect.set((int) x - border, (int) y - border, (int) x + border, (int) y + border);
+
+ mCurveEndX = x;
+ mCurveEndY = y;
+
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureStarted(this, event);
+ }
+ }
+
+ private Rect touchMove(MotionEvent event) {
+ Rect areaToRefresh = null;
+
+ final float x = event.getX();
+ final float y = event.getY();
+
+ final float previousX = mX;
+ final float previousY = mY;
+
+ final float dx = Math.abs(x - previousX);
+ final float dy = Math.abs(y - previousY);
+
+ if (dx >= GestureStroke.TOUCH_TOLERANCE || dy >= GestureStroke.TOUCH_TOLERANCE) {
+ areaToRefresh = mInvalidRect;
+
+ // start with the curve end
+ final int border = mInvalidateExtraBorder;
+ areaToRefresh.set((int) mCurveEndX - border, (int) mCurveEndY - border,
+ (int) mCurveEndX + border, (int) mCurveEndY + border);
+
+ float cX = mCurveEndX = (x + previousX) / 2;
+ float cY = mCurveEndY = (y + previousY) / 2;
+
+ mPath.quadTo(previousX, previousY, cX, cY);
+
+ // union with the control point of the new curve
+ areaToRefresh.union((int) previousX - border, (int) previousY - border,
+ (int) previousX + border, (int) previousY + border);
+
+ // union with the end point of the new curve
+ areaToRefresh.union((int) cX - border, (int) cY - border,
+ (int) cX + border, (int) cY + border);
+
+ mX = x;
+ mY = y;
+
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+
+ if (mHandleGestureActions && !mIsGesturing) {
+ mTotalLength += (float) Math.sqrt(dx * dx + dy * dy);
+
+ if (mTotalLength > mGestureStrokeLengthThreshold) {
+ final OrientedBoundingBox box =
+ GestureUtilities.computeOrientedBoundingBox(mStrokeBuffer);
+
+ float angle = Math.abs(box.orientation);
+ if (angle > 90) {
+ angle = 180 - angle;
+ }
+
+ if (box.squareness > mGestureStrokeSquarenessTreshold ||
+ (mOrientation == ORIENTATION_VERTICAL ?
+ angle < mGestureStrokeAngleThreshold :
+ angle > mGestureStrokeAngleThreshold)) {
+
+ mIsGesturing = true;
+ setCurrentColor(mCertainGestureColor);
+
+ final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingStarted(this);
+ }
+ }
+ }
+ }
+
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesture(this, event);
+ }
+ }
+
+ return areaToRefresh;
+ }
+
+ private void touchUp(MotionEvent event, boolean cancel) {
+ mIsListeningForGestures = false;
+
+ // A gesture wasn't started or was cancelled
+ if (mCurrentGesture != null) {
+ // add the stroke to the current gesture
+ mCurrentGesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+ if (!cancel) {
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureEnded(this, event);
+ }
+
+ clear(mHandleGestureActions && mFadeEnabled, mHandleGestureActions && mIsGesturing,
+ false);
+ } else {
+ cancelGesture(event);
+
+ }
+ } else {
+ cancelGesture(event);
+ }
+
+ mStrokeBuffer.clear();
+ mPreviousWasGesturing = mIsGesturing;
+ mIsGesturing = false;
+
+ final ArrayList<OnGesturingListener> listeners = mOnGesturingListeners;
+ int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGesturingEnded(this);
+ }
+ }
+
+ private void cancelGesture(MotionEvent event) {
+ // pass the event to handlers
+ final ArrayList<OnGestureListener> listeners = mOnGestureListeners;
+ final int count = listeners.size();
+ for (int i = 0; i < count; i++) {
+ listeners.get(i).onGestureCancelled(this, event);
+ }
+
+ clear(false);
+ }
+
+ private void fireOnGesturePerformed() {
+ final ArrayList<OnGesturePerformedListener> actionListeners = mOnGesturePerformedListeners;
+ final int count = actionListeners.size();
+ for (int i = 0; i < count; i++) {
+ actionListeners.get(i).onGesturePerformed(GestureOverlayView.this, mCurrentGesture);
+ }
+ }
+
+ private class FadeOutRunnable implements Runnable {
+ boolean fireActionPerformed;
+ boolean resetMultipleStrokes;
+
+ public void run() {
+ if (mIsFadingOut) {
+ final long now = AnimationUtils.currentAnimationTimeMillis();
+ final long duration = now - mFadingStart;
+
+ if (duration > mFadeDuration) {
+ if (fireActionPerformed) {
+ fireOnGesturePerformed();
+ }
+
+ mPreviousWasGesturing = false;
+ mIsFadingOut = false;
+ mFadingHasStarted = false;
+ mPath.rewind();
+ mCurrentGesture = null;
+ setPaintAlpha(255);
+ } else {
+ mFadingHasStarted = true;
+ float interpolatedTime = Math.max(0.0f,
+ Math.min(1.0f, duration / (float) mFadeDuration));
+ mFadingAlpha = 1.0f - mInterpolator.getInterpolation(interpolatedTime);
+ setPaintAlpha((int) (255 * mFadingAlpha));
+ postDelayed(this, FADE_ANIMATION_RATE);
+ }
+ } else if (resetMultipleStrokes) {
+ mResetGesture = true;
+ } else {
+ fireOnGesturePerformed();
+
+ mFadingHasStarted = false;
+ mPath.rewind();
+ mCurrentGesture = null;
+ mPreviousWasGesturing = false;
+ setPaintAlpha(255);
+ }
+
+ invalidate();
+ }
+ }
+
+ public static interface OnGesturingListener {
+ void onGesturingStarted(GestureOverlayView overlay);
+
+ void onGesturingEnded(GestureOverlayView overlay);
+ }
+
+ public static interface OnGestureListener {
+ void onGestureStarted(GestureOverlayView overlay, MotionEvent event);
+
+ void onGesture(GestureOverlayView overlay, MotionEvent event);
+
+ void onGestureEnded(GestureOverlayView overlay, MotionEvent event);
+
+ void onGestureCancelled(GestureOverlayView overlay, MotionEvent event);
+ }
+
+ public static interface OnGesturePerformedListener {
+ void onGesturePerformed(GestureOverlayView overlay, Gesture gesture);
+ }
+}
diff --git a/core/java/android/gesture/GesturePoint.java b/core/java/android/gesture/GesturePoint.java
new file mode 100644
index 0000000..3698011
--- /dev/null
+++ b/core/java/android/gesture/GesturePoint.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+/**
+ * A timed point of a gesture stroke
+ */
+
+public class GesturePoint {
+ public final float x;
+ public final float y;
+
+ public final long timestamp;
+
+ public GesturePoint(float x, float y, long t) {
+ this.x = x;
+ this.y = y;
+ timestamp = t;
+ }
+
+ static GesturePoint deserialize(DataInputStream in) throws IOException {
+ // Read X and Y
+ final float x = in.readFloat();
+ final float y = in.readFloat();
+ // Read timestamp
+ final long timeStamp = in.readLong();
+ return new GesturePoint(x, y, timeStamp);
+ }
+}
diff --git a/core/java/android/gesture/GestureStore.java b/core/java/android/gesture/GestureStore.java
new file mode 100644
index 0000000..5f1a445
--- /dev/null
+++ b/core/java/android/gesture/GestureStore.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+import android.util.Log;
+import android.os.SystemClock;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Map;
+
+import static android.gesture.GestureConstants.LOG_TAG;
+
+/**
+ * GestureLibrary maintains gesture examples and makes predictions on a new
+ * gesture
+ */
+//
+// File format for GestureStore:
+//
+// Nb. bytes Java type Description
+// -----------------------------------
+// Header
+// 2 bytes short File format version number
+// 4 bytes int Number of entries
+// Entry
+// X bytes UTF String Entry name
+// 4 bytes int Number of gestures
+// Gesture
+// 8 bytes long Gesture ID
+// 4 bytes int Number of strokes
+// Stroke
+// 4 bytes int Number of points
+// Point
+// 4 bytes float X coordinate of the point
+// 4 bytes float Y coordinate of the point
+// 8 bytes long Time stamp
+//
+public class GestureStore {
+ public static final int SEQUENCE_INVARIANT = 1;
+ // when SEQUENCE_SENSITIVE is used, only single stroke gestures are currently allowed
+ public static final int SEQUENCE_SENSITIVE = 2;
+
+ // ORIENTATION_SENSITIVE and ORIENTATION_INVARIANT are only for SEQUENCE_SENSITIVE gestures
+ public static final int ORIENTATION_INVARIANT = 1;
+ public static final int ORIENTATION_SENSITIVE = 2;
+
+ private static final short FILE_FORMAT_VERSION = 1;
+
+ private static final boolean PROFILE_LOADING_SAVING = false;
+
+ private int mSequenceType = SEQUENCE_SENSITIVE;
+ private int mOrientationStyle = ORIENTATION_SENSITIVE;
+
+ private final HashMap<String, ArrayList<Gesture>> mNamedGestures =
+ new HashMap<String, ArrayList<Gesture>>();
+
+ private Learner mClassifier;
+
+ private boolean mChanged = false;
+
+ public GestureStore() {
+ mClassifier = new InstanceLearner();
+ }
+
+ /**
+ * Specify how the gesture library will handle orientation.
+ * Use ORIENTATION_INVARIANT or ORIENTATION_SENSITIVE
+ *
+ * @param style
+ */
+ public void setOrientationStyle(int style) {
+ mOrientationStyle = style;
+ }
+
+ public int getOrientationStyle() {
+ return mOrientationStyle;
+ }
+
+ /**
+ * @param type SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
+ */
+ public void setSequenceType(int type) {
+ mSequenceType = type;
+ }
+
+ /**
+ * @return SEQUENCE_INVARIANT or SEQUENCE_SENSITIVE
+ */
+ public int getSequenceType() {
+ return mSequenceType;
+ }
+
+ /**
+ * Get all the gesture entry names in the library
+ *
+ * @return a set of strings
+ */
+ public Set<String> getGestureEntries() {
+ return mNamedGestures.keySet();
+ }
+
+ /**
+ * Recognize a gesture
+ *
+ * @param gesture the query
+ * @return a list of predictions of possible entries for a given gesture
+ */
+ public ArrayList<Prediction> recognize(Gesture gesture) {
+ Instance instance = Instance.createInstance(mSequenceType,
+ mOrientationStyle, gesture, null);
+ return mClassifier.classify(mSequenceType, instance.vector);
+ }
+
+ /**
+ * Add a gesture for the entry
+ *
+ * @param entryName entry name
+ * @param gesture
+ */
+ public void addGesture(String entryName, Gesture gesture) {
+ if (entryName == null || entryName.length() == 0) {
+ return;
+ }
+ ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
+ if (gestures == null) {
+ gestures = new ArrayList<Gesture>();
+ mNamedGestures.put(entryName, gestures);
+ }
+ gestures.add(gesture);
+ mClassifier.addInstance(
+ Instance.createInstance(mSequenceType, mOrientationStyle, gesture, entryName));
+ mChanged = true;
+ }
+
+ /**
+ * Remove a gesture from the library. If there are no more gestures for the
+ * given entry, the gesture entry will be removed.
+ *
+ * @param entryName entry name
+ * @param gesture
+ */
+ public void removeGesture(String entryName, Gesture gesture) {
+ ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
+ if (gestures == null) {
+ return;
+ }
+
+ gestures.remove(gesture);
+
+ // if there are no more samples, remove the entry automatically
+ if (gestures.isEmpty()) {
+ mNamedGestures.remove(entryName);
+ }
+
+ mClassifier.removeInstance(gesture.getID());
+
+ mChanged = true;
+ }
+
+ /**
+ * Remove a entry of gestures
+ *
+ * @param entryName the entry name
+ */
+ public void removeEntry(String entryName) {
+ mNamedGestures.remove(entryName);
+ mClassifier.removeInstances(entryName);
+ mChanged = true;
+ }
+
+ /**
+ * Get all the gestures of an entry
+ *
+ * @param entryName
+ * @return the list of gestures that is under this name
+ */
+ public ArrayList<Gesture> getGestures(String entryName) {
+ ArrayList<Gesture> gestures = mNamedGestures.get(entryName);
+ if (gestures != null) {
+ return new ArrayList<Gesture>(gestures);
+ } else {
+ return null;
+ }
+ }
+
+ public boolean hasChanged() {
+ return mChanged;
+ }
+
+ /**
+ * Save the gesture library
+ */
+ public void save(OutputStream stream) throws IOException {
+ save(stream, false);
+ }
+
+ public void save(OutputStream stream, boolean closeStream) throws IOException {
+ DataOutputStream out = null;
+
+ try {
+ long start;
+ if (PROFILE_LOADING_SAVING) {
+ start = SystemClock.elapsedRealtime();
+ }
+
+ final HashMap<String, ArrayList<Gesture>> maps = mNamedGestures;
+
+ out = new DataOutputStream((stream instanceof BufferedOutputStream) ? stream :
+ new BufferedOutputStream(stream, GestureConstants.IO_BUFFER_SIZE));
+ // Write version number
+ out.writeShort(FILE_FORMAT_VERSION);
+ // Write number of entries
+ out.writeInt(maps.size());
+
+ for (Map.Entry<String, ArrayList<Gesture>> entry : maps.entrySet()) {
+ final String key = entry.getKey();
+ final ArrayList<Gesture> examples = entry.getValue();
+ final int count = examples.size();
+
+ // Write entry name
+ out.writeUTF(key);
+ // Write number of examples for this entry
+ out.writeInt(count);
+
+ for (int i = 0; i < count; i++) {
+ examples.get(i).serialize(out);
+ }
+ }
+
+ out.flush();
+
+ if (PROFILE_LOADING_SAVING) {
+ long end = SystemClock.elapsedRealtime();
+ Log.d(LOG_TAG, "Saving gestures library = " + (end - start) + " ms");
+ }
+
+ mChanged = false;
+ } finally {
+ if (closeStream) GestureUtilities.closeStream(out);
+ }
+ }
+
+ /**
+ * Load the gesture library
+ */
+ public void load(InputStream stream) throws IOException {
+ load(stream, false);
+ }
+
+ public void load(InputStream stream, boolean closeStream) throws IOException {
+ DataInputStream in = null;
+ try {
+ in = new DataInputStream((stream instanceof BufferedInputStream) ? stream :
+ new BufferedInputStream(stream, GestureConstants.IO_BUFFER_SIZE));
+
+ long start;
+ if (PROFILE_LOADING_SAVING) {
+ start = SystemClock.elapsedRealtime();
+ }
+
+ // Read file format version number
+ final short versionNumber = in.readShort();
+ switch (versionNumber) {
+ case 1:
+ readFormatV1(in);
+ break;
+ }
+
+ if (PROFILE_LOADING_SAVING) {
+ long end = SystemClock.elapsedRealtime();
+ Log.d(LOG_TAG, "Loading gestures library = " + (end - start) + " ms");
+ }
+ } finally {
+ if (closeStream) GestureUtilities.closeStream(in);
+ }
+ }
+
+ private void readFormatV1(DataInputStream in) throws IOException {
+ final Learner classifier = mClassifier;
+ final HashMap<String, ArrayList<Gesture>> namedGestures = mNamedGestures;
+ namedGestures.clear();
+
+ // Number of entries in the library
+ final int entriesCount = in.readInt();
+
+ for (int i = 0; i < entriesCount; i++) {
+ // Entry name
+ final String name = in.readUTF();
+ // Number of gestures
+ final int gestureCount = in.readInt();
+
+ final ArrayList<Gesture> gestures = new ArrayList<Gesture>(gestureCount);
+ for (int j = 0; j < gestureCount; j++) {
+ final Gesture gesture = Gesture.deserialize(in);
+ gestures.add(gesture);
+ classifier.addInstance(
+ Instance.createInstance(mSequenceType, mOrientationStyle, gesture, name));
+ }
+
+ namedGestures.put(name, gestures);
+ }
+ }
+
+ Learner getLearner() {
+ return mClassifier;
+ }
+}
diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java
new file mode 100644
index 0000000..598eb85
--- /dev/null
+++ b/core/java/android/gesture/GestureStroke.java
@@ -0,0 +1,229 @@
+/*
+ * 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.gesture;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+import java.io.IOException;
+import java.io.DataOutputStream;
+import java.io.DataInputStream;
+import java.util.ArrayList;
+
+/**
+ * A gesture stroke started on a touch down and ended on a touch up.
+ */
+public class GestureStroke {
+ static final float TOUCH_TOLERANCE = 8;
+
+ public final RectF boundingBox;
+
+ public final float length;
+ public final float[] points;
+
+ private final long[] timestamps;
+ private Path mCachedPath;
+
+ /**
+ * Construct a gesture stroke from a list of gesture points
+ *
+ * @param points
+ */
+ public GestureStroke(ArrayList<GesturePoint> points) {
+ final int count = points.size();
+ final float[] tmpPoints = new float[count * 2];
+ final long[] times = new long[count];
+
+ RectF bx = null;
+ float len = 0;
+ int index = 0;
+
+ for (int i = 0; i < count; i++) {
+ final GesturePoint p = points.get(i);
+ tmpPoints[i * 2] = p.x;
+ tmpPoints[i * 2 + 1] = p.y;
+ times[index] = p.timestamp;
+
+ if (bx == null) {
+ bx = new RectF();
+ bx.top = p.y;
+ bx.left = p.x;
+ bx.right = p.x;
+ bx.bottom = p.y;
+ len = 0;
+ } else {
+ len += Math.sqrt(Math.pow(p.x - tmpPoints[(i - 1) * 2], 2)
+ + Math.pow(p.y - tmpPoints[(i -1 ) * 2 + 1], 2));
+ bx.union(p.x, p.y);
+ }
+ index++;
+ }
+
+ timestamps = times;
+ this.points = tmpPoints;
+ boundingBox = bx;
+ length = len;
+ }
+
+ /**
+ * Draw the gesture with a given canvas and paint
+ *
+ * @param canvas
+ */
+ void draw(Canvas canvas, Paint paint) {
+ if (mCachedPath == null) {
+ makePath();
+ }
+
+ canvas.drawPath(mCachedPath, paint);
+ }
+
+ public Path getPath() {
+ if (mCachedPath == null) {
+ makePath();
+ }
+
+ return mCachedPath;
+ }
+
+ private void makePath() {
+ final float[] localPoints = points;
+ final int count = localPoints.length;
+
+ Path path = null;
+
+ float mX = 0;
+ float mY = 0;
+
+ for (int i = 0; i < count; i += 2) {
+ float x = localPoints[i];
+ float y = localPoints[i + 1];
+ if (path == null) {
+ path = new Path();
+ path.moveTo(x, y);
+ mX = x;
+ mY = y;
+ } else {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
+ path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
+ mX = x;
+ mY = y;
+ }
+ }
+ }
+
+ mCachedPath = path;
+ }
+
+ /**
+ * Convert the stroke to a Path based on the number of points
+ *
+ * @param width the width of the bounding box of the target path
+ * @param height the height of the bounding box of the target path
+ * @param numSample the number of points needed
+ *
+ * @return the path
+ */
+ public Path toPath(float width, float height, int numSample) {
+ final float[] pts = GestureUtilities.temporalSampling(this, numSample);
+ final RectF rect = boundingBox;
+
+ GestureUtilities.translate(pts, -rect.left, -rect.top);
+
+ float sx = width / rect.width();
+ float sy = height / rect.height();
+ float scale = sx > sy ? sy : sx;
+ GestureUtilities.scale(pts, scale, scale);
+
+ float mX = 0;
+ float mY = 0;
+
+ Path path = null;
+
+ final int count = pts.length;
+
+ for (int i = 0; i < count; i += 2) {
+ float x = pts[i];
+ float y = pts[i + 1];
+ if (path == null) {
+ path = new Path();
+ path.moveTo(x, y);
+ mX = x;
+ mY = y;
+ } else {
+ float dx = Math.abs(x - mX);
+ float dy = Math.abs(y - mY);
+ if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
+ path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
+ mX = x;
+ mY = y;
+ }
+ }
+ }
+
+ return path;
+ }
+
+ void serialize(DataOutputStream out) throws IOException {
+ final float[] pts = points;
+ final long[] times = timestamps;
+ final int count = points.length;
+
+ // Write number of points
+ out.writeInt(count / 2);
+
+ for (int i = 0; i < count; i += 2) {
+ // Write X
+ out.writeFloat(pts[i]);
+ // Write Y
+ out.writeFloat(pts[i + 1]);
+ // Write timestamp
+ out.writeLong(times[i / 2]);
+ }
+ }
+
+ static GestureStroke deserialize(DataInputStream in) throws IOException {
+ // Number of points
+ final int count = in.readInt();
+
+ final ArrayList<GesturePoint> points = new ArrayList<GesturePoint>(count);
+ for (int i = 0; i < count; i++) {
+ points.add(GesturePoint.deserialize(in));
+ }
+
+ return new GestureStroke(points);
+ }
+
+ /**
+ * Invalidate the cached path that is used to render the stroke
+ */
+ public void clearPath() {
+ if (mCachedPath != null) mCachedPath.rewind();
+ }
+
+ /**
+ * Compute an oriented bounding box of the stroke
+ * @return OrientedBoundingBox
+ */
+ public OrientedBoundingBox computeOrientedBoundingBox() {
+ return GestureUtilities.computeOrientedBoundingBox(points);
+ }
+}
diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java
new file mode 100755
index 0000000..40d7029
--- /dev/null
+++ b/core/java/android/gesture/GestureUtilities.java
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+import android.graphics.RectF;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.io.Closeable;
+import java.io.IOException;
+
+import static android.gesture.GestureConstants.*;
+
+final class GestureUtilities {
+ private static final int TEMPORAL_SAMPLING_RATE = 16;
+
+ private GestureUtilities() {
+ }
+
+ /**
+ * Closes the specified stream.
+ *
+ * @param stream The stream to close.
+ */
+ static void closeStream(Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Could not close stream", e);
+ }
+ }
+ }
+
+ static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension) {
+ final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive
+ float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension];
+ Arrays.fill(sample, 0);
+
+ RectF rect = gesture.getBoundingBox();
+ float sx = targetPatchSize / rect.width();
+ float sy = targetPatchSize / rect.height();
+ float scale = sx < sy ? sx : sy;
+
+ float preDx = -rect.centerX();
+ float preDy = -rect.centerY();
+ float postDx = targetPatchSize / 2;
+ float postDy = targetPatchSize / 2;
+
+ final ArrayList<GestureStroke> strokes = gesture.getStrokes();
+ final int count = strokes.size();
+
+ int size;
+ float xpos;
+ float ypos;
+
+ for (int index = 0; index < count; index++) {
+ final GestureStroke stroke = strokes.get(index);
+ float[] strokepoints = stroke.points;
+ size = strokepoints.length;
+
+ final float[] pts = new float[size];
+
+ for (int i = 0; i < size; i += 2) {
+ pts[i] = (strokepoints[i] + preDx) * scale + postDx;
+ pts[i + 1] = (strokepoints[i + 1] + preDy) * scale + postDy;
+ }
+
+ float segmentEndX = -1;
+ float segmentEndY = -1;
+
+ for (int i = 0; i < size; i += 2) {
+
+ float segmentStartX = pts[i] < 0 ? 0 : pts[i];
+ float segmentStartY = pts[i + 1] < 0 ? 0 : pts[i + 1];
+
+ if (segmentStartX > targetPatchSize) {
+ segmentStartX = targetPatchSize;
+ }
+
+ if (segmentStartY > targetPatchSize) {
+ segmentStartY = targetPatchSize;
+ }
+
+ plot(segmentStartX, segmentStartY, sample, sampleMatrixDimension);
+
+ if (segmentEndX != -1) {
+ // evaluate horizontally
+ if (segmentEndX > segmentStartX) {
+ xpos = (float) Math.ceil(segmentStartX);
+ float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX);
+ while (xpos < segmentEndX) {
+ ypos = slope * (xpos - segmentStartX) + segmentStartY;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ xpos++;
+ }
+ } else if (segmentEndX < segmentStartX){
+ xpos = (float) Math.ceil(segmentEndX);
+ float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX);
+ while (xpos < segmentStartX) {
+ ypos = slope * (xpos - segmentStartX) + segmentStartY;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ xpos++;
+ }
+ }
+
+ // evaluating vertically
+ if (segmentEndY > segmentStartY) {
+ ypos = (float) Math.ceil(segmentStartY);
+ float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY);
+ while (ypos < segmentEndY) {
+ xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ ypos++;
+ }
+ } else if (segmentEndY < segmentStartY) {
+ ypos = (float) Math.ceil(segmentEndY);
+ float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY);
+ while (ypos < segmentStartY) {
+ xpos = invertSlope * (ypos - segmentStartY) + segmentStartX;
+ plot(xpos, ypos, sample, sampleMatrixDimension);
+ ypos++;
+ }
+ }
+ }
+
+ segmentEndX = segmentStartX;
+ segmentEndY = segmentStartY;
+ }
+ }
+
+
+ return sample;
+ }
+
+ private static void plot(float x, float y, float[] sample, int sampleSize) {
+ x = x < 0 ? 0 : x;
+ y = y < 0 ? 0 : y;
+ int xFloor = (int) Math.floor(x);
+ int xCeiling = (int) Math.ceil(x);
+ int yFloor = (int) Math.floor(y);
+ int yCeiling = (int) Math.ceil(y);
+
+ // if it's an integer
+ if (x == xFloor && y == yFloor) {
+ int index = yCeiling * sampleSize + xCeiling;
+ if (sample[index] < 1){
+ sample[index] = 1;
+ }
+ } else {
+ double topLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yFloor - y, 2));
+ double topRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yFloor - y, 2));
+ double btmLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yCeiling - y, 2));
+ double btmRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yCeiling - y, 2));
+ double sum = topLeft + topRight + btmLeft + btmRight;
+
+ double value = topLeft / sum;
+ int index = yFloor * sampleSize + xFloor;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+
+ value = topRight / sum;
+ index = yFloor * sampleSize + xCeiling;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+
+ value = btmLeft / sum;
+ index = yCeiling * sampleSize + xFloor;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+
+ value = btmRight / sum;
+ index = yCeiling * sampleSize + xCeiling;
+ if (value > sample[index]){
+ sample[index] = (float) value;
+ }
+ }
+ }
+
+ /**
+ * Featurize a stroke into a vector of a given number of elements
+ *
+ * @param stroke
+ * @param sampleSize
+ * @return a float array
+ */
+ static float[] temporalSampling(GestureStroke stroke, int sampleSize) {
+ final float increment = stroke.length / (sampleSize - 1);
+ int vectorLength = sampleSize * 2;
+ float[] vector = new float[vectorLength];
+ float distanceSoFar = 0;
+ float[] pts = stroke.points;
+ float lstPointX = pts[0];
+ float lstPointY = pts[1];
+ int index = 0;
+ float currentPointX = Float.MIN_VALUE;
+ float currentPointY = Float.MIN_VALUE;
+ vector[index] = lstPointX;
+ index++;
+ vector[index] = lstPointY;
+ index++;
+ int i = 0;
+ int count = pts.length / 2;
+ while (i < count) {
+ if (currentPointX == Float.MIN_VALUE) {
+ i++;
+ if (i >= count) {
+ break;
+ }
+ currentPointX = pts[i * 2];
+ currentPointY = pts[i * 2 + 1];
+ }
+ float deltaX = currentPointX - lstPointX;
+ float deltaY = currentPointY - lstPointY;
+ float distance = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ if (distanceSoFar + distance >= increment) {
+ float ratio = (increment - distanceSoFar) / distance;
+ float nx = lstPointX + ratio * deltaX;
+ float ny = lstPointY + ratio * deltaY;
+ vector[index] = nx;
+ index++;
+ vector[index] = ny;
+ index++;
+ lstPointX = nx;
+ lstPointY = ny;
+ distanceSoFar = 0;
+ } else {
+ lstPointX = currentPointX;
+ lstPointY = currentPointY;
+ currentPointX = Float.MIN_VALUE;
+ currentPointY = Float.MIN_VALUE;
+ distanceSoFar += distance;
+ }
+ }
+
+ for (i = index; i < vectorLength; i += 2) {
+ vector[i] = lstPointX;
+ vector[i + 1] = lstPointY;
+ }
+ return vector;
+ }
+
+ /**
+ * Calculate the centroid
+ *
+ * @param points
+ * @return the centroid
+ */
+ static float[] computeCentroid(float[] points) {
+ float centerX = 0;
+ float centerY = 0;
+ int count = points.length;
+ for (int i = 0; i < count; i++) {
+ centerX += points[i];
+ i++;
+ centerY += points[i];
+ }
+ float[] center = new float[2];
+ center[0] = 2 * centerX / count;
+ center[1] = 2 * centerY / count;
+
+ return center;
+ }
+
+ /**
+ * calculate the variance-covariance matrix, treat each point as a sample
+ *
+ * @param points
+ * @return the covariance matrix
+ */
+ private static double[][] computeCoVariance(float[] points) {
+ double[][] array = new double[2][2];
+ array[0][0] = 0;
+ array[0][1] = 0;
+ array[1][0] = 0;
+ array[1][1] = 0;
+ int count = points.length;
+ for (int i = 0; i < count; i++) {
+ float x = points[i];
+ i++;
+ float y = points[i];
+ array[0][0] += x * x;
+ array[0][1] += x * y;
+ array[1][0] = array[0][1];
+ array[1][1] += y * y;
+ }
+ array[0][0] /= (count / 2);
+ array[0][1] /= (count / 2);
+ array[1][0] /= (count / 2);
+ array[1][1] /= (count / 2);
+
+ return array;
+ }
+
+ static float computeTotalLength(float[] points) {
+ float sum = 0;
+ int count = points.length - 4;
+ for (int i = 0; i < count; i += 2) {
+ float dx = points[i + 2] - points[i];
+ float dy = points[i + 3] - points[i + 1];
+ sum += Math.sqrt(dx * dx + dy * dy);
+ }
+ return sum;
+ }
+
+ static double computeStraightness(float[] points) {
+ float totalLen = computeTotalLength(points);
+ float dx = points[2] - points[0];
+ float dy = points[3] - points[1];
+ return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ }
+
+ static double computeStraightness(float[] points, float totalLen) {
+ float dx = points[2] - points[0];
+ float dy = points[3] - points[1];
+ return Math.sqrt(dx * dx + dy * dy) / totalLen;
+ }
+
+ /**
+ * Calculate the squared Euclidean distance between two vectors
+ *
+ * @param vector1
+ * @param vector2
+ * @return the distance
+ */
+ static double squaredEuclideanDistance(float[] vector1, float[] vector2) {
+ double squaredDistance = 0;
+ int size = vector1.length;
+ for (int i = 0; i < size; i++) {
+ float difference = vector1[i] - vector2[i];
+ squaredDistance += difference * difference;
+ }
+ return squaredDistance / size;
+ }
+
+ /**
+ * Calculate the cosine distance between two instances
+ *
+ * @param vector1
+ * @param vector2
+ * @return the distance between 0 and Math.PI
+ */
+ static double cosineDistance(float[] vector1, float[] vector2) {
+ float sum = 0;
+ int len = vector1.length;
+ for (int i = 0; i < len; i++) {
+ sum += vector1[i] * vector2[i];
+ }
+ return Math.acos(sum);
+ }
+
+ static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> pts) {
+ GestureStroke stroke = new GestureStroke(pts);
+ float[] points = temporalSampling(stroke, TEMPORAL_SAMPLING_RATE);
+ return computeOrientedBoundingBox(points);
+ }
+
+ static OrientedBoundingBox computeOrientedBoundingBox(float[] points) {
+ float[] meanVector = computeCentroid(points);
+ return computeOrientedBoundingBox(points, meanVector);
+ }
+
+ static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) {
+ translate(points, -centroid[0], -centroid[1]);
+
+ double[][] array = computeCoVariance(points);
+ double[] targetVector = computeOrientation(array);
+
+ float angle;
+ if (targetVector[0] == 0 && targetVector[1] == 0) {
+ angle = (float) -Math.PI/2;
+ } else { // -PI<alpha<PI
+ angle = (float) Math.atan2(targetVector[1], targetVector[0]);
+ rotate(points, -angle);
+ }
+
+ float minx = Float.MAX_VALUE;
+ float miny = Float.MAX_VALUE;
+ float maxx = Float.MIN_VALUE;
+ float maxy = Float.MIN_VALUE;
+ int count = points.length;
+ for (int i = 0; i < count; i++) {
+ if (points[i] < minx) {
+ minx = points[i];
+ }
+ if (points[i] > maxx) {
+ maxx = points[i];
+ }
+ i++;
+ if (points[i] < miny) {
+ miny = points[i];
+ }
+ if (points[i] > maxy) {
+ maxy = points[i];
+ }
+ }
+
+ return new OrientedBoundingBox((float) (angle * 180 / Math.PI), centroid[0], centroid[1], maxx - minx, maxy - miny);
+ }
+
+ private static double[] computeOrientation(double[][] covarianceMatrix) {
+ double[] targetVector = new double[2];
+ if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) {
+ targetVector[0] = 1;
+ targetVector[1] = 0;
+ }
+
+ double a = -covarianceMatrix[0][0] - covarianceMatrix[1][1];
+ double b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1]
+ * covarianceMatrix[1][0];
+ double value = a / 2;
+ double rightside = Math.sqrt(Math.pow(value, 2) - b);
+ double lambda1 = -value + rightside;
+ double lambda2 = -value - rightside;
+ if (lambda1 == lambda2) {
+ targetVector[0] = 0;
+ targetVector[1] = 0;
+ } else {
+ double lambda = lambda1 > lambda2 ? lambda1 : lambda2;
+ targetVector[0] = 1;
+ targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1];
+ }
+ return targetVector;
+ }
+
+
+ static float[] rotate(float[] points, double angle) {
+ double cos = Math.cos(angle);
+ double sin = Math.sin(angle);
+ int size = points.length;
+ for (int i = 0; i < size; i += 2) {
+ float x = (float) (points[i] * cos - points[i + 1] * sin);
+ float y = (float) (points[i] * sin + points[i + 1] * cos);
+ points[i] = x;
+ points[i + 1] = y;
+ }
+ return points;
+ }
+
+ static float[] translate(float[] points, float dx, float dy) {
+ int size = points.length;
+ for (int i = 0; i < size; i += 2) {
+ points[i] += dx;
+ points[i + 1] += dy;
+ }
+ return points;
+ }
+
+ static float[] scale(float[] points, float sx, float sy) {
+ int size = points.length;
+ for (int i = 0; i < size; i += 2) {
+ points[i] *= sx;
+ points[i + 1] *= sy;
+ }
+ return points;
+ }
+}
diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java
new file mode 100755
index 0000000..ef208ac
--- /dev/null
+++ b/core/java/android/gesture/Instance.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+
+/**
+ * An instance represents a sample if the label is available or a query if the
+ * label is null.
+ */
+class Instance {
+ private static final int SEQUENCE_SAMPLE_SIZE = 16;
+
+ private static final int PATCH_SAMPLE_SIZE = 16;
+
+ private final static float[] ORIENTATIONS = {
+ 0, (float) (Math.PI / 4), (float) (Math.PI / 2), (float) (Math.PI * 3 / 4),
+ (float) Math.PI, -0, (float) (-Math.PI / 4), (float) (-Math.PI / 2),
+ (float) (-Math.PI * 3 / 4), (float) -Math.PI
+ };
+
+ // the feature vector
+ final float[] vector;
+
+ // the label can be null
+ final String label;
+
+ // the id of the instance
+ final long id;
+
+ private Instance(long id, float[] sample, String sampleName) {
+ this.id = id;
+ vector = sample;
+ label = sampleName;
+ }
+
+ private void normalize() {
+ float[] sample = vector;
+ float sum = 0;
+
+ int size = sample.length;
+ for (int i = 0; i < size; i++) {
+ sum += sample[i] * sample[i];
+ }
+
+ float magnitude = (float)Math.sqrt(sum);
+ for (int i = 0; i < size; i++) {
+ sample[i] /= magnitude;
+ }
+ }
+
+ /**
+ * create a learning instance for a single stroke gesture
+ *
+ * @param gesture
+ * @param label
+ * @return the instance
+ */
+ static Instance createInstance(int sequenceType, int orientationType, Gesture gesture, String label) {
+ float[] pts;
+ Instance instance;
+ if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
+ pts = temporalSampler(orientationType, gesture);
+ instance = new Instance(gesture.getID(), pts, label);
+ instance.normalize();
+ } else {
+ pts = spatialSampler(gesture);
+ instance = new Instance(gesture.getID(), pts, label);
+ }
+ return instance;
+ }
+
+ private static float[] spatialSampler(Gesture gesture) {
+ return GestureUtilities.spatialSampling(gesture, PATCH_SAMPLE_SIZE);
+ }
+
+ private static float[] temporalSampler(int orientationType, Gesture gesture) {
+ float[] pts = GestureUtilities.temporalSampling(gesture.getStrokes().get(0),
+ SEQUENCE_SAMPLE_SIZE);
+ float[] center = GestureUtilities.computeCentroid(pts);
+ float orientation = (float)Math.atan2(pts[1] - center[1], pts[0] - center[0]);
+
+ float adjustment = -orientation;
+ if (orientationType == GestureStore.ORIENTATION_SENSITIVE) {
+ int count = ORIENTATIONS.length;
+ for (int i = 0; i < count; i++) {
+ float delta = ORIENTATIONS[i] - orientation;
+ if (Math.abs(delta) < Math.abs(adjustment)) {
+ adjustment = delta;
+ }
+ }
+ }
+
+ GestureUtilities.translate(pts, -center[0], -center[1]);
+ GestureUtilities.rotate(pts, adjustment);
+
+ return pts;
+ }
+
+}
diff --git a/core/java/android/gesture/InstanceLearner.java b/core/java/android/gesture/InstanceLearner.java
new file mode 100644
index 0000000..b93b76f
--- /dev/null
+++ b/core/java/android/gesture/InstanceLearner.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.TreeMap;
+
+/**
+ * An implementation of an instance-based learner
+ */
+
+class InstanceLearner extends Learner {
+ private static final Comparator<Prediction> sComparator = new Comparator<Prediction>() {
+ public int compare(Prediction object1, Prediction object2) {
+ double score1 = object1.score;
+ double score2 = object2.score;
+ if (score1 > score2) {
+ return -1;
+ } else if (score1 < score2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ };
+
+ @Override
+ ArrayList<Prediction> classify(int sequenceType, float[] vector) {
+ ArrayList<Prediction> predictions = new ArrayList<Prediction>();
+ ArrayList<Instance> instances = getInstances();
+ int count = instances.size();
+ TreeMap<String, Double> label2score = new TreeMap<String, Double>();
+ for (int i = 0; i < count; i++) {
+ Instance sample = instances.get(i);
+ if (sample.vector.length != vector.length) {
+ continue;
+ }
+ double distance;
+ if (sequenceType == GestureStore.SEQUENCE_SENSITIVE) {
+ distance = GestureUtilities.cosineDistance(sample.vector, vector);
+ } else {
+ distance = GestureUtilities.squaredEuclideanDistance(sample.vector, vector);
+ }
+ double weight;
+ if (distance == 0) {
+ weight = Double.MAX_VALUE;
+ } else {
+ weight = 1 / distance;
+ }
+ Double score = label2score.get(sample.label);
+ if (score == null || weight > score) {
+ label2score.put(sample.label, weight);
+ }
+ }
+
+// double sum = 0;
+ for (String name : label2score.keySet()) {
+ double score = label2score.get(name);
+// sum += score;
+ predictions.add(new Prediction(name, score));
+ }
+
+ // normalize
+// for (Prediction prediction : predictions) {
+// prediction.score /= sum;
+// }
+
+ Collections.sort(predictions, sComparator);
+
+ return predictions;
+ }
+}
diff --git a/core/java/android/gesture/Learner.java b/core/java/android/gesture/Learner.java
new file mode 100755
index 0000000..feacde5
--- /dev/null
+++ b/core/java/android/gesture/Learner.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+import java.util.ArrayList;
+
+/**
+ * The abstract class of a gesture learner
+ */
+abstract class Learner {
+ private final ArrayList<Instance> mInstances = new ArrayList<Instance>();
+
+ /**
+ * Add an instance to the learner
+ *
+ * @param instance
+ */
+ void addInstance(Instance instance) {
+ mInstances.add(instance);
+ }
+
+ /**
+ * Retrieve all the instances
+ *
+ * @return instances
+ */
+ ArrayList<Instance> getInstances() {
+ return mInstances;
+ }
+
+ /**
+ * Remove an instance based on its id
+ *
+ * @param id
+ */
+ void removeInstance(long id) {
+ ArrayList<Instance> instances = mInstances;
+ int count = instances.size();
+ for (int i = 0; i < count; i++) {
+ Instance instance = instances.get(i);
+ if (id == instance.id) {
+ instances.remove(instance);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove all the instances of a category
+ *
+ * @param name the category name
+ */
+ void removeInstances(String name) {
+ final ArrayList<Instance> toDelete = new ArrayList<Instance>();
+ final ArrayList<Instance> instances = mInstances;
+ final int count = instances.size();
+
+ for (int i = 0; i < count; i++) {
+ final Instance instance = instances.get(i);
+ // the label can be null, as specified in Instance
+ if ((instance.label == null && name == null) || instance.label.equals(name)) {
+ toDelete.add(instance);
+ }
+ }
+ instances.removeAll(toDelete);
+ }
+
+ abstract ArrayList<Prediction> classify(int gestureType, float[] vector);
+}
diff --git a/core/java/android/gesture/OrientedBoundingBox.java b/core/java/android/gesture/OrientedBoundingBox.java
new file mode 100644
index 0000000..f1335ee
--- /dev/null
+++ b/core/java/android/gesture/OrientedBoundingBox.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+
+/**
+ * An oriented bounding box
+ */
+public class OrientedBoundingBox {
+ public final float squareness;
+
+ public final float width;
+ public final float height;
+
+ public final float orientation;
+
+ public final float centerX;
+ public final float centerY;
+
+ OrientedBoundingBox(float angle, float cx, float cy, float w, float h) {
+ orientation = angle;
+ width = w;
+ height = h;
+ centerX = cx;
+ centerY = cy;
+ float ratio = w / h;
+ if (ratio > 1) {
+ squareness = 1 / ratio;
+ } else {
+ squareness = ratio;
+ }
+ }
+
+ /**
+ * Currently used for debugging purpose only.
+ *
+ * @hide
+ */
+ public Path toPath() {
+ Path path = new Path();
+ float[] point = new float[2];
+ point[0] = -width / 2;
+ point[1] = height / 2;
+ Matrix matrix = new Matrix();
+ matrix.setRotate(orientation);
+ matrix.postTranslate(centerX, centerY);
+ matrix.mapPoints(point);
+ path.moveTo(point[0], point[1]);
+
+ point[0] = -width / 2;
+ point[1] = -height / 2;
+ matrix.mapPoints(point);
+ path.lineTo(point[0], point[1]);
+
+ point[0] = width / 2;
+ point[1] = -height / 2;
+ matrix.mapPoints(point);
+ path.lineTo(point[0], point[1]);
+
+ point[0] = width / 2;
+ point[1] = height / 2;
+ matrix.mapPoints(point);
+ path.lineTo(point[0], point[1]);
+
+ path.close();
+
+ return path;
+ }
+}
diff --git a/core/java/android/gesture/Prediction.java b/core/java/android/gesture/Prediction.java
new file mode 100755
index 0000000..ce6ad57
--- /dev/null
+++ b/core/java/android/gesture/Prediction.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008-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.gesture;
+
+public class Prediction {
+ public final String name;
+
+ public double score;
+
+ Prediction(String label, double predictionScore) {
+ name = label;
+ score = predictionScore;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+}
diff --git a/core/java/android/gesture/package.html b/core/java/android/gesture/package.html
new file mode 100644
index 0000000..a54a017
--- /dev/null
+++ b/core/java/android/gesture/package.html
@@ -0,0 +1,5 @@
+<HTML>
+<BODY>
+@hide
+</BODY>
+</HTML>
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index ca579b6..091bc17 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -39,13 +39,16 @@ import android.os.Message;
public class Camera {
private static final String TAG = "Camera";
- // These match the enum in libs/android_runtime/android_hardware_Camera.cpp
- private static final int SHUTTER_CALLBACK = 0;
- private static final int RAW_PICTURE_CALLBACK = 1;
- private static final int JPEG_PICTURE_CALLBACK = 2;
- private static final int PREVIEW_CALLBACK = 3;
- private static final int AUTOFOCUS_CALLBACK = 4;
- private static final int ERROR_CALLBACK = 5;
+ // These match the enums in frameworks/base/include/ui/Camera.h
+ private static final int CAMERA_MSG_ERROR = 0;
+ private static final int CAMERA_MSG_SHUTTER = 1;
+ private static final int CAMERA_MSG_FOCUS = 2;
+ private static final int CAMERA_MSG_ZOOM = 3;
+ private static final int CAMERA_MSG_PREVIEW_FRAME = 4;
+ private static final int CAMERA_MSG_VIDEO_FRAME = 5;
+ private static final int CAMERA_MSG_POSTVIEW_FRAME = 6;
+ private static final int CAMERA_MSG_RAW_IMAGE = 7;
+ private static final int CAMERA_MSG_COMPRESSED_IMAGE = 8;
private int mNativeContext; // accessed by native methods
private EventHandler mEventHandler;
@@ -152,7 +155,11 @@ public class Camera {
* @throws IOException if the method fails.
*/
public final void setPreviewDisplay(SurfaceHolder holder) throws IOException {
- setPreviewDisplay(holder.getSurface());
+ if (holder != null) {
+ setPreviewDisplay(holder.getSurface());
+ } else {
+ setPreviewDisplay((Surface)null);
+ }
}
private native final void setPreviewDisplay(Surface surface);
@@ -231,22 +238,23 @@ public class Camera {
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
- case SHUTTER_CALLBACK:
+ case CAMERA_MSG_SHUTTER:
if (mShutterCallback != null) {
mShutterCallback.onShutter();
}
return;
- case RAW_PICTURE_CALLBACK:
+
+ case CAMERA_MSG_RAW_IMAGE:
if (mRawImageCallback != null)
mRawImageCallback.onPictureTaken((byte[])msg.obj, mCamera);
return;
- case JPEG_PICTURE_CALLBACK:
+ case CAMERA_MSG_COMPRESSED_IMAGE:
if (mJpegCallback != null)
mJpegCallback.onPictureTaken((byte[])msg.obj, mCamera);
return;
- case PREVIEW_CALLBACK:
+ case CAMERA_MSG_PREVIEW_FRAME:
if (mPreviewCallback != null) {
mPreviewCallback.onPreviewFrame((byte[])msg.obj, mCamera);
if (mOneShot) {
@@ -255,12 +263,12 @@ public class Camera {
}
return;
- case AUTOFOCUS_CALLBACK:
+ case CAMERA_MSG_FOCUS:
if (mAutoFocusCallback != null)
mAutoFocusCallback.onAutoFocus(msg.arg1 == 0 ? false : true, mCamera);
return;
- case ERROR_CALLBACK:
+ case CAMERA_MSG_ERROR :
Log.e(TAG, "Error " + msg.arg1);
if (mErrorCallback != null)
mErrorCallback.onError(msg.arg1, mCamera);
@@ -363,7 +371,7 @@ public class Camera {
}
private native final void native_takePicture();
- // These match the enum in libs/android_runtime/android_hardware_Camera.cpp
+ // These match the enum in include/ui/Camera.h
/** Unspecified camerar error. @see #ErrorCallback */
public static final int CAMERA_ERROR_UNKNOWN = 1;
/** Media server died. In this case, the application must release the
diff --git a/core/java/android/hardware/ISensorService.aidl b/core/java/android/hardware/ISensorService.aidl
index 04af2ae..67180bd 100644
--- a/core/java/android/hardware/ISensorService.aidl
+++ b/core/java/android/hardware/ISensorService.aidl
@@ -17,13 +17,13 @@
package android.hardware;
-import android.os.ParcelFileDescriptor;
+import android.os.Bundle;
/**
* {@hide}
*/
interface ISensorService
{
- ParcelFileDescriptor getDataChanel();
+ Bundle getDataChannel();
boolean enableSensor(IBinder listener, String name, int sensor, int enable);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 67df23b..bf945ec 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -18,7 +18,9 @@ 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;
@@ -280,8 +282,8 @@ public class SensorManager
void startLocked(ISensorService service) {
try {
if (mThread == null) {
- ParcelFileDescriptor fd = service.getDataChanel();
- mThread = new Thread(new SensorThreadRunnable(fd),
+ Bundle dataChannel = service.getDataChannel();
+ mThread = new Thread(new SensorThreadRunnable(dataChannel),
SensorThread.class.getName());
mThread.start();
}
@@ -291,10 +293,52 @@ public class SensorManager
}
private class SensorThreadRunnable implements Runnable {
- private ParcelFileDescriptor mSensorDataFd;
- SensorThreadRunnable(ParcelFileDescriptor fd) {
- mSensorDataFd = fd;
+ private Bundle mDataChannel;
+ SensorThreadRunnable(Bundle dataChannel) {
+ mDataChannel = dataChannel;
}
+
+ private boolean open() {
+ if (mDataChannel == null) {
+ Log.e(TAG, "mDataChannel == NULL, exiting");
+ synchronized (sListeners) {
+ mThread = null;
+ }
+ return false;
+ }
+
+ // 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;
+ return true;
+ }
+
public void run() {
//Log.d(TAG, "entering main sensor thread");
final float[] values = new float[3];
@@ -302,23 +346,9 @@ public class SensorManager
final long timestamp[] = new long[1];
Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
- if (mSensorDataFd == null) {
- Log.e(TAG, "mSensorDataFd == NULL, exiting");
- synchronized (sListeners) {
- mThread = null;
- }
+ if (!open()) {
return;
}
- // this thread is guaranteed to be unique
- sensors_data_open(mSensorDataFd.getFileDescriptor());
- try {
- mSensorDataFd.close();
- } catch (IOException e) {
- // *shrug*
- Log.e(TAG, "IOException: ", e);
- }
- mSensorDataFd = null;
-
while (true) {
// wait for an event
@@ -1469,7 +1499,7 @@ public class SensorManager
// 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 fd);
+ 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);
}
diff --git a/core/java/android/net/http/RequestHandle.java b/core/java/android/net/http/RequestHandle.java
index c4ee5b0..6a97951 100644
--- a/core/java/android/net/http/RequestHandle.java
+++ b/core/java/android/net/http/RequestHandle.java
@@ -159,11 +159,11 @@ public class RequestHandle {
e.printStackTrace();
}
- // update the "cookie" header based on the redirected url
- mHeaders.remove("cookie");
+ // update the "Cookie" header based on the redirected url
+ mHeaders.remove("Cookie");
String cookie = CookieManager.getInstance().getCookie(mUri);
if (cookie != null && cookie.length() > 0) {
- mHeaders.put("cookie", cookie);
+ mHeaders.put("Cookie", cookie);
}
if ((statusCode == 302 || statusCode == 303) && mMethod.equals("POST")) {
diff --git a/core/java/android/os/AsyncTask.java b/core/java/android/os/AsyncTask.java
index 6c13582..abfb274 100644
--- a/core/java/android/os/AsyncTask.java
+++ b/core/java/android/os/AsyncTask.java
@@ -127,12 +127,12 @@ import java.util.concurrent.atomic.AtomicInteger;
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
- private static final int CORE_POOL_SIZE = 1;
- private static final int MAXIMUM_POOL_SIZE = 10;
+ private static final int CORE_POOL_SIZE = 5;
+ private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 10;
private static final BlockingQueue<Runnable> sWorkQueue =
- new LinkedBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE);
+ new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 8a0fd58..528def5 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -69,6 +69,20 @@ public abstract class BatteryStats implements Parcelable {
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;
@@ -164,7 +178,7 @@ public abstract class BatteryStats implements Parcelable {
* @return a time in microseconds
*/
public abstract long getTotalTimeLocked(long batteryRealtime, int which);
-
+
/**
* Temporary for debugging.
*/
@@ -234,11 +248,17 @@ public abstract class BatteryStats implements Parcelable {
public abstract void noteScanWifiLockReleasedLocked();
public abstract void noteWifiMulticastEnabledLocked();
public abstract void noteWifiMulticastDisabledLocked();
+ public abstract void noteAudioTurnedOnLocked();
+ public abstract void noteAudioTurnedOffLocked();
+ public abstract void noteVideoTurnedOnLocked();
+ public abstract void noteVideoTurnedOffLocked();
public abstract long getWifiTurnedOnTime(long batteryRealtime, int which);
public abstract long getFullWifiLockTime(long batteryRealtime, int which);
public abstract long getScanWifiLockTime(long batteryRealtime, int which);
public abstract long getWifiMulticastTime(long batteryRealtime,
int which);
+ public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
+ public abstract long getVideoTurnedOnTime(long batteryRealtime, int which);
/**
* Note that these must match the constants in android.os.LocalPowerManager.
@@ -287,6 +307,13 @@ public abstract class BatteryStats implements Parcelable {
* @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT.
*/
public abstract int getStarts(int which);
+
+ /**
+ * Returns the cpu time spent in microseconds while the process was in the foreground.
+ * @param which one of STATS_TOTAL, STATS_LAST, STATS_CURRENT or STATS_UNPLUGGED
+ * @return foreground cpu time in microseconds
+ */
+ public abstract long getForegroundTime(int which);
}
/**
@@ -344,7 +371,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getStartCount();
/**
- * Returns the time in milliseconds that the screen has been on while the device was
+ * Returns the time in microseconds that the screen has been on while the device was
* running on battery.
*
* {@hide}
@@ -364,7 +391,7 @@ public abstract class BatteryStats implements Parcelable {
public static final int NUM_SCREEN_BRIGHTNESS_BINS = 5;
/**
- * Returns the time in milliseconds that the screen has been on with
+ * Returns the time in microseconds that the screen has been on with
* the given brightness
*
* {@hide}
@@ -375,7 +402,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getInputEventCount(int which);
/**
- * Returns the time in milliseconds that the phone has been on while the device was
+ * Returns the time in microseconds that the phone has been on while the device was
* running on battery.
*
* {@hide}
@@ -395,7 +422,7 @@ public abstract class BatteryStats implements Parcelable {
public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
/**
- * Returns the time in milliseconds that the phone has been running with
+ * Returns the time in microseconds that the phone has been running with
* the given signal strength.
*
* {@hide}
@@ -423,7 +450,7 @@ public abstract class BatteryStats implements Parcelable {
public static final int NUM_DATA_CONNECTION_TYPES = 5;
/**
- * Returns the time in milliseconds that the phone has been running with
+ * Returns the time in microseconds that the phone has been running with
* the given data connection.
*
* {@hide}
@@ -440,7 +467,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getPhoneDataConnectionCount(int dataType, int which);
/**
- * Returns the time in milliseconds that wifi has been on while the device was
+ * Returns the time in microseconds that wifi has been on while the device was
* running on battery.
*
* {@hide}
@@ -448,7 +475,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getWifiOnTime(long batteryRealtime, int which);
/**
- * Returns the time in milliseconds that wifi has been on and the driver has
+ * Returns the time in microseconds that wifi has been on and the driver has
* been in the running state while the device was running on battery.
*
* {@hide}
@@ -456,7 +483,7 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getWifiRunningTime(long batteryRealtime, int which);
/**
- * Returns the time in milliseconds that bluetooth has been on while the device was
+ * Returns the time in microseconds that bluetooth has been on while the device was
* running on battery.
*
* {@hide}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 5487c54..830b0bd 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -38,6 +38,12 @@ public class Build {
/** The name of the underlying board, like "goldfish". */
public static final String BOARD = getString("ro.product.board");
+ /** The name of the instruction set (CPU type + ABI convention) of native code. */
+ public static final String CPU_ABI = getString("ro.product.cpu.abi");
+
+ /** The manufacturer of the product/hardware. */
+ public static final String MANUFACTURER = getString("ro.product.manufacturer");
+
/** The brand (e.g., carrier) the software is customized for, if any. */
public static final String BRAND = getString("ro.product.brand");
@@ -87,6 +93,12 @@ public class Build {
*/
public static class VERSION_CODES {
/**
+ * Magic version number for a current development build, which has
+ * not yet turned into an official release.
+ */
+ public static final int CUR_DEVELOPMENT = 10000;
+
+ /**
* October 2008: The original, first, version of Android. Yay!
*/
public static final int BASE = 1;
@@ -98,6 +110,19 @@ public class Build {
* May 2009: Android 1.5.
*/
public static final int CUPCAKE = 3;
+ /**
+ * Current work on "Donut" development branch.
+ *
+ * <p>Applications targeting this or a later release will get these
+ * new changes in behavior:</p>
+ * <ul>
+ * <li> They must explicitly request the
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permission to be
+ * able to modify the contents of the SD card. (Apps targeting
+ * earlier versions will always request the permission.)
+ * </ul>
+ */
+ public static final int DONUT = CUR_DEVELOPMENT;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index b669fa2..a91655f 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -78,6 +78,10 @@ public final class Bundle implements Parcelable, Cloneable {
readFromParcel(parcelledData);
}
+ /* package */ Bundle(Parcel parcelledData, int length) {
+ readFromParcelInner(parcelledData, length);
+ }
+
/**
* Constructs a new, empty Bundle that uses a specific ClassLoader for
* instantiating Parcelable and Serializable objects.
@@ -155,13 +159,14 @@ public final class Bundle implements Parcelable, Cloneable {
return;
}
- mParcelledData.setDataPosition(0);
- Bundle b = mParcelledData.readBundleUnpacked(mClassLoader);
- mMap = b.mMap;
-
- mHasFds = mParcelledData.hasFileDescriptors();
- mFdsKnown = true;
-
+ int N = mParcelledData.readInt();
+ if (N < 0) {
+ return;
+ }
+ if (mMap == null) {
+ mMap = new HashMap<String, Object>();
+ }
+ mParcelledData.readMapInternal(mMap, N, mClassLoader);
mParcelledData.recycle();
mParcelledData = null;
}
@@ -1427,7 +1432,25 @@ public final class Bundle implements Parcelable, Cloneable {
* @param parcel The parcel to copy this bundle to.
*/
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeBundle(this);
+ if (mParcelledData != null) {
+ int length = mParcelledData.dataSize();
+ parcel.writeInt(length);
+ parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
+ parcel.appendFrom(mParcelledData, 0, length);
+ } else {
+ parcel.writeInt(-1); // dummy, will hold length
+ parcel.writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
+
+ int oldPos = parcel.dataPosition();
+ parcel.writeMapInternal(mMap);
+ int newPos = parcel.dataPosition();
+
+ // Backpatch length
+ parcel.setDataPosition(oldPos - 8);
+ int length = newPos - oldPos;
+ parcel.writeInt(length);
+ parcel.setDataPosition(newPos);
+ }
}
/**
@@ -1436,8 +1459,33 @@ public final class Bundle implements Parcelable, Cloneable {
* @param parcel The parcel to overwrite this bundle from.
*/
public void readFromParcel(Parcel parcel) {
- mParcelledData = parcel;
- mHasFds = mParcelledData.hasFileDescriptors();
+ int length = parcel.readInt();
+ if (length < 0) {
+ throw new RuntimeException("Bad length in parcel: " + length);
+ }
+ readFromParcelInner(parcel, length);
+ }
+
+ void readFromParcelInner(Parcel parcel, int length) {
+ int magic = parcel.readInt();
+ if (magic != 0x4C444E42) {
+ //noinspection ThrowableInstanceNeverThrown
+ String st = Log.getStackTraceString(new RuntimeException());
+ Log.e("Bundle", "readBundle: bad magic number");
+ Log.e("Bundle", "readBundle: trace = " + st);
+ }
+
+ // Advance within this Parcel
+ int offset = parcel.dataPosition();
+ parcel.setDataPosition(offset + length);
+
+ Parcel p = Parcel.obtain();
+ p.setDataPosition(0);
+ p.appendFrom(parcel, offset, length);
+ p.setDataPosition(0);
+
+ mParcelledData = p;
+ mHasFds = p.hasFileDescriptors();
mFdsKnown = true;
}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 8fcb4d7..d40ea6b 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -21,6 +21,7 @@ import com.android.internal.util.TypedProperties;
import android.util.Config;
import android.util.Log;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
@@ -378,6 +379,20 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
+ * Like startMethodTracing(String, int, int), but taking an already-opened
+ * FileDescriptor in which the trace is written. The file name is also
+ * supplied simply for logging. Makes a dup of the file descriptor.
+ *
+ * Not exposed in the SDK unless we are really comfortable with supporting
+ * this and find it would be useful.
+ * @hide
+ */
+ public static void startMethodTracing(String traceName, FileDescriptor fd,
+ int bufferSize, int flags) {
+ VMDebug.startMethodTracing(traceName, fd, bufferSize, flags);
+ }
+
+ /**
* Determine whether method tracing is currently active.
* @hide
*/
diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java
index 76e4f47..c14925c 100644
--- a/core/java/android/os/MemoryFile.java
+++ b/core/java/android/os/MemoryFile.java
@@ -18,6 +18,7 @@ package android.os;
import android.util.Log;
+import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -35,48 +36,120 @@ import java.io.OutputStream;
public class MemoryFile
{
private static String TAG = "MemoryFile";
-
- // returns fd
- private native int native_open(String name, int length);
+
+ // mmap(2) protection flags from <sys/mman.h>
+ private static final int PROT_READ = 0x1;
+ private static final int PROT_WRITE = 0x2;
+
+ private static native FileDescriptor native_open(String name, int length) throws IOException;
// returns memory address for ashmem region
- private native int native_mmap(int fd, int length);
- private native void native_close(int fd);
- private native int native_read(int fd, int address, byte[] buffer,
- int srcOffset, int destOffset, int count, boolean isUnpinned);
- private native void native_write(int fd, int address, byte[] buffer,
- int srcOffset, int destOffset, int count, boolean isUnpinned);
- private native void native_pin(int fd, boolean pin);
-
- private int mFD; // ashmem file descriptor
+ private static native int native_mmap(FileDescriptor fd, int length, int mode)
+ throws IOException;
+ private static native void native_munmap(int addr, int length) throws IOException;
+ private static native void native_close(FileDescriptor fd);
+ private static native int native_read(FileDescriptor fd, int address, byte[] buffer,
+ int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
+ private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
+ int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
+ private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
+ private static native boolean native_is_ashmem_region(FileDescriptor fd) throws IOException;
+
+ private FileDescriptor mFD; // ashmem file descriptor
private int mAddress; // address of ashmem memory
private int mLength; // total length of our ashmem region
private boolean mAllowPurging = false; // true if our ashmem region is unpinned
+ private final boolean mOwnsRegion; // false if this is a ref to an existing ashmem region
/**
- * MemoryFile constructor.
+ * Allocates a new ashmem region. The region is initially not purgable.
*
* @param name optional name for the file (can be null).
* @param length of the memory file in bytes.
+ * @throws IOException if the memory file could not be created.
*/
- public MemoryFile(String name, int length) {
+ public MemoryFile(String name, int length) throws IOException {
mLength = length;
mFD = native_open(name, length);
- mAddress = native_mmap(mFD, length);
+ mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
+ mOwnsRegion = true;
}
/**
- * Closes and releases all resources for the memory file.
+ * Creates a reference to an existing memory file. Changes to the original file
+ * will be available through this reference.
+ * Calls to {@link #allowPurging(boolean)} on the returned MemoryFile will fail.
+ *
+ * @param fd File descriptor for an existing memory file, as returned by
+ * {@link #getFileDescriptor()}. This file descriptor will be closed
+ * by {@link #close()}.
+ * @param length Length of the memory file in bytes.
+ * @param mode File mode. Currently only "r" for read-only access is supported.
+ * @throws NullPointerException if <code>fd</code> is null.
+ * @throws IOException If <code>fd</code> does not refer to an existing memory file,
+ * or if the file mode of the existing memory file is more restrictive
+ * than <code>mode</code>.
+ *
+ * @hide
+ */
+ public MemoryFile(FileDescriptor fd, int length, String mode) throws IOException {
+ if (fd == null) {
+ throw new NullPointerException("File descriptor is null.");
+ }
+ if (!isMemoryFile(fd)) {
+ throw new IllegalArgumentException("Not a memory file.");
+ }
+ mLength = length;
+ mFD = fd;
+ mAddress = native_mmap(mFD, length, modeToProt(mode));
+ mOwnsRegion = false;
+ }
+
+ /**
+ * Closes the memory file. If there are no other open references to the memory
+ * file, it will be deleted.
*/
public void close() {
- if (mFD > 0) {
+ deactivate();
+ if (!isClosed()) {
native_close(mFD);
- mFD = 0;
}
}
+ /**
+ * Unmaps the memory file from the process's memory space, but does not close it.
+ * After this method has been called, read and write operations through this object
+ * will fail, but {@link #getFileDescriptor()} will still return a valid file descriptor.
+ *
+ * @hide
+ */
+ public void deactivate() {
+ if (!isDeactivated()) {
+ try {
+ native_munmap(mAddress, mLength);
+ mAddress = 0;
+ } catch (IOException ex) {
+ Log.e(TAG, ex.toString());
+ }
+ }
+ }
+
+ /**
+ * Checks whether the memory file has been deactivated.
+ */
+ private boolean isDeactivated() {
+ return mAddress == 0;
+ }
+
+ /**
+ * Checks whether the memory file has been closed.
+ */
+ private boolean isClosed() {
+ return !mFD.valid();
+ }
+
@Override
protected void finalize() {
- if (mFD > 0) {
+ if (!isClosed()) {
Log.e(TAG, "MemoryFile.finalize() called while ashmem still open");
close();
}
@@ -108,6 +181,9 @@ public class MemoryFile
* @return previous value of allowPurging
*/
synchronized public boolean allowPurging(boolean allowPurging) throws IOException {
+ if (!mOwnsRegion) {
+ throw new IOException("Only the owner can make ashmem regions purgable.");
+ }
boolean oldValue = mAllowPurging;
if (oldValue != allowPurging) {
native_pin(mFD, !allowPurging);
@@ -131,7 +207,6 @@ public class MemoryFile
@return OutputStream
*/
public OutputStream getOutputStream() {
-
return new MemoryOutputStream();
}
@@ -144,9 +219,13 @@ public class MemoryFile
* @param destOffset offset into the byte array buffer to read into.
* @param count number of bytes to read.
* @return number of bytes read.
+ * @throws IOException if the memory file has been purged or deactivated.
*/
public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
+ if (isDeactivated()) {
+ throw new IOException("Can't read from deactivated memory file.");
+ }
if (destOffset < 0 || destOffset > buffer.length || count < 0
|| count > buffer.length - destOffset
|| srcOffset < 0 || srcOffset > mLength
@@ -164,9 +243,13 @@ public class MemoryFile
* @param srcOffset offset into the byte array buffer to write from.
* @param destOffset offset into the memory file to write to.
* @param count number of bytes to write.
+ * @throws IOException if the memory file has been purged or deactivated.
*/
public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
+ if (isDeactivated()) {
+ throw new IOException("Can't write to deactivated memory file.");
+ }
if (srcOffset < 0 || srcOffset > buffer.length || count < 0
|| count > buffer.length - srcOffset
|| destOffset < 0 || destOffset > mLength
@@ -176,6 +259,64 @@ public class MemoryFile
native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
}
+ /**
+ * Gets a ParcelFileDescriptor for the memory file. See {@link #getFileDescriptor()}
+ * for caveats. This must be here to allow classes outside <code>android.os</code< to
+ * make ParcelFileDescriptors from MemoryFiles, as
+ * {@link ParcelFileDescriptor#ParcelFileDescriptor(FileDescriptor)} is package private.
+ *
+ *
+ * @return The file descriptor owned by this memory file object.
+ * The file descriptor is not duplicated.
+ * @throws IOException If the memory file has been closed.
+ *
+ * @hide
+ */
+ public ParcelFileDescriptor getParcelFileDescriptor() throws IOException {
+ return new ParcelFileDescriptor(getFileDescriptor());
+ }
+
+ /**
+ * Gets a FileDescriptor for the memory file. Note that this file descriptor
+ * is only safe to pass to {@link #MemoryFile(FileDescriptor,int)}). It
+ * should not be used with file descriptor operations that expect a file descriptor
+ * for a normal file.
+ *
+ * The returned file descriptor is not duplicated.
+ *
+ * @throws IOException If the memory file has been closed.
+ *
+ * @hide
+ */
+ public FileDescriptor getFileDescriptor() throws IOException {
+ return mFD;
+ }
+
+ /**
+ * Checks whether the given file descriptor refers to a memory file.
+ *
+ * @throws IOException If <code>fd</code> is not a valid file descriptor.
+ *
+ * @hide
+ */
+ public static boolean isMemoryFile(FileDescriptor fd) throws IOException {
+ return native_is_ashmem_region(fd);
+ }
+
+ /**
+ * Converts a file mode string to a <code>prot</code> value as expected by
+ * native_mmap().
+ *
+ * @throws IllegalArgumentException if the file mode is invalid.
+ */
+ private static int modeToProt(String mode) {
+ if ("r".equals(mode)) {
+ return PROT_READ;
+ } else {
+ throw new IllegalArgumentException("Unsupported file mode: '" + mode + "'");
+ }
+ }
+
private class MemoryInputStream extends InputStream {
private int mMark = 0;
@@ -212,13 +353,22 @@ public class MemoryFile
}
int result = read(mSingleByte, 0, 1);
if (result != 1) {
- throw new IOException("read() failed");
+ return -1;
}
return mSingleByte[0];
}
@Override
public int read(byte buffer[], int offset, int count) throws IOException {
+ if (offset < 0 || count < 0 || offset + count > buffer.length) {
+ // readBytes() also does this check, but we need to do it before
+ // changing count.
+ throw new IndexOutOfBoundsException();
+ }
+ count = Math.min(count, available());
+ if (count < 1) {
+ return -1;
+ }
int result = readBytes(buffer, mOffset, offset, count);
if (result > 0) {
mOffset += result;
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 9a71f6e..6cfccee 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -457,7 +457,7 @@ public final class Parcel {
* Flatten a Map into the parcel at the current dataPosition(),
* growing dataCapacity() if needed. The Map keys must be String objects.
*/
- private void writeMapInternal(Map<String,Object> val) {
+ /* package */ void writeMapInternal(Map<String,Object> val) {
if (val == null) {
writeInt(-1);
return;
@@ -480,23 +480,7 @@ public final class Parcel {
return;
}
- if (val.mParcelledData != null) {
- int length = val.mParcelledData.dataSize();
- appendFrom(val.mParcelledData, 0, length);
- } else {
- writeInt(-1); // dummy, will hold length
- int oldPos = dataPosition();
- writeInt(0x4C444E42); // 'B' 'N' 'D' 'L'
-
- writeMapInternal(val.mMap);
- int newPos = dataPosition();
-
- // Backpatch length
- setDataPosition(oldPos - 4);
- int length = newPos - oldPos;
- writeInt(length);
- setDataPosition(newPos);
- }
+ val.writeToParcel(this, 0);
}
/**
@@ -1352,27 +1336,12 @@ public final class Parcel {
* Returns null if the previously written Bundle object was null.
*/
public final Bundle readBundle(ClassLoader loader) {
- int offset = dataPosition();
int length = readInt();
if (length < 0) {
return null;
}
- int magic = readInt();
- if (magic != 0x4C444E42) {
- //noinspection ThrowableInstanceNeverThrown
- String st = Log.getStackTraceString(new RuntimeException());
- Log.e("Bundle", "readBundle: bad magic number");
- Log.e("Bundle", "readBundle: trace = " + st);
- }
-
- // Advance within this Parcel
- setDataPosition(offset + length + 4);
-
- Parcel p = new Parcel(0);
- p.setDataPosition(0);
- p.appendFrom(this, offset, length + 4);
- p.setDataPosition(0);
- final Bundle bundle = new Bundle(p);
+
+ final Bundle bundle = new Bundle(this, length);
if (loader != null) {
bundle.setClassLoader(loader);
}
@@ -1380,33 +1349,6 @@ public final class Parcel {
}
/**
- * Read and return a new Bundle object from the parcel at the current
- * dataPosition(). Returns null if the previously written Bundle object was
- * null. The returned bundle will have its contents fully unpacked using
- * the given ClassLoader.
- */
- /* package */ Bundle readBundleUnpacked(ClassLoader loader) {
- int length = readInt();
- if (length == -1) {
- return null;
- }
- int magic = readInt();
- if (magic != 0x4C444E42) {
- //noinspection ThrowableInstanceNeverThrown
- String st = Log.getStackTraceString(new RuntimeException());
- Log.e("Bundle", "readBundleUnpacked: bad magic number");
- Log.e("Bundle", "readBundleUnpacked: trace = " + st);
- }
- Bundle m = new Bundle(loader);
- int N = readInt();
- if (N < 0) {
- return null;
- }
- readMapInternal(m.mMap, N, loader);
- return m;
- }
-
- /**
* Read and return a byte[] object from the parcel.
*/
public final native byte[] createByteArray();
@@ -1998,7 +1940,7 @@ public final class Parcel {
private native void init(int obj);
private native void destroy();
- private void readMapInternal(Map outVal, int N,
+ /* package */ void readMapInternal(Map outVal, int N,
ClassLoader loader) {
while (N > 0) {
Object key = readValue(loader);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 30acef9..1214abc 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -573,7 +573,21 @@ public class Process {
* directly to a gid.
*/
public static final native int getGidForName(String name);
-
+
+ /**
+ * Returns a uid for a currently running process.
+ * @param pid the process id
+ * @return the uid of the process, or -1 if the process is not running.
+ * @hide pending API council review
+ */
+ public static final int getUidForPid(int pid) {
+ String[] procStatusLabels = { "Uid:" };
+ long[] procStatusValues = new long[1];
+ procStatusValues[0] = -1;
+ Process.readProcLines("/proc/" + pid + "/status", procStatusLabels, procStatusValues);
+ return (int) procStatusValues[0];
+ }
+
/**
* Set the priority of a thread, based on Linux priorities.
*
@@ -604,6 +618,20 @@ public class Process {
*/
public static final native void setThreadGroup(int tid, int group)
throws IllegalArgumentException, SecurityException;
+ /**
+ * Sets the scheduling group for a process and all child threads
+ * @hide
+ * @param pid The indentifier of the process to change.
+ * @param group The target group for this process.
+ *
+ * @throws IllegalArgumentException Throws IllegalArgumentException if
+ * <var>tid</var> does not exist.
+ * @throws SecurityException Throws SecurityException if your process does
+ * not have permission to modify the given thread, or to use the given
+ * priority.
+ */
+ public static final native void setProcessGroup(int pid, int group)
+ throws IllegalArgumentException, SecurityException;
/**
* Set the priority of the calling thread, based on Linux priorities. See
diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java
index edf69ee..3ea9b4a 100644
--- a/core/java/android/pim/EventRecurrence.java
+++ b/core/java/android/pim/EventRecurrence.java
@@ -408,13 +408,13 @@ public class EventRecurrence
private String dayToString(Resources r, int day) {
switch (day) {
- case SU: return r.getString(com.android.internal.R.string.sunday);
- case MO: return r.getString(com.android.internal.R.string.monday);
- case TU: return r.getString(com.android.internal.R.string.tuesday);
- case WE: return r.getString(com.android.internal.R.string.wednesday);
- case TH: return r.getString(com.android.internal.R.string.thursday);
- case FR: return r.getString(com.android.internal.R.string.friday);
- case SA: return r.getString(com.android.internal.R.string.saturday);
+ case SU: return r.getString(com.android.internal.R.string.day_of_week_long_sunday);
+ case MO: return r.getString(com.android.internal.R.string.day_of_week_long_monday);
+ case TU: return r.getString(com.android.internal.R.string.day_of_week_long_tuesday);
+ case WE: return r.getString(com.android.internal.R.string.day_of_week_long_wednesday);
+ case TH: return r.getString(com.android.internal.R.string.day_of_week_long_thursday);
+ case FR: return r.getString(com.android.internal.R.string.day_of_week_long_friday);
+ case SA: return r.getString(com.android.internal.R.string.day_of_week_long_saturday);
default: throw new IllegalArgumentException("bad day argument: " + day);
}
}
diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java
index 1e9b7ae..cf5664c 100644
--- a/core/java/android/preference/CheckBoxPreference.java
+++ b/core/java/android/preference/CheckBoxPreference.java
@@ -16,6 +16,7 @@
package android.preference;
+import android.app.Service;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
@@ -23,6 +24,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.Checkable;
import android.widget.TextView;
@@ -42,6 +45,9 @@ public class CheckBoxPreference extends Preference {
private CharSequence mSummaryOff;
private boolean mChecked;
+ private boolean mSendAccessibilityEventViewClickedType;
+
+ private AccessibilityManager mAccessibilityManager;
private boolean mDisableDependentsState;
@@ -55,6 +61,9 @@ public class CheckBoxPreference extends Preference {
mDisableDependentsState = a.getBoolean(
com.android.internal.R.styleable.CheckBoxPreference_disableDependentsState, false);
a.recycle();
+
+ mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
}
public CheckBoxPreference(Context context, AttributeSet attrs) {
@@ -64,14 +73,26 @@ public class CheckBoxPreference extends Preference {
public CheckBoxPreference(Context context) {
this(context, null);
}
-
+
@Override
protected void onBindView(View view) {
super.onBindView(view);
-
+
View checkboxView = view.findViewById(com.android.internal.R.id.checkbox);
if (checkboxView != null && checkboxView instanceof Checkable) {
((Checkable) checkboxView).setChecked(mChecked);
+
+ // send an event to announce the value change of the CheckBox and is done here
+ // because clicking a preference does not immediately change the checked state
+ // for example when enabling the WiFi
+ if (mSendAccessibilityEventViewClickedType &&
+ mAccessibilityManager.isEnabled() &&
+ checkboxView.isEnabled()) {
+ mSendAccessibilityEventViewClickedType = false;
+
+ int eventType = AccessibilityEvent.TYPE_VIEW_CLICKED;
+ checkboxView.sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
+ }
}
// Sync the summary view
@@ -85,7 +106,7 @@ public class CheckBoxPreference extends Preference {
summaryView.setText(mSummaryOff);
useDefaultSummary = false;
}
-
+
if (useDefaultSummary) {
final CharSequence summary = getSummary();
if (summary != null) {
@@ -111,6 +132,10 @@ public class CheckBoxPreference extends Preference {
boolean newValue = !isChecked();
+ // in onBindView() an AccessibilityEventViewClickedType is sent to announce the change
+ // not sending
+ mSendAccessibilityEventViewClickedType = true;
+
if (!callChangeListener(newValue)) {
return;
}
@@ -124,10 +149,11 @@ public class CheckBoxPreference extends Preference {
* @param checked The checked state.
*/
public void setChecked(boolean checked) {
+
mChecked = checked;
persistBoolean(checked);
-
+
notifyDependencyChange(shouldDisableDependents());
notifyChanged();
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
index 5353b53..95e5432 100644
--- a/core/java/android/preference/PreferenceScreen.java
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -22,6 +22,7 @@ import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Adapter;
@@ -147,13 +148,20 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi
ListView listView = new ListView(context);
bind(listView);
- Dialog dialog = mDialog = new Dialog(context, com.android.internal.R.style.Theme_NoTitleBar);
+ // Set the title bar if title is available, else no title bar
+ final CharSequence title = getTitle();
+ Dialog dialog = mDialog = new Dialog(context, TextUtils.isEmpty(title)
+ ? com.android.internal.R.style.Theme_NoTitleBar
+ : com.android.internal.R.style.Theme);
dialog.setContentView(listView);
+ if (!TextUtils.isEmpty(title)) {
+ dialog.setTitle(title);
+ }
dialog.setOnDismissListener(this);
if (state != null) {
dialog.onRestoreInstanceState(state);
}
-
+
// Add the screen to the list of preferences screens opened as dialogs
getPreferenceManager().addPreferencesScreen(dialog);
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index c597b3c..1ba5e25 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -34,6 +34,12 @@ public class Browser {
Uri.parse("content://browser/bookmarks");
/**
+ * The inline scheme to show embedded content in a browser.
+ * @hide
+ */
+ public static final Uri INLINE_URI = Uri.parse("inline:");
+
+ /**
* The name of extra data when starting Browser with ACTION_VIEW or
* ACTION_SEARCH intent.
* <p>
@@ -53,7 +59,48 @@ public class Browser {
* identifier.
*/
public static final String EXTRA_APPLICATION_ID =
- "com.android.browser.application_id";
+ "com.android.browser.application_id";
+
+ /**
+ * The content to be rendered when url's scheme is inline.
+ * @hide
+ */
+ public static final String EXTRA_INLINE_CONTENT ="com.android.browser.inline.content";
+
+ /**
+ * The encoding of the inlined content for inline scheme.
+ * @hide
+ */
+ public static final String EXTRA_INLINE_ENCODING ="com.android.browser.inline.encoding";
+
+ /**
+ * The url used when the inline content is falied to render.
+ * @hide
+ */
+ public static final String EXTRA_INLINE_FAILURL ="com.android.browser.inline.failurl";
+
+ /**
+ * The name of the extra data in the VIEW intent. The data is in boolean.
+ * <p>
+ * If the Browser is handling the intent and the setting for
+ * USE_LOCATION_FOR_SERVICES is allow, the Browser will send the location in
+ * the POST data if this extra data is presented and it is true.
+ * <p>
+ * pending api approval
+ * @hide
+ */
+ public static final String EXTRA_APPEND_LOCATION = "com.android.browser.append_location";
+
+ /**
+ * The name of the extra data in the VIEW intent. The data is in the format of
+ * a byte array.
+ * <p>
+ * Any value sent here will be passed in the http request to the provided url as post data.
+ * <p>
+ * pending api approval
+ * @hide
+ */
+ public static final String EXTRA_POST_DATA = "com.android.browser.post_data";
/* if you change column order you must also change indices
below */
@@ -132,6 +179,7 @@ public class Browser {
/**
* Return a cursor pointing to a list of all the bookmarks.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
*/
public static final Cursor getAllBookmarks(ContentResolver cr) throws
@@ -143,6 +191,7 @@ public class Browser {
/**
* Return a cursor pointing to a list of all visited site urls.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
*/
public static final Cursor getAllVisitedUrls(ContentResolver cr) throws
@@ -154,6 +203,8 @@ public class Browser {
/**
* Update the visited history to acknowledge that a site has been
* visited.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param url The site being visited.
* @param real Whether this is an actual visit, and should be added to the
@@ -203,6 +254,8 @@ public class Browser {
* of them. This is used to keep our history table to a
* reasonable size. Note: it does not prune bookmarks. If the
* user wants 1000 bookmarks, the user gets 1000 bookmarks.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
*
* @param cr The ContentResolver used to access the database.
*/
@@ -236,6 +289,7 @@ public class Browser {
/**
* Returns whether there is any history to clear.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @return boolean True if the history can be cleared.
*/
@@ -261,6 +315,7 @@ public class Browser {
/**
* Delete all entries from the bookmarks/history table which are
* not bookmarks. Also set all visited bookmarks to unvisited.
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
*/
public static final void clearHistory(ContentResolver cr) {
@@ -270,6 +325,8 @@ public class Browser {
/**
* Helper function to delete all history items and revert all
* bookmarks to zero visits which meet the criteria provided.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param whereClause String to limit the items affected.
* null means all items.
@@ -332,6 +389,7 @@ public class Browser {
/**
* Delete all history items from begin to end.
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param begin First date to remove. If -1, all dates before end.
* Inclusive.
@@ -359,6 +417,7 @@ public class Browser {
/**
* Remove a specific url from the history database.
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param url url to remove.
*/
@@ -372,6 +431,8 @@ public class Browser {
/**
* Add a search string to the searches database.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param search The string to add to the searches database.
*/
@@ -401,6 +462,7 @@ public class Browser {
}
/**
* Remove all searches from the search database.
+ * Requires {@link android.Manifest.permission#WRITE_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
*/
public static final void clearSearches(ContentResolver cr) {
@@ -415,6 +477,7 @@ public class Browser {
/**
* Request all icons from the database.
+ * Requires {@link android.Manifest.permission#READ_HISTORY_BOOKMARKS}
* @param cr The ContentResolver used to access the database.
* @param where Clause to be used to limit the query from the database.
* Must be an allowable string to be passed into a database query.
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index abd6934..7d03801 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -151,6 +151,9 @@ public class CallLog {
int presentation, int callType, long start, int duration) {
final ContentResolver resolver = context.getContentResolver();
+ // TODO(Moto): Which is correct: original code, this only changes the
+ // number if the number is empty and never changes the caller info name.
+ if (false) {
if (TextUtils.isEmpty(number)) {
if (presentation == Connection.PRESENTATION_RESTRICTED) {
number = CallerInfo.PRIVATE_NUMBER;
@@ -160,7 +163,22 @@ public class CallLog {
number = CallerInfo.UNKNOWN_NUMBER;
}
}
-
+ } else {
+ // NEWCODE: From Motorola
+
+ //If this is a private number then set the number to Private, otherwise check
+ //if the number field is empty and set the number to Unavailable
+ if (presentation == Connection.PRESENTATION_RESTRICTED) {
+ number = CallerInfo.PRIVATE_NUMBER;
+ ci.name = "";
+ } else if (presentation == Connection.PRESENTATION_PAYPHONE) {
+ number = CallerInfo.PAYPHONE_NUMBER;
+ ci.name = "";
+ } else if (TextUtils.isEmpty(number) || presentation == Connection.PRESENTATION_UNKNOWN) {
+ number = CallerInfo.UNKNOWN_NUMBER;
+ ci.name = "";
+ }
+ }
ContentValues values = new ContentValues(5);
values.put(NUMBER, number);
diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java
index 3c23db0..f2c275e 100644
--- a/core/java/android/provider/Checkin.java
+++ b/core/java/android/provider/Checkin.java
@@ -137,6 +137,8 @@ public final class Checkin {
CRASHES_TRUNCATED,
ELAPSED_REALTIME_SEC,
ELAPSED_UPTIME_SEC,
+ HTTP_REQUEST,
+ HTTP_REUSED,
HTTP_STATUS,
PHONE_GSM_REGISTERED,
PHONE_GPRS_ATTEMPTED,
@@ -351,6 +353,3 @@ public final class Checkin {
}
}
}
-
-
-
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index 3141f1a..84fe184 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -340,27 +340,33 @@ public class Contacts {
}
/**
- * Adds a person to the My Contacts group.
- *
- * @param resolver the resolver to use
- * @param personId the person to add to the group
- * @return the URI of the group membership row
- * @throws IllegalStateException if the My Contacts group can't be found
+ * @hide Used in vCard parser code.
*/
- public static Uri addToMyContactsGroup(ContentResolver resolver, long personId) {
- long groupId = 0;
+ public static long tryGetMyContactsGroupId(ContentResolver resolver) {
Cursor groupsCursor = resolver.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null);
if (groupsCursor != null) {
try {
if (groupsCursor.moveToFirst()) {
- groupId = groupsCursor.getLong(0);
+ return groupsCursor.getLong(0);
}
} finally {
groupsCursor.close();
}
}
+ return 0;
+ }
+ /**
+ * Adds a person to the My Contacts group.
+ *
+ * @param resolver the resolver to use
+ * @param personId the person to add to the group
+ * @return the URI of the group membership row
+ * @throws IllegalStateException if the My Contacts group can't be found
+ */
+ public static Uri addToMyContactsGroup(ContentResolver resolver, long personId) {
+ long groupId = tryGetMyContactsGroupId(resolver);
if (groupId == 0) {
throw new IllegalStateException("Failed to find the My Contacts group");
}
@@ -869,6 +875,17 @@ public class Contacts {
public static final int TYPE_OTHER = 3;
/**
+ * @hide This is temporal. TYPE_MOBILE should be added to TYPE in the future.
+ */
+ public static final int MOBILE_EMAIL_TYPE_INDEX = 2;
+
+ /**
+ * @hide This is temporal. TYPE_MOBILE should be added to TYPE in the future.
+ * This is not "mobile" but "CELL" since vCard uses it for identifying mobile phone.
+ */
+ public static final String MOBILE_EMAIL_TYPE_NAME = "_AUTO_CELL";
+
+ /**
* The user defined label for the the contact method.
* <P>Type: TEXT</P>
*/
@@ -1005,7 +1022,13 @@ public class Contacts {
}
} else {
if (!TextUtils.isEmpty(label)) {
- display = label;
+ if (label.toString().equals(MOBILE_EMAIL_TYPE_NAME)) {
+ display =
+ context.getString(
+ com.android.internal.R.string.mobileEmailTypeName);
+ } else {
+ display = label;
+ }
}
}
break;
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index b6f96c4..21e5865 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -344,7 +344,10 @@ public final class MediaStore
// Check if file exists with a FileInputStream
FileInputStream stream = new FileInputStream(imagePath);
try {
- return insertImage(cr, BitmapFactory.decodeFile(imagePath), name, description);
+ Bitmap bm = BitmapFactory.decodeFile(imagePath);
+ String ret = insertImage(cr, bm, name, description);
+ bm.recycle();
+ return ret;
} finally {
try {
stream.close();
@@ -719,9 +722,15 @@ public final class MediaStore
*/
public static String keyFor(String name) {
if (name != null) {
+ boolean sortfirst = false;
if (name.equals(android.media.MediaFile.UNKNOWN_STRING)) {
return "\001";
}
+ // Check if the first character is \001. We use this to
+ // force sorting of certain special files, like the silent ringtone.
+ if (name.startsWith("\001")) {
+ sortfirst = true;
+ }
name = name.trim().toLowerCase();
if (name.startsWith("the ")) {
name = name.substring(4);
@@ -737,7 +746,7 @@ public final class MediaStore
name.endsWith(", a") || name.endsWith(",a")) {
name = name.substring(0, name.lastIndexOf(','));
}
- name = name.replaceAll("[\\[\\]\\(\\)'.,?!]", "").trim();
+ name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
if (name.length() > 0) {
// Insert a separator between the characters to avoid
// matches on a partial character. If we ever change
@@ -750,7 +759,11 @@ public final class MediaStore
b.append('.');
}
name = b.toString();
- return DatabaseUtils.getCollationKey(name);
+ String key = DatabaseUtils.getCollationKey(name);
+ if (sortfirst) {
+ key = "\001" + key;
+ }
+ return key;
} else {
return "";
}
@@ -797,7 +810,7 @@ public final class MediaStore
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = TITLE;
+ public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
/**
* Activity Action: Start SoundRecorder application.
@@ -894,7 +907,7 @@ public final class MediaStore
/**
* The default sort order for this table
*/
- public static final String DEFAULT_SORT_ORDER = TITLE;
+ public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
/**
* The ID of the audio file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4dd6524..aa583ac 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -148,7 +148,7 @@ public final class Settings {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WIFI_SETTINGS =
"android.settings.WIFI_SETTINGS";
-
+
/**
* Activity Action: Show settings to allow configuration of a static IP
* address for Wi-Fi.
@@ -305,7 +305,7 @@ public final class Settings {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_QUICK_LAUNCH_SETTINGS =
"android.settings.QUICK_LAUNCH_SETTINGS";
-
+
/**
* Activity Action: Show settings to manage installed applications.
* <p>
@@ -319,7 +319,7 @@ public final class Settings {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS =
"android.settings.MANAGE_APPLICATIONS_SETTINGS";
-
+
/**
* Activity Action: Show settings for system update functionality.
* <p>
@@ -329,7 +329,7 @@ public final class Settings {
* Input: Nothing.
* <p>
* Output: Nothing.
- *
+ *
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -349,7 +349,7 @@ public final class Settings {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_SYNC_SETTINGS =
"android.settings.SYNC_SETTINGS";
-
+
/**
* Activity Action: Show settings for selecting the network operator.
* <p>
@@ -404,7 +404,7 @@ public final class Settings {
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_MEMORY_CARD_SETTINGS =
"android.settings.MEMORY_CARD_SETTINGS";
-
+
// End of Intent actions for Settings
private static final String JID_RESOURCE_PREFIX = "android";
@@ -495,7 +495,7 @@ public final class Settings {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
private static volatile NameValueCache mNameValueCache = null;
-
+
private static final HashSet<String> MOVED_TO_SECURE;
static {
MOVED_TO_SECURE = new HashSet<String>(30);
@@ -901,12 +901,12 @@ public final class Settings {
* plugged in.
*/
public static final int WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED = 1;
-
+
/**
* Value for {@link #WIFI_SLEEP_POLICY} to never go to sleep.
*/
public static final int WIFI_SLEEP_POLICY_NEVER = 2;
-
+
/**
* Whether to use static IP and other static network attributes.
* <p>
@@ -1025,6 +1025,14 @@ public final class Settings {
public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
/**
+ * If 0, the compatibility mode is off for all applications.
+ * If 1, older applications run under compatibility mode.
+ * TODO: remove this settings before code freeze (bug/1907571)
+ * @hide
+ */
+ public static final String COMPATIBILITY_MODE = "compatibility_mode";
+
+ /**
* The screen backlight brightness between 0 and 255.
*/
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
@@ -1115,12 +1123,12 @@ public final class Settings {
* Note: This is a one-off setting that will be removed in the future
* when there is profile support. For this reason, it is kept hidden
* from the public APIs.
- *
+ *
* @hide
*/
- public static final String NOTIFICATIONS_USE_RING_VOLUME =
+ public static final String NOTIFICATIONS_USE_RING_VOLUME =
"notifications_use_ring_volume";
-
+
/**
* The mapping of stream type (integer) to its setting.
*/
@@ -1188,7 +1196,7 @@ public final class Settings {
* feature converts two spaces to a "." and space.
*/
public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
-
+
/**
* Setting to showing password characters in text editors. 1 = On, 0 = Off
*/
@@ -1266,17 +1274,125 @@ public final class Settings {
public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
/**
+ * CDMA only settings
+ * DTMF tone type played by the dialer when dialing.
+ * 0 = Normal
+ * 1 = Long
+ * @hide
+ */
+ public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type";
+
+ /**
+ * CDMA only settings
+ * Emergency Tone 0 = Off
+ * 1 = Alert
+ * 2 = Vibrate
+ * @hide
+ */
+ public static final String EMERGENCY_TONE = "emergency_tone";
+
+ /**
+ * CDMA only settings
+ * Whether the auto retry is enabled. The value is
+ * boolean (1 or 0).
+ * @hide
+ */
+ public static final String CALL_AUTO_RETRY = "call_auto_retry";
+
+ /**
+ * Whether the hearing aid is enabled. The value is
+ * boolean (1 or 0).
+ * @hide
+ */
+ public static final String HEARING_AID = "hearing_aid";
+
+ /**
+ * CDMA only settings
+ * TTY Mode
+ * 0 = OFF
+ * 1 = FULL
+ * 2 = VCO
+ * 3 = HCO
+ * @hide
+ */
+ public static final String TTY_MODE = "tty_mode";
+
+ /**
* Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
-
+
/**
* Whether the haptic feedback (long presses, ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
+ /**
+ * Whether live web suggestions while the user types into search dialogs are
+ * enabled. Browsers and other search UIs should respect this, as it allows
+ * a user to avoid sending partial queries to a search engine, if it poses
+ * any privacy concern. The value is boolean (1 or 0).
+ */
+ public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
+
+ /**
+ * Settings to backup. This is here so that it's in the same place as the settings
+ * keys and easy to update.
+ * @hide
+ */
+ public static final String[] SETTINGS_TO_BACKUP = {
+ STAY_ON_WHILE_PLUGGED_IN,
+ END_BUTTON_BEHAVIOR,
+ WIFI_SLEEP_POLICY,
+ WIFI_USE_STATIC_IP,
+ WIFI_STATIC_IP,
+ WIFI_STATIC_GATEWAY,
+ WIFI_STATIC_NETMASK,
+ WIFI_STATIC_DNS1,
+ WIFI_STATIC_DNS2,
+ BLUETOOTH_DISCOVERABILITY,
+ BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ DIM_SCREEN,
+ SCREEN_OFF_TIMEOUT,
+ SCREEN_BRIGHTNESS,
+ VIBRATE_ON,
+ NOTIFICATIONS_USE_RING_VOLUME,
+ MODE_RINGER,
+ MODE_RINGER_STREAMS_AFFECTED,
+ MUTE_STREAMS_AFFECTED,
+ VOLUME_VOICE,
+ VOLUME_SYSTEM,
+ VOLUME_RING,
+ VOLUME_MUSIC,
+ VOLUME_ALARM,
+ VOLUME_NOTIFICATION,
+ VOLUME_VOICE + APPEND_FOR_LAST_AUDIBLE,
+ VOLUME_SYSTEM + APPEND_FOR_LAST_AUDIBLE,
+ VOLUME_RING + APPEND_FOR_LAST_AUDIBLE,
+ VOLUME_MUSIC + APPEND_FOR_LAST_AUDIBLE,
+ VOLUME_ALARM + APPEND_FOR_LAST_AUDIBLE,
+ VOLUME_NOTIFICATION + APPEND_FOR_LAST_AUDIBLE,
+ TEXT_AUTO_REPLACE,
+ TEXT_AUTO_CAPS,
+ TEXT_AUTO_PUNCTUATE,
+ TEXT_SHOW_PASSWORD,
+ AUTO_TIME,
+ TIME_12_24,
+ DATE_FORMAT,
+ ACCELEROMETER_ROTATION,
+ DTMF_TONE_WHEN_DIALING,
+ DTMF_TONE_TYPE_WHEN_DIALING,
+ EMERGENCY_TONE,
+ CALL_AUTO_RETRY,
+ HEARING_AID,
+ TTY_MODE,
+ SOUND_EFFECTS_ENABLED,
+ HAPTIC_FEEDBACK_ENABLED,
+ SHOW_WEB_SUGGESTIONS
+ };
+
// Settings moved to Settings.Secure
/**
@@ -1321,7 +1437,7 @@ public final class Settings {
*/
@Deprecated
public static final String INSTALL_NON_MARKET_APPS = Secure.INSTALL_NON_MARKET_APPS;
-
+
/**
* @deprecated Use {@link android.provider.Settings.Secure#LOCATION_PROVIDERS_ALLOWED}
* instead
@@ -1334,7 +1450,7 @@ public final class Settings {
*/
@Deprecated
public static final String LOGGING_ID = Secure.LOGGING_ID;
-
+
/**
* @deprecated Use {@link android.provider.Settings.Secure#NETWORK_PREFERENCE} instead
*/
@@ -1374,7 +1490,7 @@ public final class Settings {
*/
@Deprecated
public static final String USB_MASS_STORAGE_ENABLED = Secure.USB_MASS_STORAGE_ENABLED;
-
+
/**
* @deprecated Use {@link android.provider.Settings.Secure#USE_GOOGLE_MAIL} instead
*/
@@ -1412,7 +1528,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY;
-
+
/**
* @deprecated Use {@link android.provider.Settings.Secure#WIFI_NUM_OPEN_NETWORKS_KEPT}
* instead
@@ -1448,7 +1564,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS;
-
+
/**
* @deprecated Use
* {@link android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED} instead
@@ -1824,19 +1940,19 @@ public final class Settings {
* Whether the device has been provisioned (0 = false, 1 = true)
*/
public static final String DEVICE_PROVISIONED = "device_provisioned";
-
+
/**
* List of input methods that are currently enabled. This is a string
* containing the IDs of all enabled input methods, each ID separated
* by ':'.
*/
public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
-
+
/**
* Host name and port for a user-selected proxy.
*/
public static final String HTTP_PROXY = "http_proxy";
-
+
/**
* Whether the package installer should allow installation of apps downloaded from
* sources other than the Android Market (vending machine).
@@ -1845,12 +1961,12 @@ public final class Settings {
* 0 = only allow installing from the Android Market
*/
public static final String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
-
+
/**
* Comma-separated list of location providers that activities may access.
*/
public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
-
+
/**
* The Logging ID (a unique 64-bit value) as a hex string.
* Used as a pseudonymous identifier for logging.
@@ -1872,19 +1988,19 @@ public final class Settings {
* connectivity service should touch this.
*/
public static final String NETWORK_PREFERENCE = "network_preference";
-
- /**
+
+ /**
*/
public static final String PARENTAL_CONTROL_ENABLED = "parental_control_enabled";
-
- /**
+
+ /**
*/
public static final String PARENTAL_CONTROL_LAST_UPDATE = "parental_control_last_update";
-
- /**
+
+ /**
*/
public static final String PARENTAL_CONTROL_REDIRECT_URL = "parental_control_redirect_url";
-
+
/**
* Settings classname to launch when Settings is clicked from All
* Applications. Needed because of user testing between the old
@@ -1892,18 +2008,67 @@ public final class Settings {
*/
// TODO: 881807
public static final String SETTINGS_CLASSNAME = "settings_classname";
-
+
/**
* USB Mass Storage Enabled
*/
public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
-
+
/**
* If this setting is set (to anything), then all references
* to Gmail on the device must change to Google Mail.
*/
public static final String USE_GOOGLE_MAIL = "use_google_mail";
-
+
+ /**
+ * If accessibility is enabled.
+ */
+ public static final String ACCESSIBILITY_ENABLED = "accessibility_enabled";
+
+ /**
+ * List of the enabled accessibility providers.
+ */
+ public static final String ENABLED_ACCESSIBILITY_SERVICES =
+ "enabled_accessibility_services";
+
+ /**
+ * Setting to always use the default text-to-speech settings regardless
+ * of the application settings.
+ * 1 = override application settings,
+ * 0 = use application settings (if specified).
+ */
+ public static final String TTS_USE_DEFAULTS = "tts_use_defaults";
+
+ /**
+ * Default text-to-speech engine speech rate. 100 = 1x
+ */
+ public static final String TTS_DEFAULT_RATE = "tts_default_rate";
+
+ /**
+ * Default text-to-speech engine pitch. 100 = 1x
+ */
+ public static final String TTS_DEFAULT_PITCH = "tts_default_pitch";
+
+ /**
+ * Default text-to-speech engine.
+ */
+ public static final String TTS_DEFAULT_SYNTH = "tts_default_synth";
+
+ /**
+ * Default text-to-speech language.
+ */
+ public static final String TTS_DEFAULT_LANG = "tts_default_lang";
+
+ /**
+ * Default text-to-speech country.
+ */
+ public static final String TTS_DEFAULT_COUNTRY = "tts_default_country";
+
+ /**
+ * Default text-to-speech locale variant.
+ */
+ public static final String TTS_DEFAULT_VARIANT = "tts_default_variant";
+
/**
* Whether to notify the user of open networks.
* <p>
@@ -1915,64 +2080,64 @@ public final class Settings {
*/
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
"wifi_networks_available_notification_on";
-
+
/**
* Delay (in seconds) before repeating the Wi-Fi networks available notification.
* Connecting to a network will reset the timer.
*/
public static final String WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY =
"wifi_networks_available_repeat_delay";
-
+
/**
* The number of radio channels that are allowed in the local
* 802.11 regulatory domain.
* @hide
*/
public static final String WIFI_NUM_ALLOWED_CHANNELS = "wifi_num_allowed_channels";
-
+
/**
* When the number of open networks exceeds this number, the
* least-recently-used excess networks will be removed.
*/
public static final String WIFI_NUM_OPEN_NETWORKS_KEPT = "wifi_num_open_networks_kept";
-
+
/**
* Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this.
*/
public static final String WIFI_ON = "wifi_on";
-
+
/**
* The acceptable packet loss percentage (range 0 - 100) before trying
* another AP on the same network.
*/
public static final String WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE =
"wifi_watchdog_acceptable_packet_loss_percentage";
-
+
/**
* The number of access points required for a network in order for the
* watchdog to monitor it.
*/
public static final String WIFI_WATCHDOG_AP_COUNT = "wifi_watchdog_ap_count";
-
+
/**
* The delay between background checks.
*/
public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS =
"wifi_watchdog_background_check_delay_ms";
-
+
/**
* Whether the Wi-Fi watchdog is enabled for background checking even
* after it thinks the user has connected to a good access point.
*/
public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED =
"wifi_watchdog_background_check_enabled";
-
+
/**
* The timeout for a background ping
*/
public static final String WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS =
"wifi_watchdog_background_check_timeout_ms";
-
+
/**
* The number of initial pings to perform that *may* be ignored if they
* fail. Again, if these fail, they will *not* be used in packet loss
@@ -1981,7 +2146,7 @@ public final class Settings {
*/
public static final String WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT =
"wifi_watchdog_initial_ignored_ping_count";
-
+
/**
* The maximum number of access points (per network) to attempt to test.
* If this number is reached, the watchdog will no longer monitor the
@@ -1989,7 +2154,7 @@ public final class Settings {
* networks containing multiple APs whose DNS does not respond to pings.
*/
public static final String WIFI_WATCHDOG_MAX_AP_CHECKS = "wifi_watchdog_max_ap_checks";
-
+
/**
* Whether the Wi-Fi watchdog is enabled.
*/
@@ -2004,24 +2169,24 @@ public final class Settings {
* The number of pings to test if an access point is a good connection.
*/
public static final String WIFI_WATCHDOG_PING_COUNT = "wifi_watchdog_ping_count";
-
+
/**
* The delay between pings.
*/
public static final String WIFI_WATCHDOG_PING_DELAY_MS = "wifi_watchdog_ping_delay_ms";
-
+
/**
* The timeout per ping.
*/
public static final String WIFI_WATCHDOG_PING_TIMEOUT_MS = "wifi_watchdog_ping_timeout_ms";
-
+
/**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
* A value of N means that we will make N+1 connection attempts in all.
*/
public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";
-
+
/**
* Maximum amount of time in milliseconds to hold a wakelock while waiting for mobile
* data connectivity to be established after a disconnect from Wi-Fi.
@@ -2051,20 +2216,29 @@ public final class Settings {
public static final String CDMA_SUBSCRIPTION_MODE = "subscription_mode";
/**
- * represents current active phone class
- * 1 = GSM-Phone, 0 = CDMA-Phone
+ * The preferred network mode 7 = Global
+ * 6 = EvDo only
+ * 5 = CDMA w/o EvDo
+ * 4 = CDMA / EvDo auto
+ * 3 = GSM / WCDMA auto
+ * 2 = WCDMA only
+ * 1 = GSM only
+ * 0 = GSM / WCDMA preferred
* @hide
*/
- public static final String CURRENT_ACTIVE_PHONE = "current_active_phone";
+ public static final String PREFERRED_NETWORK_MODE =
+ "preferred_network_mode";
/**
- * The preferred network mode 7 = Global, CDMA default
- * 4 = CDMA only
- * 3 = GSM/UMTS only
+ * The preferred TTY mode 0 = TTy Off, CDMA default
+ * 1 = TTY Full
+ * 2 = TTY HCO
+ * 3 = TTY VCO
* @hide
*/
- public static final String PREFERRED_NETWORK_MODE =
- "preferred_network_mode";
+ public static final String PREFERRED_TTY_MODE =
+ "preferred_tty_mode";
+
/**
* CDMA Cell Broadcast SMS
@@ -2100,6 +2274,71 @@ public final class Settings {
public static final String TTY_MODE_ENABLED = "tty_mode_enabled";
/**
+ * Flag for allowing service provider to use location information to improve products and
+ * services.
+ * Type: int ( 0 = disallow, 1 = allow )
+ * @hide
+ */
+ public static final String USE_LOCATION_FOR_SERVICES = "use_location";
+
+ /**
+ * Controls whether settings backup is enabled.
+ * Type: int ( 0 = disabled, 1 = enabled )
+ * @hide
+ */
+ public static final String BACKUP_ENABLED = "backup_enabled";
+
+ /**
+ * Indicates whether settings backup has been fully provisioned.
+ * Type: int ( 0 = unprovisioned, 1 = fully provisioned )
+ * @hide
+ */
+ public static final String BACKUP_PROVISIONED = "backup_provisioned";
+
+ /**
+ * Component of the transport to use for backup/restore.
+ * @hide
+ */
+ public static final String BACKUP_TRANSPORT = "backup_transport";
+
+ /**
+ * Version for which the setup wizard was last shown. Bumped for
+ * each release when there is new setup information to show.
+ * @hide
+ */
+ public static final String LAST_SETUP_SHOWN = "last_setup_shown";
+
+ /**
+ * @hide
+ */
+ public static final String[] SETTINGS_TO_BACKUP = {
+ ADB_ENABLED,
+ ALLOW_MOCK_LOCATION,
+ INSTALL_NON_MARKET_APPS,
+ PARENTAL_CONTROL_ENABLED,
+ PARENTAL_CONTROL_REDIRECT_URL,
+ USB_MASS_STORAGE_ENABLED,
+ ACCESSIBILITY_ENABLED,
+ ENABLED_ACCESSIBILITY_SERVICES,
+ TTS_USE_DEFAULTS,
+ TTS_DEFAULT_RATE,
+ TTS_DEFAULT_PITCH,
+ TTS_DEFAULT_SYNTH,
+ TTS_DEFAULT_LANG,
+ TTS_DEFAULT_COUNTRY,
+ WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+ WIFI_NUM_ALLOWED_CHANNELS,
+ WIFI_NUM_OPEN_NETWORKS_KEPT,
+ BACKGROUND_DATA,
+ PREFERRED_NETWORK_MODE,
+ PREFERRED_TTY_MODE,
+ CDMA_CELL_BROADCAST_SMS,
+ PREFERRED_CDMA_SUBSCRIPTION,
+ ENHANCED_VOICE_PRIVACY_ENABLED
+ };
+
+ /**
* Helper method for determining if a location provider is enabled.
* @param cr the content resolver to use
* @param provider the location provider to query
@@ -2115,7 +2354,7 @@ public final class Settings {
allowedProviders.startsWith(provider + ",") ||
allowedProviders.endsWith("," + provider));
}
- return false;
+ return false;
}
/**
@@ -2139,7 +2378,7 @@ public final class Settings {
putString(cr, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, provider);
}
}
-
+
/**
* Gservices settings, containing the network names for Google's
* various services. This table holds simple name/addr pairs.
@@ -2160,6 +2399,13 @@ public final class Settings {
public static final String CHANGED_ACTION =
"com.google.gservices.intent.action.GSERVICES_CHANGED";
+ /**
+ * Intent action to override Gservices for testing. (Requires WRITE_GSERVICES permission.)
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String OVERRIDE_ACTION =
+ "com.google.gservices.intent.action.GSERVICES_OVERRIDE";
+
private static volatile NameValueCache mNameValueCache = null;
private static final Object mNameValueCacheLock = new Object();
@@ -2260,7 +2506,7 @@ public final class Settings {
* Event tags from the kernel event log to upload during checkin.
*/
public static final String CHECKIN_EVENTS = "checkin_events";
-
+
/**
* Event tags for list of services to upload during checkin.
*/
@@ -2428,12 +2674,34 @@ public final class Settings {
public static final String GMAIL_BUFFER_SERVER_RESPONSE = "gmail_buffer_server_response";
/**
+ * The maximum size in bytes allowed for the provider to gzip a protocol buffer uploaded to
+ * the server.
+ */
+ public static final String GMAIL_MAX_GZIP_SIZE = "gmail_max_gzip_size_bytes";
+
+ /**
* Controls whether Gmail will discard uphill operations that repeatedly fail. Value must be
* an integer where non-zero means true. Defaults to 1.
*/
public static final String GMAIL_DISCARD_ERROR_UPHILL_OP = "gmail_discard_error_uphill_op";
/**
+ * Controls how many attempts Gmail will try to upload an uphill operations before it
+ * abandons the operation. Defaults to 20.
+ */
+ public static final String GMAIL_NUM_RETRY_UPHILL_OP = "gmail_discard_error_uphill_op";
+
+ /**
+ * the transcoder URL for mobile devices.
+ */
+ public static final String TRANSCODER_URL = "mobile_transcoder_url";
+
+ /**
+ * URL that points to the privacy terms of the Google Talk service.
+ */
+ public static final String GTALK_TERMS_OF_SERVICE_URL = "gtalk_terms_of_service_url";
+
+ /**
* Hostname of the GTalk server.
*/
public static final String GTALK_SERVICE_HOSTNAME = "gtalk_hostname";
@@ -2561,6 +2829,21 @@ public final class Settings {
"gtalk_ssl_handshake_timeout_ms";
/**
+ * Compress the gtalk stream.
+ */
+ public static final String GTALK_COMPRESS = "gtalk_compress";
+
+ /**
+ * This is the timeout for which Google Talk will send the message using bareJID. In a
+ * established chat between two XMPP endpoints, Google Talk uses fullJID in the format
+ * of user@domain/resource in order to send the message to the specific client. However,
+ * if Google Talk hasn't received a message from that client after some time, it would
+ * fall back to use the bareJID, which would broadcast the message to all clients for
+ * the other user.
+ */
+ public static final String GTALK_USE_BARE_JID_TIMEOUT_MS = "gtalk_use_barejid_timeout_ms";
+
+ /**
* Enable use of ssl session caching.
* 'db' - save each session in a (per process) database
* 'file' - save each session in a (per process) file
@@ -2657,6 +2940,20 @@ public final class Settings {
public static final String VENDING_TAB_2_TITLE = "vending_tab_2_title";
/**
+ * Frequency in milliseconds at which we should request MCS heartbeats
+ * from the Vending Machine client.
+ */
+ public static final String VENDING_HEARTBEAT_FREQUENCY_MS =
+ "vending_heartbeat_frequency_ms";
+
+ /**
+ * Frequency in milliseconds at which we should resend pending download
+ * requests to the API Server from the Vending Machine client.
+ */
+ public static final String VENDING_PENDING_DOWNLOAD_RESEND_FREQUENCY_MS =
+ "vending_pd_resend_frequency_ms";
+
+ /**
* URL that points to the legal terms of service to display in Settings.
* <p>
* This should be a https URL. For a pretty user-friendly URL, use
@@ -2796,12 +3093,12 @@ public final class Settings {
* out without asking for use permit, to limit the un-authorized SMS
* usage.
*/
- public static final String SMS_OUTGOING_CEHCK_INTERVAL_MS =
+ public static final String SMS_OUTGOING_CHECK_INTERVAL_MS =
"sms_outgoing_check_interval_ms";
/**
* The number of outgoing SMS sent without asking for user permit
- * (of {@link #SMS_OUTGOING_CEHCK_INTERVAL_MS}
+ * (of {@link #SMS_OUTGOING_CHECK_INTERVAL_MS}
*/
public static final String SMS_OUTGOING_CEHCK_MAX_COUNT =
"sms_outgoing_check_max_count";
@@ -2950,13 +3247,21 @@ public final class Settings {
public static final String BATTERY_DISCHARGE_DURATION_THRESHOLD =
"battery_discharge_duration_threshold";
public static final String BATTERY_DISCHARGE_THRESHOLD = "battery_discharge_threshold";
-
+
/**
* An email address that anr bugreports should be sent to.
*/
public static final String ANR_BUGREPORT_RECIPIENT = "anr_bugreport_recipient";
/**
+ * Flag for allowing service provider to use location information to improve products and
+ * services.
+ * Type: int ( 0 = disallow, 1 = allow )
+ * @deprecated
+ */
+ public static final String USE_LOCATION_FOR_SERVICES = "use_location";
+
+ /**
* @deprecated
* @hide
*/
@@ -3094,7 +3399,7 @@ public final class Settings {
/**
* Add a new bookmark to the system.
- *
+ *
* @param cr The ContentResolver to query.
* @param intent The desired target of the bookmark.
* @param title Bookmark title that is shown to the user; null if none
@@ -3159,7 +3464,7 @@ public final class Settings {
/**
* Return the title as it should be displayed to the user. This takes
* care of localizing bookmarks that point to activities.
- *
+ *
* @param context A context.
* @param cursor A cursor pointing to the row whose title should be
* returned. The cursor must contain at least the {@link #TITLE}
@@ -3174,24 +3479,24 @@ public final class Settings {
throw new IllegalArgumentException(
"The cursor must contain the TITLE and INTENT columns.");
}
-
+
String title = cursor.getString(titleColumn);
if (!TextUtils.isEmpty(title)) {
return title;
}
-
+
String intentUri = cursor.getString(intentColumn);
if (TextUtils.isEmpty(intentUri)) {
return "";
}
-
+
Intent intent;
try {
intent = Intent.getIntent(intentUri);
} catch (URISyntaxException e) {
return "";
}
-
+
PackageManager packageManager = context.getPackageManager();
ResolveInfo info = packageManager.resolveActivity(intent, 0);
return info != null ? info.loadLabel(packageManager) : "";
@@ -3247,4 +3552,3 @@ public final class Settings {
return "android-" + Long.toHexString(androidId);
}
}
-
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a4145c4..4078fa6 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -466,6 +466,24 @@ public final class Telephony {
*/
public static final class Intents {
/**
+ * Set by BroadcastReceiver. Indicates the message was handled
+ * successfully.
+ */
+ public static final int RESULT_SMS_HANDLED = 1;
+
+ /**
+ * Set by BroadcastReceiver. Indicates a generic error while
+ * processing the message.
+ */
+ public static final int RESULT_SMS_GENERIC_ERROR = 2;
+
+ /**
+ * Set by BroadcastReceiver. Indicates insufficient memory to store
+ * the message.
+ */
+ public static final int RESULT_SMS_OUT_OF_MEMORY = 3;
+
+ /**
* Broadcast Action: A new text based SMS message has been received
* by the device. The intent will have the following extra
* values:</p>
@@ -476,7 +494,10 @@ public final class Telephony {
* </ul>
*
* <p>The extra values can be extracted using
- * {@link #getMessagesFromIntent(Intent)}</p>
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String SMS_RECEIVED_ACTION =
@@ -493,7 +514,10 @@ public final class Telephony {
* </ul>
*
* <p>The extra values can be extracted using
- * {@link #getMessagesFromIntent(Intent)}</p>
+ * {@link #getMessagesFromIntent(Intent)}.</p>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String DATA_SMS_RECEIVED_ACTION =
@@ -510,6 +534,9 @@ public final class Telephony {
* <li><em>pduType (Integer)</em> - The WAP PDU type</li>
* <li><em>data</em> - The data payload of the message</li>
* </ul>
+ *
+ * <p>If a BroadcastReceiver encounters an error while processing
+ * this intent it should set the result code appropriately.</p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String WAP_PUSH_RECEIVED_ACTION =
diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java
index 8e5cee9..8c843ef 100644
--- a/core/java/android/server/BluetoothDeviceService.java
+++ b/core/java/android/server/BluetoothDeviceService.java
@@ -372,6 +372,10 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
mEventLoop.onModeChanged(getModeNative());
}
+ if (mIsAirplaneSensitive && isAirplaneModeOn()) {
+ disable(false);
+ }
+
}
}
@@ -1220,6 +1224,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub {
break;
}
pw.println("getHeadsetAddress() = " + headset.getHeadsetAddress());
+ pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
+
headset.close();
}
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
index 03623d6..373e61f 100644
--- a/core/java/android/server/search/SearchManagerService.java
+++ b/core/java/android/server/search/SearchManagerService.java
@@ -17,48 +17,69 @@
package android.server.search;
import android.app.ISearchManager;
+import android.app.ISearchManagerCallback;
+import android.app.SearchDialog;
+import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.os.Bundle;
import android.os.Handler;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
/**
* This is a simplified version of the Search Manager service. It no longer handles
- * presentation (UI). Its function is to maintain the map & list of "searchable"
+ * presentation (UI). Its function is to maintain the map & list of "searchable"
* items, which provides a mapping from individual activities (where a user might have
* invoked search) to specific searchable activities (where the search will be dispatched).
*/
public class SearchManagerService extends ISearchManager.Stub
+ implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener
{
// general debugging support
private static final String TAG = "SearchManagerService";
- private static final boolean DEBUG = false;
-
- // configuration choices
- private static final boolean IMMEDIATE_SEARCHABLES_UPDATE = true;
+ private static final boolean DBG = false;
// class maintenance and general shared data
private final Context mContext;
private final Handler mHandler;
private boolean mSearchablesDirty;
- private Searchables mSearchables;
-
+ private final Searchables mSearchables;
+
+ final SearchDialog mSearchDialog;
+ ISearchManagerCallback mCallback = null;
+
+ private final boolean mDisabledOnBoot;
+
+ private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog";
+
/**
* Initializes the Search Manager service in the provided system context.
* Only one instance of this object should be created!
*
* @param context to use for accessing DB, window manager, etc.
*/
- public SearchManagerService(Context context) {
+ public SearchManagerService(Context context) {
mContext = context;
mHandler = new Handler();
mSearchablesDirty = true;
mSearchables = new Searchables(context);
-
+ mSearchDialog = new SearchDialog(context);
+ mSearchDialog.setOnCancelListener(this);
+ mSearchDialog.setOnDismissListener(this);
+
// Setup the infrastructure for updating and maintaining the list
// of searchable activities.
IntentFilter filter = new IntentFilter();
@@ -67,17 +88,18 @@ public class SearchManagerService extends ISearchManager.Stub
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
-
+
// After startup settles down, preload the searchables list,
// which will reduce the delay when the search UI is invoked.
- if (IMMEDIATE_SEARCHABLES_UPDATE) {
- mHandler.post(mRunUpdateSearchable);
- }
+ mHandler.post(mRunUpdateSearchable);
+
+ // allows disabling of search dialog for stress testing runs
+ mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY));
}
-
+
/**
* Listens for intent broadcasts.
- *
+ *
* The primary purpose here is to refresh the "searchables" list
* if packages are added/removed.
*/
@@ -85,29 +107,25 @@ public class SearchManagerService extends ISearchManager.Stub
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
-
+
// First, test for intents that matter at any time
if (action.equals(Intent.ACTION_PACKAGE_ADDED) ||
action.equals(Intent.ACTION_PACKAGE_REMOVED) ||
action.equals(Intent.ACTION_PACKAGE_CHANGED)) {
mSearchablesDirty = true;
- if (IMMEDIATE_SEARCHABLES_UPDATE) {
- mHandler.post(mRunUpdateSearchable);
- }
+ mHandler.post(mRunUpdateSearchable);
return;
}
}
};
-
+
/**
* This runnable (for the main handler / UI thread) will update the searchables list.
*/
private Runnable mRunUpdateSearchable = new Runnable() {
public void run() {
- if (mSearchablesDirty) {
- updateSearchables();
- }
- }
+ updateSearchablesIfDirty();
+ }
};
/**
@@ -115,42 +133,251 @@ public class SearchManagerService extends ISearchManager.Stub
* a package add/remove broadcast message.
*/
private void updateSearchables() {
+ if (DBG) debug("updateSearchables()");
mSearchables.buildSearchableList();
mSearchablesDirty = false;
}
/**
+ * Updates the list of searchables if needed.
+ */
+ private void updateSearchablesIfDirty() {
+ if (mSearchablesDirty) {
+ updateSearchables();
+ }
+ }
+
+ /**
* Returns the SearchableInfo for a given activity
*
* @param launchActivity The activity from which we're launching this search.
* @param globalSearch If false, this will only launch the search that has been specifically
- * defined by the application (which is usually defined as a local search). If no default
+ * defined by the application (which is usually defined as a local search). If no default
* search is defined in the current application or activity, no search will be launched.
* If true, this will always launch a platform-global (e.g. web-based) search instead.
* @return Returns a SearchableInfo record describing the parameters of the search,
* or null if no searchable metadata was available.
*/
public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) {
- // final check. however we should try to avoid this, because
- // it slows down the entry into the UI.
- if (mSearchablesDirty) {
- updateSearchables();
- }
+ updateSearchablesIfDirty();
SearchableInfo si = null;
if (globalSearch) {
si = mSearchables.getDefaultSearchable();
} else {
+ if (launchActivity == null) {
+ Log.e(TAG, "getSearchableInfo(), activity == null");
+ return null;
+ }
si = mSearchables.getSearchableInfo(launchActivity);
}
return si;
}
-
+
/**
* Returns a list of the searchable activities that can be included in global search.
*/
public List<SearchableInfo> getSearchablesInGlobalSearch() {
+ updateSearchablesIfDirty();
return mSearchables.getSearchablesInGlobalSearchList();
}
+ /**
+ * Launches the search UI on the main thread of the service.
+ *
+ * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean)
+ */
+ public void startSearch(final String initialQuery,
+ final boolean selectInitialQuery,
+ final ComponentName launchActivity,
+ final Bundle appSearchData,
+ final boolean globalSearch,
+ final ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("startSearch()");
+ Runnable task = new Runnable() {
+ public void run() {
+ performStartSearch(initialQuery,
+ selectInitialQuery,
+ launchActivity,
+ appSearchData,
+ globalSearch,
+ searchManagerCallback);
+ }
+ };
+ mHandler.post(task);
+ }
+
+ /**
+ * Actually launches the search. This must be called on the service UI thread.
+ */
+ /*package*/ void performStartSearch(String initialQuery,
+ boolean selectInitialQuery,
+ ComponentName launchActivity,
+ Bundle appSearchData,
+ boolean globalSearch,
+ ISearchManagerCallback searchManagerCallback) {
+ if (DBG) debug("performStartSearch()");
+
+ if (mDisabledOnBoot) {
+ Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY
+ + " system property is set.");
+ return;
+ }
+
+ mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData,
+ globalSearch);
+ if (searchManagerCallback != null) {
+ mCallback = searchManagerCallback;
+ }
+ }
+
+ /**
+ * Cancels the search dialog. Can be called from any thread.
+ */
+ public void stopSearch() {
+ if (DBG) debug("stopSearch()");
+ mHandler.post(new Runnable() {
+ public void run() {
+ performStopSearch();
+ }
+ });
+ }
+
+ /**
+ * Cancels the search dialog. Must be called from the service UI thread.
+ */
+ /*package*/ void performStopSearch() {
+ if (DBG) debug("performStopSearch()");
+ mSearchDialog.cancel();
+ }
+
+ /**
+ * Determines if the Search UI is currently displayed.
+ *
+ * @see SearchManager#isVisible()
+ */
+ public boolean isVisible() {
+ return postAndWait(mIsShowing, false, "isShowing()");
+ }
+
+ private final Callable<Boolean> mIsShowing = new Callable<Boolean>() {
+ public Boolean call() {
+ return mSearchDialog.isShowing();
+ }
+ };
+
+ public Bundle onSaveInstanceState() {
+ return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()");
+ }
+
+ private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() {
+ public Bundle call() {
+ if (mSearchDialog.isShowing()) {
+ return mSearchDialog.onSaveInstanceState();
+ } else {
+ return null;
+ }
+ }
+ };
+
+ public void onRestoreInstanceState(final Bundle searchDialogState) {
+ if (searchDialogState != null) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mSearchDialog.onRestoreInstanceState(searchDialogState);
+ }
+ });
+ }
+ }
+
+ public void onConfigurationChanged(final Configuration newConfig) {
+ mHandler.post(new Runnable() {
+ public void run() {
+ if (mSearchDialog.isShowing()) {
+ mSearchDialog.onConfigurationChanged(newConfig);
+ }
+ }
+ });
+ }
+
+ /**
+ * Called by {@link SearchDialog} when it goes away.
+ */
+ public void onDismiss(DialogInterface dialog) {
+ if (DBG) debug("onDismiss()");
+ if (mCallback != null) {
+ try {
+ mCallback.onDismiss();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onDismiss() failed: " + ex);
+ }
+ }
+ }
+
+ /**
+ * Called by {@link SearchDialog} when the user or activity cancels search.
+ * When this is called, {@link #onDismiss} is called too.
+ */
+ public void onCancel(DialogInterface dialog) {
+ if (DBG) debug("onCancel()");
+ if (mCallback != null) {
+ try {
+ mCallback.onCancel();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "onCancel() failed: " + ex);
+ }
+ }
+ }
+
+ /**
+ * Returns a list of the searchable activities that handle web searches.
+ */
+ public List<SearchableInfo> getSearchablesForWebSearch() {
+ updateSearchablesIfDirty();
+ return mSearchables.getSearchablesForWebSearchList();
+ }
+
+ /**
+ * Returns the default searchable activity for web searches.
+ */
+ public SearchableInfo getDefaultSearchableForWebSearch() {
+ updateSearchablesIfDirty();
+ return mSearchables.getDefaultSearchableForWebSearch();
+ }
+
+ /**
+ * Sets the default searchable activity for web searches.
+ */
+ public void setDefaultWebSearch(ComponentName component) {
+ mSearchables.setDefaultWebSearch(component);
+ }
+
+ /**
+ * Runs an operation on the handler for the service, blocks until it returns,
+ * and returns the value returned by the operation.
+ *
+ * @param <V> Return value type.
+ * @param callable Operation to run.
+ * @param errorResult Value to return if the operations throws an exception.
+ * @param name Operation name to include in error log messages.
+ * @return The value returned by the operation.
+ */
+ private <V> V postAndWait(Callable<V> callable, V errorResult, String name) {
+ FutureTask<V> task = new FutureTask<V>(callable);
+ mHandler.post(task);
+ try {
+ return task.get();
+ } catch (InterruptedException ex) {
+ Log.e(TAG, "Error calling " + name + ": " + ex);
+ return errorResult;
+ } catch (ExecutionException ex) {
+ Log.e(TAG, "Error calling " + name + ": " + ex);
+ return errorResult;
+ }
+ }
+
+ private static void debug(String msg) {
+ Thread thread = Thread.currentThread();
+ Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")");
+ }
}
diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java
index 842fc75..8ef1f15 100644
--- a/core/java/android/server/search/SearchableInfo.java
+++ b/core/java/android/server/search/SearchableInfo.java
@@ -40,7 +40,7 @@ import java.util.HashMap;
public final class SearchableInfo implements Parcelable {
// general debugging support
- private static final boolean DBG = true;
+ private static final boolean DBG = false;
private static final String LOG_TAG = "SearchableInfo";
// static strings used for XML lookups.
@@ -66,6 +66,8 @@ public final class SearchableInfo implements Parcelable {
private final int mSearchInputType;
private final int mSearchImeOptions;
private final boolean mIncludeInGlobalSearch;
+ private final boolean mQueryAfterZeroResults;
+ private final String mSettingsDescription;
private final String mSuggestAuthority;
private final String mSuggestPath;
private final String mSuggestSelection;
@@ -133,6 +135,14 @@ public final class SearchableInfo implements Parcelable {
public boolean shouldRewriteQueryFromText() {
return 0 != (mSearchMode & SEARCH_MODE_QUERY_REWRITE_FROM_TEXT);
}
+
+ /**
+ * Gets the description to use for this source in system search settings, or null if
+ * none has been specified.
+ */
+ public String getSettingsDescription() {
+ return mSettingsDescription;
+ }
/**
* Retrieve the path for obtaining search suggestions.
@@ -276,7 +286,11 @@ public final class SearchableInfo implements Parcelable {
EditorInfo.IME_ACTION_SEARCH);
mIncludeInGlobalSearch = a.getBoolean(
com.android.internal.R.styleable.Searchable_includeInGlobalSearch, false);
+ mQueryAfterZeroResults = a.getBoolean(
+ com.android.internal.R.styleable.Searchable_queryAfterZeroResults, false);
+ mSettingsDescription = a.getString(
+ com.android.internal.R.styleable.Searchable_searchSettingsDescription);
mSuggestAuthority = a.getString(
com.android.internal.R.styleable.Searchable_searchSuggestAuthority);
mSuggestPath = a.getString(
@@ -317,7 +331,7 @@ public final class SearchableInfo implements Parcelable {
// for now, implement some form of rules - minimal data
if (mLabelId == 0) {
- throw new IllegalArgumentException("No label.");
+ throw new IllegalArgumentException("Search label must be a resource reference.");
}
}
@@ -438,13 +452,18 @@ public final class SearchableInfo implements Parcelable {
xml.close();
if (DBG) {
- Log.d(LOG_TAG, "Checked " + activityInfo.name
- + ",label=" + searchable.getLabelId()
- + ",icon=" + searchable.getIconId()
- + ",suggestAuthority=" + searchable.getSuggestAuthority()
- + ",target=" + searchable.getSearchActivity().getClassName()
- + ",global=" + searchable.shouldIncludeInGlobalSearch()
- + ",threshold=" + searchable.getSuggestThreshold());
+ if (searchable != null) {
+ Log.d(LOG_TAG, "Checked " + activityInfo.name
+ + ",label=" + searchable.getLabelId()
+ + ",icon=" + searchable.getIconId()
+ + ",suggestAuthority=" + searchable.getSuggestAuthority()
+ + ",target=" + searchable.getSearchActivity().getClassName()
+ + ",global=" + searchable.shouldIncludeInGlobalSearch()
+ + ",settingsDescription=" + searchable.getSettingsDescription()
+ + ",threshold=" + searchable.getSuggestThreshold());
+ } else {
+ Log.d(LOG_TAG, "Checked " + activityInfo.name + ", no searchable meta-data");
+ }
}
return searchable;
}
@@ -637,6 +656,17 @@ public final class SearchableInfo implements Parcelable {
}
/**
+ * Checks whether this searchable activity should be invoked after a query returned zero
+ * results.
+ *
+ * @return The value of the <code>queryAfterZeroResults</code> attribute,
+ * or <code>false</code> if the attribute is not set.
+ */
+ public boolean queryAfterZeroResults() {
+ return mQueryAfterZeroResults;
+ }
+
+ /**
* Support for parcelable and aidl operations.
*/
public static final Parcelable.Creator<SearchableInfo> CREATOR
@@ -667,7 +697,9 @@ public final class SearchableInfo implements Parcelable {
mSearchInputType = in.readInt();
mSearchImeOptions = in.readInt();
mIncludeInGlobalSearch = in.readInt() != 0;
-
+ mQueryAfterZeroResults = in.readInt() != 0;
+
+ mSettingsDescription = in.readString();
mSuggestAuthority = in.readString();
mSuggestPath = in.readString();
mSuggestSelection = in.readString();
@@ -702,7 +734,9 @@ public final class SearchableInfo implements Parcelable {
dest.writeInt(mSearchInputType);
dest.writeInt(mSearchImeOptions);
dest.writeInt(mIncludeInGlobalSearch ? 1 : 0);
+ dest.writeInt(mQueryAfterZeroResults ? 1 : 0);
+ dest.writeString(mSettingsDescription);
dest.writeString(mSuggestAuthority);
dest.writeString(mSuggestPath);
dest.writeString(mSuggestSelection);
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
index 9586d56..c7cc8ed 100644
--- a/core/java/android/server/search/Searchables.java
+++ b/core/java/android/server/search/Searchables.java
@@ -16,49 +16,64 @@
package android.server.search;
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.R;
+
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
import android.os.Bundle;
+import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
- * This class maintains the information about all searchable activities.
+ * This class maintains the information about all searchable activities.
*/
public class Searchables {
+ private static final String LOG_TAG = "Searchables";
+
// static strings used for XML lookups, etc.
- // TODO how should these be documented for the developer, in a more structured way than
+ // TODO how should these be documented for the developer, in a more structured way than
// the current long wordy javadoc in SearchManager.java ?
private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
-
+
private Context mContext;
-
+
private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
private ArrayList<SearchableInfo> mSearchablesList = null;
private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
+ private ArrayList<SearchableInfo> mSearchablesForWebSearchList = null;
private SearchableInfo mDefaultSearchable = null;
-
+ private SearchableInfo mDefaultSearchableForWebSearch = null;
+
+ public static String GOOGLE_SEARCH_COMPONENT_NAME =
+ "com.android.googlesearch/.GoogleSearch";
+ public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME =
+ "com.google.android.providers.enhancedgooglesearch/.Launcher";
+
/**
- *
+ *
* @param context Context to use for looking up activities etc.
*/
public Searchables (Context context) {
mContext = context;
}
-
+
/**
* Look up, or construct, based on the activity.
- *
- * The activities fall into three cases, based on meta-data found in
+ *
+ * The activities fall into three cases, based on meta-data found in
* the manifest entry:
* <ol>
* <li>The activity itself implements search. This is indicated by the
@@ -70,16 +85,16 @@ public class Searchables {
* case the factory will "redirect" and return the searchable data.</li>
* <li>No searchability data is provided. We return null here and other
* code will insert the "default" (e.g. contacts) search.
- *
+ *
* TODO: cache the result in the map, and check the map first.
* TODO: it might make sense to implement the searchable reference as
* an application meta-data entry. This way we don't have to pepper each
* and every activity.
* TODO: can we skip the constructor step if it's a non-searchable?
- * TODO: does it make sense to plug the default into a slot here for
+ * TODO: does it make sense to plug the default into a slot here for
* automatic return? Probably not, but it's one way to do it.
*
- * @param activity The name of the current activity, or null if the
+ * @param activity The name of the current activity, or null if the
* activity does not define any explicit searchable metadata.
*/
public SearchableInfo getSearchableInfo(ComponentName activity) {
@@ -89,18 +104,18 @@ public class Searchables {
result = mSearchablesMap.get(activity);
if (result != null) return result;
}
-
+
// Step 2. See if the current activity references a searchable.
// Note: Conceptually, this could be a while(true) loop, but there's
- // no point in implementing reference chaining here and risking a loop.
+ // no point in implementing reference chaining here and risking a loop.
// References must point directly to searchable activities.
-
+
ActivityInfo ai = null;
try {
ai = mContext.getPackageManager().
getActivityInfo(activity, PackageManager.GET_META_DATA );
String refActivityName = null;
-
+
// First look for activity-specific reference
Bundle md = ai.metaData;
if (md != null) {
@@ -113,11 +128,11 @@ public class Searchables {
refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
}
}
-
+
// Irrespective of source, if a reference was found, follow it.
if (refActivityName != null)
{
- // An app or activity can declare that we should simply launch
+ // An app or activity can declare that we should simply launch
// "system default search" if search is invoked.
if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
return getDefaultSearchable();
@@ -143,95 +158,212 @@ public class Searchables {
} catch (PackageManager.NameNotFoundException e) {
// case 3: no metadata
}
-
+
// Step 3. None found. Return null.
return null;
-
+
}
-
+
/**
* Provides the system-default search activity, which you can use
* whenever getSearchableInfo() returns null;
- *
+ *
* @return Returns the system-default search activity, null if never defined
*/
public synchronized SearchableInfo getDefaultSearchable() {
return mDefaultSearchable;
}
-
+
public synchronized boolean isDefaultSearchable(SearchableInfo searchable) {
return searchable == mDefaultSearchable;
}
-
+
/**
- * Builds an entire list (suitable for display) of
- * activities that are searchable, by iterating the entire set of
- * ACTION_SEARCH intents.
- *
+ * Builds an entire list (suitable for display) of
+ * activities that are searchable, by iterating the entire set of
+ * ACTION_SEARCH & ACTION_WEB_SEARCH intents.
+ *
* Also clears the hash of all activities -> searches which will
* refill as the user clicks "search".
- *
+ *
* This should only be done at startup and again if we know that the
* list has changed.
- *
+ *
* TODO: every activity that provides a ACTION_SEARCH intent should
* also provide searchability meta-data. There are a bunch of checks here
* that, if data is not found, silently skip to the next activity. This
* won't help a developer trying to figure out why their activity isn't
* showing up in the list, but an exception here is too rough. I would
* like to find a better notification mechanism.
- *
+ *
* TODO: sort the list somehow? UI choice.
*/
public void buildSearchableList() {
-
// These will become the new values at the end of the method
- HashMap<ComponentName, SearchableInfo> newSearchablesMap
+ HashMap<ComponentName, SearchableInfo> newSearchablesMap
= new HashMap<ComponentName, SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesList
= new ArrayList<SearchableInfo>();
ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
= new ArrayList<SearchableInfo>();
+ ArrayList<SearchableInfo> newSearchablesForWebSearchList
+ = new ArrayList<SearchableInfo>();
final PackageManager pm = mContext.getPackageManager();
-
- // use intent resolver to generate list of ACTION_SEARCH receivers
- List<ResolveInfo> infoList;
+
+ // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
+ List<ResolveInfo> searchList;
final Intent intent = new Intent(Intent.ACTION_SEARCH);
- infoList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
-
+ searchList = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+
+ List<ResolveInfo> webSearchInfoList;
+ final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
+ webSearchInfoList = pm.queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
+
// analyze each one, generate a Searchables record, and record
- if (infoList != null) {
- int count = infoList.size();
+ if (searchList != null || webSearchInfoList != null) {
+ int search_count = (searchList == null ? 0 : searchList.size());
+ int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
+ int count = search_count + web_search_count;
for (int ii = 0; ii < count; ii++) {
// for each component, try to find metadata
- ResolveInfo info = infoList.get(ii);
+ ResolveInfo info = (ii < search_count)
+ ? searchList.get(ii)
+ : webSearchInfoList.get(ii - search_count);
ActivityInfo ai = info.activityInfo;
- SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
- if (searchable != null) {
- newSearchablesList.add(searchable);
- newSearchablesMap.put(searchable.getSearchActivity(), searchable);
- if (searchable.shouldIncludeInGlobalSearch()) {
- newSearchablesInGlobalSearchList.add(searchable);
+ // Check first to avoid duplicate entries.
+ if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
+ SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai);
+ if (searchable != null) {
+ newSearchablesList.add(searchable);
+ newSearchablesMap.put(searchable.getSearchActivity(), searchable);
+ if (searchable.shouldIncludeInGlobalSearch()) {
+ newSearchablesInGlobalSearchList.add(searchable);
+ }
}
}
}
}
-
+
+ if (webSearchInfoList != null) {
+ for (int i = 0; i < webSearchInfoList.size(); ++i) {
+ ActivityInfo ai = webSearchInfoList.get(i).activityInfo;
+ ComponentName component = new ComponentName(ai.packageName, ai.name);
+ newSearchablesForWebSearchList.add(newSearchablesMap.get(component));
+ }
+ }
+
// Find the global search provider
Intent globalSearchIntent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
ComponentName globalSearchActivity = globalSearchIntent.resolveActivity(pm);
SearchableInfo newDefaultSearchable = newSearchablesMap.get(globalSearchActivity);
+ if (newDefaultSearchable == null) {
+ Log.w(LOG_TAG, "No searchable info found for new default searchable activity "
+ + globalSearchActivity);
+ }
+
+ // Find the default web search provider.
+ ComponentName webSearchActivity = getPreferredWebSearchActivity();
+ SearchableInfo newDefaultSearchableForWebSearch = null;
+ if (webSearchActivity != null) {
+ newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity);
+ }
+ if (newDefaultSearchableForWebSearch == null) {
+ Log.w(LOG_TAG, "No searchable info found for new default web search activity "
+ + webSearchActivity);
+ }
+
// Store a consistent set of new values
synchronized (this) {
mSearchablesMap = newSearchablesMap;
mSearchablesList = newSearchablesList;
mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
+ mSearchablesForWebSearchList = newSearchablesForWebSearchList;
mDefaultSearchable = newDefaultSearchable;
+ mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch;
+ }
+
+ // Inform all listeners that the list of searchables has been updated.
+ mContext.sendBroadcast(new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
+ }
+
+ /**
+ * Checks if the given activity component is present in the system and if so makes it the
+ * preferred activity for handling ACTION_WEB_SEARCH.
+ * @param component Name of the component to check and set as preferred.
+ * @param action Intent action for which this activity is to be set as preferred.
+ * @return true if component was detected and set as preferred activity, false if not.
+ */
+ private boolean setPreferredActivity(ComponentName component, String action) {
+ Log.d(LOG_TAG, "Checking component " + component);
+ PackageManager pm = mContext.getPackageManager();
+ ActivityInfo ai;
+ try {
+ ai = pm.getActivityInfo(component, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ // The code here to find the value for bestMatch is heavily inspired by the code
+ // in ResolverActivity where the preferred activity is set.
+ Intent intent = new Intent(action);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ List<ResolveInfo> webSearchActivities = pm.queryIntentActivities(intent, 0);
+ ComponentName set[] = new ComponentName[webSearchActivities.size()];
+ int bestMatch = 0;
+ for (int i = 0; i < webSearchActivities.size(); ++i) {
+ ResolveInfo ri = webSearchActivities.get(i);
+ set[i] = new ComponentName(ri.activityInfo.packageName,
+ ri.activityInfo.name);
+ if (ri.match > bestMatch) bestMatch = ri.match;
+ }
+
+ Log.d(LOG_TAG, "Setting preferred web search activity to " + component);
+ IntentFilter filter = new IntentFilter(action);
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+ pm.replacePreferredActivity(filter, bestMatch, set, component);
+ return true;
+ }
+
+ public ComponentName getPreferredWebSearchActivity() {
+ // Check if we have a preferred web search activity.
+ Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+
+ if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) {
+ Log.d(LOG_TAG, "No preferred activity set for action web search.");
+
+ // The components in the providers array are checked in the order of declaration so the
+ // first one has the highest priority. If the component exists in the system it is set
+ // as the preferred activity to handle intent action web search.
+ String[] preferredActivities = mContext.getResources().getStringArray(
+ com.android.internal.R.array.default_web_search_providers);
+ for (String componentName : preferredActivities) {
+ ComponentName component = ComponentName.unflattenFromString(componentName);
+ if (setPreferredActivity(component, Intent.ACTION_WEB_SEARCH)) {
+ return component;
+ }
+ }
+ } else {
+ // If the current preferred activity is GoogleSearch, and we detect
+ // EnhancedGoogleSearch installed as well, set the latter as preferred since that
+ // is a superset and provides more functionality.
+ ComponentName cn = new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
+ if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) {
+ ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString(
+ ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME);
+ if (setPreferredActivity(enhancedGoogleSearch, Intent.ACTION_WEB_SEARCH)) {
+ return enhancedGoogleSearch;
+ }
+ }
}
+
+ if (ri == null) return null;
+ return new ComponentName(ri.activityInfo.packageName, ri.activityInfo.name);
}
-
+
/**
* Returns the list of searchable activities.
*/
@@ -239,11 +371,33 @@ public class Searchables {
ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
return result;
}
-
+
/**
* Returns a list of the searchable activities that can be included in global search.
*/
public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() {
return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
}
+
+ /**
+ * Returns a list of the searchable activities that handle web searches.
+ */
+ public synchronized ArrayList<SearchableInfo> getSearchablesForWebSearchList() {
+ return new ArrayList<SearchableInfo>(mSearchablesForWebSearchList);
+ }
+
+ /**
+ * Returns the default searchable activity for web searches.
+ */
+ public synchronized SearchableInfo getDefaultSearchableForWebSearch() {
+ return mDefaultSearchableForWebSearch;
+ }
+
+ /**
+ * Sets the default searchable activity for web searches.
+ */
+ public synchronized void setDefaultWebSearch(ComponentName component) {
+ setPreferredActivity(component, Intent.ACTION_WEB_SEARCH);
+ buildSearchableList();
+ }
}
diff --git a/core/java/android/speech/IRecognitionListener.aidl b/core/java/android/speech/IRecognitionListener.aidl
index 6ed32b5..2da2258 100644
--- a/core/java/android/speech/IRecognitionListener.aidl
+++ b/core/java/android/speech/IRecognitionListener.aidl
@@ -17,6 +17,7 @@
package android.speech;
import android.os.Bundle;
+import android.speech.RecognitionResult;
/**
* Listener for speech recognition events, used with RecognitionService.
@@ -43,13 +44,17 @@ interface IRecognitionListener {
/** Called after the user stops speaking. */
void onEndOfSpeech();
- /** A network or recognition error occurred. */
- void onError(in String error);
+ /**
+ * A network or recognition error occurred. The code is defined in
+ * {@link android.speech.RecognitionResult}
+ */
+ void onError(in int error);
/**
- * Called when recognition transcripts are ready.
- * results: an ordered list of the most likely transcripts (N-best list).
- * @hide
+ * Called when recognition results are ready.
+ * @param results: an ordered list of the most likely results (N-best list).
+ * @param key: a key associated with the results. The same results can
+ * be retrieved asynchronously later using the key, if available.
*/
- void onResults(in List<String> results);
+ void onResults(in List<RecognitionResult> results, long key);
}
diff --git a/core/java/android/speech/IRecognitionService.aidl b/core/java/android/speech/IRecognitionService.aidl
index 36d12e9a..a18c380 100644
--- a/core/java/android/speech/IRecognitionService.aidl
+++ b/core/java/android/speech/IRecognitionService.aidl
@@ -18,6 +18,7 @@ package android.speech;
import android.content.Intent;
import android.speech.IRecognitionListener;
+import android.speech.RecognitionResult;
// A Service interface to speech recognition. Call startListening when
// you want to begin capturing audio; RecognitionService will automatically
@@ -29,6 +30,8 @@ interface IRecognitionService {
// see RecognizerIntent.java for constants used to specify the intent.
void startListening(in Intent recognizerIntent,
in IRecognitionListener listener);
+
+ List<RecognitionResult> getRecognitionResults(in long key);
void cancel();
}
diff --git a/core/java/android/speech/RecognitionResult.aidl b/core/java/android/speech/RecognitionResult.aidl
new file mode 100644
index 0000000..59e53ab
--- /dev/null
+++ b/core/java/android/speech/RecognitionResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.speech;
+
+parcelable RecognitionResult;
diff --git a/core/java/android/speech/RecognitionResult.java b/core/java/android/speech/RecognitionResult.java
new file mode 100644
index 0000000..8d031fc
--- /dev/null
+++ b/core/java/android/speech/RecognitionResult.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.speech;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * RecognitionResult is a passive object that stores a single recognized
+ * query and its search result.
+ * TODO: revisit and improve. May be we should have a separate result
+ * object for each type, and put them (type/value) in bundle?
+ *
+ * {@hide}
+ */
+public class RecognitionResult implements Parcelable {
+ /**
+ * Status of the recognize request.
+ */
+ public static final int NETWORK_TIMEOUT = 1; // Network operation timed out.
+ public static final int NETWORK_ERROR = 2; // Other networkrelated errors.
+ public static final int AUDIO_ERROR = 3; // Audio recording error.
+ public static final int SERVER_ERROR = 4; // Server sends error status.
+ public static final int CLIENT_ERROR = 5; // Other client side errors.
+ public static final int SPEECH_TIMEOUT = 6; // No speech input
+ public static final int NO_MATCH = 7; // No recognition result matched.
+ public static final int SERVICE_BUSY = 8; // RecognitionService busy.
+
+ /**
+ * Type of the recognition results.
+ */
+ public static final int RAW_RECOGNITION_RESULT = 0;
+ public static final int WEB_SEARCH_RESULT = 1;
+ public static final int CONTACT_RESULT = 2;
+
+ /**
+ * A factory method to create a raw RecognitionResult
+ *
+ * @param sentence the recognized text.
+ */
+ public static RecognitionResult newRawRecognitionResult(String sentence) {
+ return new RecognitionResult(RAW_RECOGNITION_RESULT, sentence, null, null);
+ }
+
+ /**
+ * A factory method to create RecognitionResult for contacts.
+ *
+ * @param contact the contact name.
+ * @param phoneType the phone type.
+ */
+ public static RecognitionResult newContactResult(String contact, int phoneType) {
+ return new RecognitionResult(CONTACT_RESULT, contact, phoneType);
+ }
+
+ /**
+ * A factory method to create a RecognitionResult for Web Search Query.
+ *
+ * @param query the query string.
+ * @param html the html page of the search result.
+ * @param url the url that performs the search with the query.
+ */
+ public static RecognitionResult newWebResult(String query, String html, String url) {
+ return new RecognitionResult(WEB_SEARCH_RESULT, query, html, url);
+ }
+
+ public static final Parcelable.Creator<RecognitionResult> CREATOR
+ = new Parcelable.Creator<RecognitionResult>() {
+
+ public RecognitionResult createFromParcel(Parcel in) {
+ return new RecognitionResult(in);
+ }
+
+ public RecognitionResult[] newArray(int size) {
+ return new RecognitionResult[size];
+ }
+ };
+
+ /**
+ * Result type.
+ */
+ public final int mResultType;
+
+ /**
+ * The recognized string when mResultType is WEB_SEARCH_RESULT.
+ * The name of the contact when mResultType is CONTACT_RESULT.
+ */
+ public final String mText;
+
+ /**
+ * The HTML result page for the query. If this is null, then the
+ * application must use the url field to get the HTML result page.
+ */
+ public final String mHtml;
+
+ /**
+ * The url to get the result page for the query string. The
+ * application must use this url instead of performing the search
+ * with the query.
+ */
+ public final String mUrl;
+
+ /** Phone number type. This is valid only when mResultType == CONTACT_RESULT */
+ public final int mPhoneType;
+
+ private RecognitionResult(int type, String query, String html, String url) {
+ mResultType = type;
+ mText = query;
+ mHtml = html;
+ mUrl = url;
+ mPhoneType = -1;
+ }
+
+ private RecognitionResult(int type, String query, int at) {
+ mResultType = type;
+ mText = query;
+ mPhoneType = at;
+ mHtml = null;
+ mUrl = null;
+ }
+
+ private RecognitionResult(Parcel in) {
+ mResultType = in.readInt();
+ mText = in.readString();
+ mHtml= in.readString();
+ mUrl= in.readString();
+ mPhoneType = in.readInt();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mResultType);
+ out.writeString(mText);
+ out.writeString(mHtml);
+ out.writeString(mUrl);
+ out.writeInt(mPhoneType);
+ }
+
+
+ @Override
+ public String toString() {
+ String resultType[] = { "RAW", "WEB", "CONTACT" };
+ return "[type=" + resultType[mResultType] +
+ ", text=" + mText+ ", mUrl=" + mUrl + ", html=" + mHtml + "]";
+ }
+
+ public int describeContents() {
+ // no special description
+ return 0;
+ }
+}
diff --git a/core/java/android/speech/RecognitionServiceUtil.java b/core/java/android/speech/RecognitionServiceUtil.java
index 650c0fd..a8c7868 100644
--- a/core/java/android/speech/RecognitionServiceUtil.java
+++ b/core/java/android/speech/RecognitionServiceUtil.java
@@ -21,6 +21,9 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
+import android.speech.RecognitionResult;
+import android.util.Log;
import java.util.List;
@@ -56,6 +59,11 @@ public class RecognitionServiceUtil {
public static final Intent sDefaultIntent = new Intent(
RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+ // Recognize request parameters
+ public static final String USE_LOCATION = "useLocation";
+ public static final String CONTACT_AUTH_TOKEN = "contactAuthToken";
+
+ // Bundles
public static final String NOISE_LEVEL = "NoiseLevel";
public static final String SIGNAL_NOISE_RATIO = "SignalNoiseRatio";
@@ -72,8 +80,8 @@ public class RecognitionServiceUtil {
public void onRmsChanged(float rmsdB) {}
public void onBufferReceived(byte[] buf) {}
public void onEndOfSpeech() {}
- public void onError(String error) {}
- public void onResults(List<String> results) {}
+ public void onError(int error) {}
+ public void onResults(List<RecognitionResult> results, long key) {}
}
/**
diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl
new file mode 100755
index 0000000..c9a6180
--- /dev/null
+++ b/core/java/android/speech/tts/ITts.aidl
@@ -0,0 +1,63 @@
+/*
+ * 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.speech.tts;
+
+import android.speech.tts.ITtsCallback;
+
+import android.content.Intent;
+
+/**
+ * AIDL for the TTS Service
+ * ITts.java is autogenerated from this.
+ *
+ * {@hide}
+ */
+interface ITts {
+ int setSpeechRate(in int speechRate);
+
+ int setPitch(in int pitch);
+
+ int speak(in String text, in int queueMode, in String[] params);
+
+ boolean isSpeaking();
+
+ int stop();
+
+ void addSpeech(in String text, in String packageName, in int resId);
+
+ void addSpeechFile(in String text, in String filename);
+
+ String[] getLanguage();
+
+ int isLanguageAvailable(in String language, in String country, in String variant);
+
+ int setLanguage(in String language, in String country, in String variant);
+
+ boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
+
+ int playEarcon(in String earcon, in int queueMode, in String[] params);
+
+ void addEarcon(in String earcon, in String packageName, in int resId);
+
+ void addEarconFile(in String earcon, in String filename);
+
+ void registerCallback(ITtsCallback cb);
+
+ void unregisterCallback(ITtsCallback cb);
+
+ int playSilence(in long duration, in int queueMode, in String[] params);
+}
diff --git a/core/java/android/speech/tts/ITtsCallback.aidl b/core/java/android/speech/tts/ITtsCallback.aidl
new file mode 100755
index 0000000..48ed73e
--- /dev/null
+++ b/core/java/android/speech/tts/ITtsCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.speech.tts;
+
+/**
+ * AIDL for the callback from the TTS Service
+ * ITtsCallback.java is autogenerated from this.
+ *
+ * {@hide}
+ */
+oneway interface ITtsCallback {
+ void markReached(String mark);
+}
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
new file mode 100644
index 0000000..616b3f1
--- /dev/null
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2009 Google Inc.
+ *
+ * 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.speech.tts;
+
+import android.speech.tts.ITts;
+import android.speech.tts.ITtsCallback;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ *
+ * Synthesizes speech from text for immediate playback or to create a sound file.
+ *
+ */
+//TODO complete javadoc + add links to constants
+public class TextToSpeech {
+
+ /**
+ * Denotes a successful operation.
+ */
+ public static final int TTS_SUCCESS = 0;
+ /**
+ * Denotes a generic operation failure.
+ */
+ public static final int TTS_ERROR = -1;
+
+ /**
+ * Queue mode where all entries in the playback queue (media to be played
+ * and text to be synthesized) are dropped and replaced by the new entry.
+ */
+ public static final int TTS_QUEUE_FLUSH = 0;
+ /**
+ * Queue mode where the new entry is added at the end of the playback queue.
+ */
+ public static final int TTS_QUEUE_ADD = 1;
+
+
+ /**
+ * Denotes the language is available exactly as specified by the locale
+ */
+ public static final int TTS_LANG_COUNTRY_VAR_AVAILABLE = 2;
+
+
+ /**
+ * Denotes the language is available for the language and country specified
+ * by the locale, but not the variant.
+ */
+ public static final int TTS_LANG_COUNTRY_AVAILABLE = 1;
+
+
+ /**
+ * Denotes the language is available for the language by the locale,
+ * but not the country and variant.
+ */
+ public static final int TTS_LANG_AVAILABLE = 0;
+
+ /**
+ * Denotes the language data is missing.
+ */
+ public static final int TTS_LANG_MISSING_DATA = -1;
+
+ /**
+ * Denotes the language is not supported by the current TTS engine.
+ */
+ public static final int TTS_LANG_NOT_SUPPORTED = -2;
+
+
+ /**
+ * Called when the TTS has initialized.
+ *
+ * The InitListener must implement the onInit function. onInit is passed a
+ * status code indicating the result of the TTS initialization.
+ */
+ public interface OnInitListener {
+ public void onInit(int status);
+ }
+
+ /**
+ * Internal constants for the TTS functionality
+ *
+ * {@hide}
+ */
+ public class Engine {
+ // default values for a TTS engine when settings are not found in the provider
+ public static final int FALLBACK_TTS_DEFAULT_RATE = 100; // 1x
+ public static final int FALLBACK_TTS_DEFAULT_PITCH = 100;// 1x
+ public static final int FALLBACK_TTS_USE_DEFAULTS = 0; // false
+ public static final String FALLBACK_TTS_DEFAULT_LANG = "eng";
+ public static final String FALLBACK_TTS_DEFAULT_COUNTRY = "";
+ public static final String FALLBACK_TTS_DEFAULT_VARIANT = "";
+
+ // return codes for a TTS engine's check data activity
+ public static final int CHECK_VOICE_DATA_PASS = 1;
+ public static final int CHECK_VOICE_DATA_FAIL = 0;
+ public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
+ public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
+ public static final int CHECK_VOICE_DATA_MISSING_DATA_NO_SDCARD = -3;
+
+ // return codes for a TTS engine's check data activity
+ public static final String VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+ public static final String VOICE_DATA_FILES = "dataFiles";
+ public static final String VOICE_DATA_FILES_INFO = "dataFilesInfo";
+
+ // keys for the parameters passed with speak commands
+ public static final String TTS_KEY_PARAM_RATE = "rate";
+ public static final String TTS_KEY_PARAM_LANGUAGE = "language";
+ public static final String TTS_KEY_PARAM_COUNTRY = "country";
+ public static final String TTS_KEY_PARAM_VARIANT = "variant";
+ public static final int TTS_PARAM_POSITION_RATE = 0;
+ public static final int TTS_PARAM_POSITION_LANGUAGE = 2;
+ public static final int TTS_PARAM_POSITION_COUNTRY = 4;
+ public static final int TTS_PARAM_POSITION_VARIANT = 6;
+ }
+
+ /**
+ * Connection needed for the TTS.
+ */
+ private ServiceConnection mServiceConnection;
+
+ private ITts mITts = null;
+ private Context mContext = null;
+ private OnInitListener mInitListener = null;
+ private boolean mStarted = false;
+ private final Object mStartLock = new Object();
+ /**
+ * Used to store the cached parameters sent along with each synthesis request to the
+ * TTS service.
+ */
+ private String[] mCachedParams;
+
+ /**
+ * The constructor for the TTS.
+ *
+ * @param context
+ * The context
+ * @param listener
+ * The InitListener that will be called when the TTS has
+ * initialized successfully.
+ */
+ public TextToSpeech(Context context, OnInitListener listener) {
+ mContext = context;
+ mInitListener = listener;
+
+ mCachedParams = new String[2*4]; // 4 parameters, store key and value
+ mCachedParams[Engine.TTS_PARAM_POSITION_RATE] = Engine.TTS_KEY_PARAM_RATE;
+ mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE] = Engine.TTS_KEY_PARAM_LANGUAGE;
+ mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY] = Engine.TTS_KEY_PARAM_COUNTRY;
+ mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT] = Engine.TTS_KEY_PARAM_VARIANT;
+
+ mCachedParams[Engine.TTS_PARAM_POSITION_RATE + 1] =
+ String.valueOf(Engine.FALLBACK_TTS_DEFAULT_RATE);
+ // initialize the language cached parameters with the current Locale
+ Locale defaultLoc = Locale.getDefault();
+ mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1] = defaultLoc.getISO3Language();
+ mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1] = defaultLoc.getISO3Country();
+ mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] = defaultLoc.getVariant();
+
+ initTts();
+ }
+
+
+ private void initTts() {
+ mStarted = false;
+
+ // Initialize the TTS, run the callback after the binding is successful
+ mServiceConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized(mStartLock) {
+ mITts = ITts.Stub.asInterface(service);
+ mStarted = true;
+ if (mInitListener != null) {
+ // TODO manage failures and missing resources
+ mInitListener.onInit(TTS_SUCCESS);
+ }
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized(mStartLock) {
+ mITts = null;
+ mInitListener = null;
+ mStarted = false;
+ }
+ }
+ };
+
+ Intent intent = new Intent("android.intent.action.START_TTS_SERVICE");
+ intent.addCategory("android.intent.category.TTS");
+ mContext.bindService(intent, mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ // TODO handle case where the binding works (should always work) but
+ // the plugin fails
+ }
+
+
+ /**
+ * Shuts down the TTS. It is good practice to call this in the onDestroy
+ * method of the Activity that is using the TTS so that the TTS is stopped
+ * cleanly.
+ */
+ public void shutdown() {
+ try {
+ mContext.unbindService(mServiceConnection);
+ } catch (IllegalArgumentException e) {
+ // Do nothing and fail silently since an error here indicates that
+ // binding never succeeded in the first place.
+ }
+ }
+
+
+ /**
+ * Adds a mapping between a string of text and a sound resource in a
+ * package.
+ *
+ * @see #TTS.speak(String text, int queueMode, String[] params)
+ *
+ * @param text
+ * Example: <b><code>"south_south_east"</code></b><br/>
+ *
+ * @param packagename
+ * Pass the packagename of the application that contains the
+ * resource. If the resource is in your own application (this is
+ * the most common case), then put the packagename of your
+ * application here.<br/>
+ * Example: <b>"com.google.marvin.compass"</b><br/>
+ * The packagename can be found in the AndroidManifest.xml of
+ * your application.
+ * <p>
+ * <code>&lt;manifest xmlns:android=&quot;...&quot;
+ * package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
+ * </p>
+ *
+ * @param resourceId
+ * Example: <b><code>R.raw.south_south_east</code></b>
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int addSpeech(String text, String packagename, int resourceId) {
+ synchronized(mStartLock) {
+ if (!mStarted) {
+ return TTS_ERROR;
+ }
+ try {
+ mITts.addSpeech(text, packagename, resourceId);
+ return TTS_SUCCESS;
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ }
+ return TTS_ERROR;
+ }
+ }
+
+
+ /**
+ * Adds a mapping between a string of text and a sound file. Using this, it
+ * is possible to add custom pronounciations for text.
+ *
+ * @param text
+ * The string of text
+ * @param filename
+ * The full path to the sound file (for example:
+ * "/sdcard/mysounds/hello.wav")
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int addSpeech(String text, String filename) {
+ synchronized (mStartLock) {
+ if (!mStarted) {
+ return TTS_ERROR;
+ }
+ try {
+ mITts.addSpeechFile(text, filename);
+ return TTS_SUCCESS;
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ }
+ return TTS_ERROR;
+ }
+ }
+
+
+ /**
+ * Speaks the string using the specified queuing strategy and speech
+ * parameters. Note that the speech parameters are not universally supported
+ * by all engines and will be treated as a hint. The TTS library will try to
+ * fulfill these parameters as much as possible, but there is no guarantee
+ * that the voice used will have the properties specified.
+ *
+ * @param text
+ * The string of text to be spoken.
+ * @param queueMode
+ * The queuing strategy to use.
+ * See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH.
+ * @param params
+ * The hashmap of speech parameters to be used.
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int speak(String text, int queueMode, HashMap<String,String> params)
+ {
+ synchronized (mStartLock) {
+ int result = TTS_ERROR;
+ Log.i("TTS received: ", text);
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ // TODO support extra parameters, passing cache of current parameters for the moment
+ result = mITts.speak(text, queueMode, mCachedParams);
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+
+ /**
+ * Plays the earcon using the specified queueing mode and parameters.
+ *
+ * @param earcon
+ * The earcon that should be played
+ * @param queueMode
+ * See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH.
+ * @param params
+ * The hashmap of parameters to be used.
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int playEarcon(String earcon, int queueMode,
+ HashMap<String,String> params) {
+ synchronized (mStartLock) {
+ int result = TTS_ERROR;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ // TODO support extra parameters, passing null for the moment
+ result = mITts.playEarcon(earcon, queueMode, null);
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+ /**
+ * Plays silence for the specified amount of time using the specified
+ * queue mode.
+ *
+ * @param durationInMs
+ * A long that indicates how long the silence should last.
+ * @param queueMode
+ * See TTS_QUEUE_ADD and TTS_QUEUE_FLUSH.
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int playSilence(long durationInMs, int queueMode) {
+ synchronized (mStartLock) {
+ int result = TTS_ERROR;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ // TODO support extra parameters, passing cache of current parameters for the moment
+ result = mITts.playSilence(durationInMs, queueMode, mCachedParams);
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+
+ /**
+ * Returns whether or not the TTS is busy speaking.
+ *
+ * @return Whether or not the TTS is busy speaking.
+ */
+ public boolean isSpeaking() {
+ synchronized (mStartLock) {
+ if (!mStarted) {
+ return false;
+ }
+ try {
+ return mITts.isSpeaking();
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ }
+ return false;
+ }
+ }
+
+
+ /**
+ * Stops speech from the TTS.
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int stop() {
+ synchronized (mStartLock) {
+ int result = TTS_ERROR;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ result = mITts.stop();
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+
+ /**
+ * Sets the speech rate for the TTS engine.
+ *
+ * Note that the speech rate is not universally supported by all engines and
+ * will be treated as a hint. The TTS library will try to use the specified
+ * speech rate, but there is no guarantee.
+ * This has no effect on any pre-recorded speech.
+ *
+ * @param speechRate
+ * The speech rate for the TTS engine. 1 is the normal speed,
+ * lower values slow down the speech (0.5 is half the normal speech rate),
+ * greater values accelerate it (2 is twice the normal speech rate).
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int setSpeechRate(float speechRate) {
+ synchronized (mStartLock) {
+ int result = TTS_ERROR;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ if (speechRate > 0) {
+ int rate = (int)(speechRate*100);
+ mCachedParams[Engine.TTS_PARAM_POSITION_RATE + 1] = String.valueOf(rate);
+ result = mITts.setSpeechRate(rate);
+ }
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+
+ /**
+ * Sets the speech pitch for the TTS engine.
+ *
+ * Note that the pitch is not universally supported by all engines and
+ * will be treated as a hint. The TTS library will try to use the specified
+ * pitch, but there is no guarantee.
+ * This has no effect on any pre-recorded speech.
+ *
+ * @param pitch
+ * The pitch for the TTS engine. 1 is the normal pitch,
+ * lower values lower the tone of the synthesized voice,
+ * greater values increase it.
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int setPitch(float pitch) {
+ synchronized (mStartLock) {
+ int result = TTS_ERROR;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ if (pitch > 0) {
+ result = mITts.setPitch((int)(pitch*100));
+ }
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+
+ /**
+ * Sets the language for the TTS engine.
+ *
+ * Note that the language is not universally supported by all engines and
+ * will be treated as a hint. The TTS library will try to use the specified
+ * language as represented by the Locale, but there is no guarantee.
+ *
+ * @param loc
+ * The locale describing the language to be used.
+ *
+ * @return Code indicating the support status for the locale. See the TTS_LANG_ codes.
+ */
+ public int setLanguage(Locale loc) {
+ synchronized (mStartLock) {
+ int result = TTS_LANG_NOT_SUPPORTED;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1] = loc.getISO3Language();
+ mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1] = loc.getISO3Country();
+ mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] = loc.getVariant();
+ result = mITts.setLanguage(mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1],
+ mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1],
+ mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] );
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+
+ /**
+ * Returns a Locale instance describing the language currently being used by the TTS engine.
+ * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
+ * instance, or null is the TTS engine has failed.
+ */
+ public Locale getLanguage() {
+ synchronized (mStartLock) {
+ if (!mStarted) {
+ return null;
+ }
+ try {
+ String[] locStrings = mITts.getLanguage();
+ if (locStrings.length == 3) {
+ return new Locale(locStrings[0], locStrings[1], locStrings[2]);
+ } else {
+ return null;
+ }
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the specified language as represented by the Locale is available.
+ *
+ * @param loc
+ * The Locale describing the language to be used.
+ *
+ * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE,
+ * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE.
+ */
+ public int isLanguageAvailable(Locale loc) {
+ synchronized (mStartLock) {
+ int result = TTS_LANG_NOT_SUPPORTED;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ result = mITts.isLanguageAvailable(loc.getISO3Language(),
+ loc.getISO3Country(), loc.getVariant());
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+
+ /**
+ * Synthesizes the given text to a file using the specified parameters.
+ *
+ * @param text
+ * The String of text that should be synthesized
+ * @param params
+ * A hashmap of parameters.
+ * @param filename
+ * The string that gives the full output filename; it should be
+ * something like "/sdcard/myappsounds/mysound.wav".
+ *
+ * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS.
+ */
+ public int synthesizeToFile(String text, HashMap<String,String> params,
+ String filename) {
+ synchronized (mStartLock) {
+ int result = TTS_ERROR;
+ if (!mStarted) {
+ return result;
+ }
+ try {
+ // TODO support extra parameters, passing null for the moment
+ if (mITts.synthesizeToFile(text, null, filename)){
+ result = TTS_SUCCESS;
+ }
+ } catch (RemoteException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (NullPointerException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } catch (IllegalStateException e) {
+ // TTS died; restart it.
+ mStarted = false;
+ initTts();
+ } finally {
+ return result;
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/syncml/pim/PropertyNode.java b/core/java/android/syncml/pim/PropertyNode.java
index cc52499..983ecb8 100644
--- a/core/java/android/syncml/pim/PropertyNode.java
+++ b/core/java/android/syncml/pim/PropertyNode.java
@@ -17,12 +17,16 @@
package android.syncml.pim;
import android.content.ContentValues;
-import android.util.Log;
+import org.apache.commons.codec.binary.Base64;
+
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
public class PropertyNode {
@@ -52,7 +56,9 @@ public class PropertyNode {
public Set<String> propGroupSet;
public PropertyNode() {
+ propName = "";
propValue = "";
+ propValue_vector = new ArrayList<String>();
paramMap = new ContentValues();
paramMap_TYPE = new HashSet<String>();
propGroupSet = new HashSet<String>();
@@ -62,13 +68,21 @@ public class PropertyNode {
String propName, String propValue, List<String> propValue_vector,
byte[] propValue_bytes, ContentValues paramMap, Set<String> paramMap_TYPE,
Set<String> propGroupSet) {
- this.propName = propName;
+ if (propName != null) {
+ this.propName = propName;
+ } else {
+ this.propName = "";
+ }
if (propValue != null) {
this.propValue = propValue;
} else {
this.propValue = "";
}
- this.propValue_vector = propValue_vector;
+ if (propValue_vector != null) {
+ this.propValue_vector = propValue_vector;
+ } else {
+ this.propValue_vector = new ArrayList<String>();
+ }
this.propValue_bytes = propValue_bytes;
if (paramMap != null) {
this.paramMap = paramMap;
@@ -117,17 +131,9 @@ public class PropertyNode {
// decoded by BASE64 or QUOTED-PRINTABLE. When the size of propValue_vector
// is 1, the encoded value is stored in propValue, so we do not have to
// check it.
- if (propValue_vector != null) {
- // Log.d("@@@", "===" + propValue_vector + ", " + node.propValue_vector);
- return (propValue_vector.equals(node.propValue_vector) ||
- (propValue_vector.size() == 1));
- } else if (node.propValue_vector != null) {
- // Log.d("@@@", "===" + propValue_vector + ", " + node.propValue_vector);
- return (node.propValue_vector.equals(propValue_vector) ||
- (node.propValue_vector.size() == 1));
- } else {
- return true;
- }
+ return (propValue_vector.equals(node.propValue_vector) ||
+ propValue_vector.size() == 1 ||
+ node.propValue_vector.size() == 1);
}
}
@@ -154,4 +160,164 @@ public class PropertyNode {
builder.append(propValue);
return builder.toString();
}
+
+ /**
+ * Encode this object into a string which can be decoded.
+ */
+ public String encode() {
+ // PropertyNode#toString() is for reading, not for parsing in the future.
+ // We construct appropriate String here.
+ StringBuilder builder = new StringBuilder();
+ if (propName.length() > 0) {
+ builder.append("propName:[");
+ builder.append(propName);
+ builder.append("],");
+ }
+ int size = propGroupSet.size();
+ if (size > 0) {
+ Set<String> set = propGroupSet;
+ builder.append("propGroup:[");
+ int i = 0;
+ for (String group : set) {
+ // We do not need to double quote groups.
+ // group = 1*(ALPHA / DIGIT / "-")
+ builder.append(group);
+ if (i < size - 1) {
+ builder.append(",");
+ }
+ i++;
+ }
+ builder.append("],");
+ }
+
+ if (paramMap.size() > 0 || paramMap_TYPE.size() > 0) {
+ ContentValues values = paramMap;
+ builder.append("paramMap:[");
+ size = paramMap.size();
+ int i = 0;
+ for (Entry<String, Object> entry : values.valueSet()) {
+ // Assuming param-key does not contain NON-ASCII nor symbols.
+ //
+ // According to vCard 3.0:
+ // param-name = iana-token / x-name
+ builder.append(entry.getKey());
+
+ // param-value may contain any value including NON-ASCIIs.
+ // We use the following replacing rule.
+ // \ -> \\
+ // , -> \,
+ // In String#replaceAll(), "\\\\" means a single backslash.
+ builder.append("=");
+ builder.append(entry.getValue().toString()
+ .replaceAll("\\\\", "\\\\\\\\")
+ .replaceAll(",", "\\\\,"));
+ if (i < size -1) {
+ builder.append(",");
+ }
+ i++;
+ }
+
+ Set<String> set = paramMap_TYPE;
+ size = paramMap_TYPE.size();
+ if (i > 0 && size > 0) {
+ builder.append(",");
+ }
+ i = 0;
+ for (String type : set) {
+ builder.append("TYPE=");
+ builder.append(type
+ .replaceAll("\\\\", "\\\\\\\\")
+ .replaceAll(",", "\\\\,"));
+ if (i < size - 1) {
+ builder.append(",");
+ }
+ i++;
+ }
+ builder.append("],");
+ }
+
+ size = propValue_vector.size();
+ if (size > 0) {
+ builder.append("propValue:[");
+ List<String> list = propValue_vector;
+ for (int i = 0; i < size; i++) {
+ builder.append(list.get(i)
+ .replaceAll("\\\\", "\\\\\\\\")
+ .replaceAll(",", "\\\\,"));
+ if (i < size -1) {
+ builder.append(",");
+ }
+ }
+ builder.append("],");
+ }
+
+ return builder.toString();
+ }
+
+ public static PropertyNode decode(String encodedString) {
+ PropertyNode propertyNode = new PropertyNode();
+ String trimed = encodedString.trim();
+ if (trimed.length() == 0) {
+ return propertyNode;
+ }
+ String[] elems = trimed.split("],");
+
+ for (String elem : elems) {
+ int index = elem.indexOf('[');
+ String name = elem.substring(0, index - 1);
+ Pattern pattern = Pattern.compile("(?<!\\\\),");
+ String[] values = pattern.split(elem.substring(index + 1), -1);
+ if (name.equals("propName")) {
+ propertyNode.propName = values[0];
+ } else if (name.equals("propGroupSet")) {
+ for (String value : values) {
+ propertyNode.propGroupSet.add(value);
+ }
+ } else if (name.equals("paramMap")) {
+ ContentValues paramMap = propertyNode.paramMap;
+ Set<String> paramMap_TYPE = propertyNode.paramMap_TYPE;
+ for (String value : values) {
+ String[] tmp = value.split("=", 2);
+ String mapKey = tmp[0];
+ // \, -> ,
+ // \\ -> \
+ // In String#replaceAll(), "\\\\" means a single backslash.
+ String mapValue =
+ tmp[1].replaceAll("\\\\,", ",").replaceAll("\\\\\\\\", "\\\\");
+ if (mapKey.equalsIgnoreCase("TYPE")) {
+ paramMap_TYPE.add(mapValue);
+ } else {
+ paramMap.put(mapKey, mapValue);
+ }
+ }
+ } else if (name.equals("propValue")) {
+ StringBuilder builder = new StringBuilder();
+ List<String> list = propertyNode.propValue_vector;
+ int length = values.length;
+ for (int i = 0; i < length; i++) {
+ String normValue = values[i]
+ .replaceAll("\\\\,", ",")
+ .replaceAll("\\\\\\\\", "\\\\");
+ list.add(normValue);
+ builder.append(normValue);
+ if (i < length - 1) {
+ builder.append(";");
+ }
+ }
+ propertyNode.propValue = builder.toString();
+ }
+ }
+
+ // At this time, QUOTED-PRINTABLE is already decoded to Java String.
+ // We just need to decode BASE64 String to binary.
+ String encoding = propertyNode.paramMap.getAsString("ENCODING");
+ if (encoding != null &&
+ (encoding.equalsIgnoreCase("BASE64") ||
+ encoding.equalsIgnoreCase("B"))) {
+ propertyNode.propValue_bytes =
+ Base64.decodeBase64(propertyNode.propValue_vector.get(0).getBytes());
+ }
+
+ return propertyNode;
+ }
}
diff --git a/core/java/android/syncml/pim/VBuilderCollection.java b/core/java/android/syncml/pim/VBuilderCollection.java
new file mode 100644
index 0000000..f09c1c4
--- /dev/null
+++ b/core/java/android/syncml/pim/VBuilderCollection.java
@@ -0,0 +1,100 @@
+/*
+ * 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.syncml.pim;
+
+import java.util.Collection;
+import java.util.List;
+
+public class VBuilderCollection implements VBuilder {
+
+ private final Collection<VBuilder> mVBuilderCollection;
+
+ public VBuilderCollection(Collection<VBuilder> vBuilderCollection) {
+ mVBuilderCollection = vBuilderCollection;
+ }
+
+ public Collection<VBuilder> getVBuilderCollection() {
+ return mVBuilderCollection;
+ }
+
+ public void start() {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.start();
+ }
+ }
+
+ public void end() {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.end();
+ }
+ }
+
+ public void startRecord(String type) {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.startRecord(type);
+ }
+ }
+
+ public void endRecord() {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.endRecord();
+ }
+ }
+
+ public void startProperty() {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.startProperty();
+ }
+ }
+
+
+ public void endProperty() {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.endProperty();
+ }
+ }
+
+ public void propertyGroup(String group) {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.propertyGroup(group);
+ }
+ }
+
+ public void propertyName(String name) {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.propertyName(name);
+ }
+ }
+
+ public void propertyParamType(String type) {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.propertyParamType(type);
+ }
+ }
+
+ public void propertyParamValue(String value) {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.propertyParamValue(value);
+ }
+ }
+
+ public void propertyValues(List<String> values) {
+ for (VBuilder builder : mVBuilderCollection) {
+ builder.propertyValues(values);
+ }
+ }
+}
diff --git a/core/java/android/syncml/pim/VDataBuilder.java b/core/java/android/syncml/pim/VDataBuilder.java
index 8c67cf5..f6e5b65 100644
--- a/core/java/android/syncml/pim/VDataBuilder.java
+++ b/core/java/android/syncml/pim/VDataBuilder.java
@@ -17,8 +17,10 @@
package android.syncml.pim;
import android.content.ContentValues;
+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;
@@ -26,9 +28,7 @@ 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;
-import java.util.Vector;
/**
* Store the parse result to custom datastruct: VNode, PropertyNode
@@ -38,7 +38,13 @@ import java.util.Vector;
*/
public class VDataBuilder implements VBuilder {
static private String LOG_TAG = "VDATABuilder";
-
+
+ /**
+ * If there's no other information available, this class uses this charset for encoding
+ * byte arrays.
+ */
+ static public String DEFAULT_CHARSET = "UTF-8";
+
/** type=VNode */
public List<VNode> vNodeList = new ArrayList<VNode>();
private int mNodeListPos = 0;
@@ -47,34 +53,74 @@ public class VDataBuilder implements VBuilder {
private String mCurrentParamType;
/**
- * Assumes that each String can be encoded into byte array using this encoding.
+ * The charset using which VParser parses the text.
+ */
+ private String mSourceCharset;
+
+ /**
+ * The charset with which byte array is encoded to String.
*/
- private String mCharset;
+ private String mTargetCharset;
private boolean mStrictLineBreakParsing;
public VDataBuilder() {
- mCharset = "ISO-8859-1";
- mStrictLineBreakParsing = false;
+ this(VParser.DEFAULT_CHARSET, DEFAULT_CHARSET, false);
}
- public VDataBuilder(String encoding, boolean strictLineBreakParsing) {
- mCharset = encoding;
- mStrictLineBreakParsing = strictLineBreakParsing;
+ public VDataBuilder(String charset, boolean strictLineBreakParsing) {
+ this(null, charset, strictLineBreakParsing);
}
+ /**
+ * @hide sourceCharset is temporal.
+ */
+ public VDataBuilder(String sourceCharset, String targetCharset,
+ boolean strictLineBreakParsing) {
+ if (sourceCharset != null) {
+ mSourceCharset = sourceCharset;
+ } else {
+ mSourceCharset = VParser.DEFAULT_CHARSET;
+ }
+ if (targetCharset != null) {
+ mTargetCharset = targetCharset;
+ } else {
+ mTargetCharset = DEFAULT_CHARSET;
+ }
+ mStrictLineBreakParsing = strictLineBreakParsing;
+ }
+
public void start() {
}
public void end() {
}
+ // Note: I guess that this code assumes the Record may nest like this:
+ // START:VPOS
+ // ...
+ // START:VPOS2
+ // ...
+ // END:VPOS2
+ // ...
+ // END:VPOS
+ //
+ // However the following code has a bug.
+ // When error occurs after calling startRecord(), the entry which is probably
+ // the cause of the error remains to be in vNodeList, while endRecord() is not called.
+ //
+ // I leave this code as is since I'm not familiar with vcalendar specification.
+ // But I believe we should refactor this code in the future.
+ // Until this, the last entry has to be removed when some error occurs.
public void startRecord(String type) {
+
VNode vnode = new VNode();
vnode.parseStatus = 1;
vnode.VName = type;
+ // I feel this should be done in endRecord(), but it cannot be done because of
+ // the reason above.
vNodeList.add(vnode);
- mNodeListPos = vNodeList.size()-1;
+ mNodeListPos = vNodeList.size() - 1;
mCurrentVNode = vNodeList.get(mNodeListPos);
}
@@ -90,15 +136,14 @@ public class VDataBuilder implements VBuilder {
}
public void startProperty() {
- // System.out.println("+ startProperty. ");
+ mCurrentPropNode = new PropertyNode();
}
public void endProperty() {
- // System.out.println("- endProperty. ");
+ mCurrentVNode.propList.add(mCurrentPropNode);
}
public void propertyName(String name) {
- mCurrentPropNode = new PropertyNode();
mCurrentPropNode.propName = name;
}
@@ -122,139 +167,145 @@ public class VDataBuilder implements VBuilder {
mCurrentParamType = null;
}
- private String encodeString(String originalString, String targetEncoding) {
- Charset charset = Charset.forName(mCharset);
+ private String encodeString(String originalString, String targetCharset) {
+ if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
+ return originalString;
+ }
+ Charset charset = Charset.forName(mSourceCharset);
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, targetEncoding);
+ return new String(bytes, targetCharset);
} catch (UnsupportedEncodingException e) {
- return null;
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(bytes);
}
}
- public void propertyValues(List<String> values) {
- ContentValues paramMap = mCurrentPropNode.paramMap;
-
- String charsetString = paramMap.getAsString("CHARSET");
-
- boolean setupParamValues = false;
- //decode value string to propValue_bytes
- if (paramMap.containsKey("ENCODING")) {
- String encoding = paramMap.getAsString("ENCODING");
- if (encoding.equalsIgnoreCase("BASE64") ||
- encoding.equalsIgnoreCase("B")) {
- if (values.size() > 1) {
- Log.e(LOG_TAG,
- ("BASE64 encoding is used while " +
- "there are multiple values (" + values.size()));
- }
+ private String handleOneValue(String value, String targetCharset, String encoding) {
+ if (encoding != null) {
+ if (encoding.equals("BASE64") || encoding.equals("B")) {
+ // Assume BASE64 is used only when the number of values is 1.
mCurrentPropNode.propValue_bytes =
- Base64.decodeBase64(values.get(0).
- replaceAll(" ","").replaceAll("\t","").
- replaceAll("\r\n","").
- getBytes());
- }
-
- if(encoding.equalsIgnoreCase("QUOTED-PRINTABLE")){
- // if CHARSET is defined, we translate each String into the Charset.
- List<String> tmpValues = new ArrayList<String>();
- Vector<byte[]> byteVector = new Vector<byte[]>();
- int size = 0;
- try{
- for (String value : values) {
- String quotedPrintable = value
- .replaceAll("= ", " ").replaceAll("=\t", "\t");
- String[] lines;
- if (mStrictLineBreakParsing) {
- lines = quotedPrintable.split("\r\n");
- } else {
- lines = quotedPrintable
- .replace("\r\n", "\n").replace("\r", "\n").split("\n");
- }
- StringBuilder builder = new StringBuilder();
- for (String line : lines) {
- if (line.endsWith("=")) {
- line = line.substring(0, line.length() - 1);
- }
- builder.append(line);
- }
- byte[] bytes = QuotedPrintableCodec.decodeQuotedPrintable(
- builder.toString().getBytes());
- if (charsetString != null) {
- try {
- tmpValues.add(new String(bytes, charsetString));
- } catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Failed to encode: charset=" + charsetString);
- tmpValues.add(new String(bytes));
+ Base64.decodeBase64(value.getBytes());
+ return value;
+ } else if (encoding.equals("QUOTED-PRINTABLE")) {
+ String quotedPrintable = value
+ .replaceAll("= ", " ").replaceAll("=\t", "\t");
+ String[] lines;
+ if (mStrictLineBreakParsing) {
+ lines = quotedPrintable.split("\r\n");
+ } else {
+ StringBuilder builder = new StringBuilder();
+ 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 {
- tmpValues.add(new String(bytes));
- }
- byteVector.add(bytes);
- size += bytes.length;
- } // for (String value : values) {
- mCurrentPropNode.propValue_vector = tmpValues;
- mCurrentPropNode.propValue = listToString(tmpValues);
-
- mCurrentPropNode.propValue_bytes = new byte[size];
-
- {
- byte[] tmpBytes = mCurrentPropNode.propValue_bytes;
- int index = 0;
- for (byte[] bytes : byteVector) {
- int length = bytes.length;
- for (int i = 0; i < length; i++, index++) {
- tmpBytes[index] = bytes[i];
- }
+ builder.append(ch);
}
}
- setupParamValues = true;
- } catch(Exception e) {
- Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
+ String finalLine = builder.toString();
+ if (finalLine.length() > 0) {
+ list.add(finalLine);
+ }
+ lines = list.toArray(new String[0]);
}
- } // QUOTED-PRINTABLE
- } // ENCODING
-
- if (!setupParamValues) {
- // if CHARSET is defined, we translate each String into the Charset.
- if (charsetString != null) {
- List<String> tmpValues = new ArrayList<String>();
- for (String value : values) {
- String result = encodeString(value, charsetString);
- if (result != null) {
- tmpValues.add(result);
- } else {
- Log.e(LOG_TAG, "Failed to encode: charset=" + charsetString);
- tmpValues.add(value);
+ StringBuilder 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(mSourceCharset);
+ } catch (UnsupportedEncodingException e1) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
+ 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, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(bytes);
}
- values = tmpValues;
}
-
- mCurrentPropNode.propValue_vector = values;
- mCurrentPropNode.propValue = listToString(values);
+ // Unknown encoding. Fall back to default.
}
- mCurrentVNode.propList.add(mCurrentPropNode);
+ return encodeString(value, targetCharset);
}
-
- private String listToString(Collection<String> list){
- StringBuilder typeListB = new StringBuilder();
- for (String type : list) {
- typeListB.append(type).append(";");
+
+ public void propertyValues(List<String> values) {
+ if (values == null || values.size() == 0) {
+ mCurrentPropNode.propValue_bytes = null;
+ mCurrentPropNode.propValue_vector.clear();
+ mCurrentPropNode.propValue_vector.add("");
+ mCurrentPropNode.propValue = "";
+ return;
+ }
+
+ ContentValues paramMap = mCurrentPropNode.paramMap;
+
+ String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET"));
+ String encoding = paramMap.getAsString("ENCODING");
+
+ if (targetCharset == null || targetCharset.length() == 0) {
+ targetCharset = mTargetCharset;
+ }
+
+ for (String value : values) {
+ mCurrentPropNode.propValue_vector.add(
+ handleOneValue(value, targetCharset, encoding));
}
- int len = typeListB.length();
- if (len > 0 && typeListB.charAt(len - 1) == ';') {
- return typeListB.substring(0, len - 1);
+
+ mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector);
+ }
+
+ private String listToString(List<String> list){
+ int size = list.size();
+ if (size > 1) {
+ StringBuilder typeListB = new StringBuilder();
+ for (String type : list) {
+ typeListB.append(type).append(";");
+ }
+ int len = typeListB.length();
+ if (len > 0 && typeListB.charAt(len - 1) == ';') {
+ return typeListB.substring(0, len - 1);
+ }
+ return typeListB.toString();
+ } else if (size == 1) {
+ return list.get(0);
+ } else {
+ return "";
}
- return typeListB.toString();
}
public String getResult(){
return null;
}
}
-
diff --git a/core/java/android/syncml/pim/VParser.java b/core/java/android/syncml/pim/VParser.java
index df93f38..57c5f7a 100644
--- a/core/java/android/syncml/pim/VParser.java
+++ b/core/java/android/syncml/pim/VParser.java
@@ -26,6 +26,9 @@ import java.io.UnsupportedEncodingException;
*
*/
abstract public class VParser {
+ // Assume 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 String DEFAULT_CHARSET = "iso-8859-1";
/**
* The buffer used to store input stream
@@ -96,6 +99,20 @@ abstract public class VParser {
}
/**
+ * Parse the given stream with the default encoding.
+ *
+ * @param is
+ * The source to parse.
+ * @param builder
+ * The v builder which used to construct data.
+ * @return Return true for success, otherwise false.
+ * @throws IOException
+ */
+ public boolean parse(InputStream is, VBuilder builder) throws IOException {
+ return parse(is, DEFAULT_CHARSET, builder);
+ }
+
+ /**
* Copy the content of input stream and filter the "folding"
*/
protected void setInputStream(InputStream is, String encoding)
diff --git a/core/java/android/syncml/pim/vcard/ContactStruct.java b/core/java/android/syncml/pim/vcard/ContactStruct.java
index 8d9b7fa..ecd719d 100644
--- a/core/java/android/syncml/pim/vcard/ContactStruct.java
+++ b/core/java/android/syncml/pim/vcard/ContactStruct.java
@@ -16,45 +16,103 @@
package android.syncml.pim.vcard;
-import java.util.List;
+import android.content.AbstractSyncableContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.Contacts;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.Extensions;
+import android.provider.Contacts.GroupMembership;
+import android.provider.Contacts.Organizations;
+import android.provider.Contacts.People;
+import android.provider.Contacts.Phones;
+import android.provider.Contacts.Photos;
+import android.syncml.pim.PropertyNode;
+import android.syncml.pim.VNode;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
/**
- * The parameter class of VCardCreator.
+ * The parameter class of VCardComposer.
* This class standy by the person-contact in
* Android system, we must use this class instance as parameter to transmit to
- * VCardCreator so that create vCard string.
+ * VCardComposer so that create vCard string.
*/
// TODO: rename the class name, next step
public class ContactStruct {
- public String company;
+ private static final String LOG_TAG = "ContactStruct";
+
+ // Note: phonetic name probably should be "LAST FIRST MIDDLE" for European languages, and
+ // space should be added between each element while it should not be in Japanese.
+ // But unfortunately, we currently do not have the data and are not sure whether we should
+ // support European version of name ordering.
+ //
+ // TODO: Implement the logic described above if we really need European version of
+ // phonetic name handling. Also, adding the appropriate test case of vCard would be
+ // highly appreciated.
+ public static final int NAME_ORDER_TYPE_ENGLISH = 0;
+ public static final int NAME_ORDER_TYPE_JAPANESE = 1;
+
/** MUST exist */
public String name;
+ public String phoneticName;
/** maybe folding */
- public String notes;
+ public List<String> notes = new ArrayList<String>();
/** maybe folding */
public String title;
/** binary bytes of pic. */
public byte[] photoBytes;
- /** mime_type col of images table */
+ /** The type of Photo (e.g. JPEG, BMP, etc.) */
public String photoType;
/** Only for GET. Use addPhoneList() to PUT. */
public List<PhoneData> phoneList;
/** Only for GET. Use addContactmethodList() to PUT. */
public List<ContactMethod> contactmethodList;
+ /** Only for GET. Use addOrgList() to PUT. */
+ public List<OrganizationData> organizationList;
+ /** Only for GET. Use addExtension() to PUT */
+ public Map<String, List<String>> extensionMap;
- public static class PhoneData{
+ // Use organizationList instead when handling ORG.
+ @Deprecated
+ public String company;
+
+ public static class PhoneData {
+ public int type;
/** maybe folding */
public String data;
- public String type;
public String label;
+ public boolean isPrimary;
}
- public static class ContactMethod{
- public String kind;
- public String type;
+ public static class ContactMethod {
+ // Contacts.KIND_EMAIL, Contacts.KIND_POSTAL
+ public int kind;
+ // e.g. Contacts.ContactMethods.TYPE_HOME, Contacts.PhoneColumns.TYPE_HOME
+ // If type == Contacts.PhoneColumns.TYPE_CUSTOM, label is used.
+ public int type;
public String data;
+ // Used only when TYPE is TYPE_CUSTOM.
public String label;
+ public boolean isPrimary;
+ }
+
+ public static class OrganizationData {
+ public int type;
+ public String companyName;
+ public String positionName;
+ public boolean isPrimary;
}
/**
@@ -63,29 +121,858 @@ public class ContactStruct {
* @param type type col of content://contacts/phones
* @param label lable col of content://contacts/phones
*/
- public void addPhone(String data, String type, String label){
- if(phoneList == null)
+ public void addPhone(int type, String data, String label, boolean isPrimary){
+ if (phoneList == null) {
phoneList = new ArrayList<PhoneData>();
- PhoneData st = new PhoneData();
- st.data = data;
- st.type = type;
- st.label = label;
- phoneList.add(st);
+ }
+ PhoneData phoneData = new PhoneData();
+ phoneData.type = type;
+
+ StringBuilder builder = new StringBuilder();
+ String trimed = data.trim();
+ int length = trimed.length();
+ for (int i = 0; i < length; i++) {
+ char ch = trimed.charAt(i);
+ if (('0' <= ch && ch <= '9') || (i == 0 && ch == '+')) {
+ builder.append(ch);
+ }
+ }
+ phoneData.data = PhoneNumberUtils.formatNumber(builder.toString());
+ phoneData.label = label;
+ phoneData.isPrimary = isPrimary;
+ phoneList.add(phoneData);
}
+
/**
* Add a contactmethod info to contactmethodList.
- * @param data contact data
+ * @param kind integer value defined in Contacts.java
+ * (e.g. Contacts.KIND_EMAIL)
* @param type type col of content://contacts/contact_methods
+ * @param data contact data
+ * @param label extra string used only when kind is Contacts.KIND_CUSTOM.
*/
- public void addContactmethod(String kind, String data, String type,
- String label){
- if(contactmethodList == null)
+ public void addContactmethod(int kind, int type, String data,
+ String label, boolean isPrimary){
+ if (contactmethodList == null) {
contactmethodList = new ArrayList<ContactMethod>();
- ContactMethod st = new ContactMethod();
- st.kind = kind;
- st.data = data;
- st.type = type;
- st.label = label;
- contactmethodList.add(st);
+ }
+ ContactMethod contactMethod = new ContactMethod();
+ contactMethod.kind = kind;
+ contactMethod.type = type;
+ contactMethod.data = data;
+ contactMethod.label = label;
+ contactMethod.isPrimary = isPrimary;
+ contactmethodList.add(contactMethod);
+ }
+
+ /**
+ * Add a Organization info to organizationList.
+ */
+ public void addOrganization(int type, String companyName, String positionName,
+ boolean isPrimary) {
+ if (organizationList == null) {
+ organizationList = new ArrayList<OrganizationData>();
+ }
+ OrganizationData organizationData = new OrganizationData();
+ organizationData.type = type;
+ organizationData.companyName = companyName;
+ organizationData.positionName = positionName;
+ organizationData.isPrimary = isPrimary;
+ organizationList.add(organizationData);
+ }
+
+ /**
+ * Set "position" value to the appropriate data. If there's more than one
+ * OrganizationData objects, the value is set to the last one. If there's no
+ * OrganizationData object, a new OrganizationData is created, whose company name is
+ * empty.
+ *
+ * TODO: incomplete logic. fix this:
+ *
+ * e.g. This assumes ORG comes earlier, but TITLE may come earlier like this, though we do not
+ * know how to handle it in general cases...
+ * ----
+ * TITLE:Software Engineer
+ * ORG:Google
+ * ----
+ */
+ public void setPosition(String positionValue) {
+ if (organizationList == null) {
+ organizationList = new ArrayList<OrganizationData>();
+ }
+ int size = organizationList.size();
+ if (size == 0) {
+ addOrganization(Contacts.OrganizationColumns.TYPE_OTHER, "", null, false);
+ size = 1;
+ }
+ OrganizationData lastData = organizationList.get(size - 1);
+ lastData.positionName = positionValue;
+ }
+
+ public void addExtension(PropertyNode propertyNode) {
+ if (propertyNode.propValue.length() == 0) {
+ return;
+ }
+ // Now store the string into extensionMap.
+ List<String> list;
+ String name = propertyNode.propName;
+ if (extensionMap == null) {
+ extensionMap = new HashMap<String, List<String>>();
+ }
+ if (!extensionMap.containsKey(name)){
+ list = new ArrayList<String>();
+ extensionMap.put(name, list);
+ } else {
+ list = extensionMap.get(name);
+ }
+
+ list.add(propertyNode.encode());
+ }
+
+ private static String getNameFromNProperty(List<String> elems, int nameOrderType) {
+ // Family, Given, Middle, Prefix, Suffix. (1 - 5)
+ int size = elems.size();
+ if (size > 1) {
+ StringBuilder builder = new StringBuilder();
+ boolean builderIsEmpty = true;
+ // Prefix
+ if (size > 3 && elems.get(3).length() > 0) {
+ builder.append(elems.get(3));
+ builderIsEmpty = false;
+ }
+ String first, second;
+ if (nameOrderType == NAME_ORDER_TYPE_JAPANESE) {
+ first = elems.get(0);
+ second = elems.get(1);
+ } else {
+ first = elems.get(1);
+ second = elems.get(0);
+ }
+ if (first.length() > 0) {
+ if (!builderIsEmpty) {
+ builder.append(' ');
+ }
+ builder.append(first);
+ builderIsEmpty = false;
+ }
+ // Middle name
+ if (size > 2 && elems.get(2).length() > 0) {
+ if (!builderIsEmpty) {
+ builder.append(' ');
+ }
+ builder.append(elems.get(2));
+ builderIsEmpty = false;
+ }
+ if (second.length() > 0) {
+ if (!builderIsEmpty) {
+ builder.append(' ');
+ }
+ builder.append(second);
+ builderIsEmpty = false;
+ }
+ // Suffix
+ if (size > 4 && elems.get(4).length() > 0) {
+ if (!builderIsEmpty) {
+ builder.append(' ');
+ }
+ builder.append(elems.get(4));
+ builderIsEmpty = false;
+ }
+ return builder.toString();
+ } else if (size == 1) {
+ return elems.get(0);
+ } else {
+ return "";
+ }
+ }
+
+ public static ContactStruct constructContactFromVNode(VNode node,
+ int nameOrderType) {
+ if (!node.VName.equals("VCARD")) {
+ // Impossible in current implementation. Just for safety.
+ Log.e(LOG_TAG, "Non VCARD data is inserted.");
+ return null;
+ }
+
+ // For name, there are three fields in vCard: FN, N, NAME.
+ // We prefer FN, which is a required field in vCard 3.0 , but not in vCard 2.1.
+ // Next, we prefer NAME, which is defined only in vCard 3.0.
+ // Finally, we use N, which is a little difficult to parse.
+ String fullName = null;
+ String nameFromNProperty = null;
+
+ // Some vCard has "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", and
+ // "X-PHONETIC-LAST-NAME"
+ String xPhoneticFirstName = null;
+ String xPhoneticMiddleName = null;
+ String xPhoneticLastName = null;
+
+ ContactStruct contact = new ContactStruct();
+
+ // Each Column of four properties has ISPRIMARY field
+ // (See android.provider.Contacts)
+ // If false even after the following loop, we choose the first
+ // entry as a "primary" entry.
+ boolean prefIsSetAddress = false;
+ boolean prefIsSetPhone = false;
+ boolean prefIsSetEmail = false;
+ boolean prefIsSetOrganization = false;
+
+ for (PropertyNode propertyNode: node.propList) {
+ String name = propertyNode.propName;
+
+ if (TextUtils.isEmpty(propertyNode.propValue)) {
+ continue;
+ }
+
+ if (name.equals("VERSION")) {
+ // vCard version. Ignore this.
+ } else if (name.equals("FN")) {
+ fullName = propertyNode.propValue;
+ } else if (name.equals("NAME") && fullName == null) {
+ // Only in vCard 3.0. Use this if FN does not exist.
+ // Though, note that vCard 3.0 requires FN.
+ fullName = propertyNode.propValue;
+ } else if (name.equals("N")) {
+ nameFromNProperty = getNameFromNProperty(propertyNode.propValue_vector,
+ nameOrderType);
+ } else if (name.equals("SORT-STRING")) {
+ contact.phoneticName = propertyNode.propValue;
+ } else if (name.equals("SOUND")) {
+ if (propertyNode.paramMap_TYPE.contains("X-IRMC-N") &&
+ contact.phoneticName == null) {
+ // Some Japanese mobile phones use this field for phonetic name,
+ // since vCard 2.1 does not have "SORT-STRING" type.
+ // Also, in some cases, the field has some ';' in it.
+ // We remove them.
+ StringBuilder builder = new StringBuilder();
+ String value = propertyNode.propValue;
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char ch = value.charAt(i);
+ if (ch != ';') {
+ builder.append(ch);
+ }
+ }
+ contact.phoneticName = builder.toString();
+ } else {
+ contact.addExtension(propertyNode);
+ }
+ } else if (name.equals("ADR")) {
+ List<String> values = propertyNode.propValue_vector;
+ boolean valuesAreAllEmpty = true;
+ for (String value : values) {
+ if (value.length() > 0) {
+ valuesAreAllEmpty = false;
+ break;
+ }
+ }
+ if (valuesAreAllEmpty) {
+ continue;
+ }
+
+ int kind = Contacts.KIND_POSTAL;
+ int type = -1;
+ String label = "";
+ boolean isPrimary = false;
+ for (String typeString : propertyNode.paramMap_TYPE) {
+ if (typeString.equals("PREF") && !prefIsSetAddress) {
+ // Only first "PREF" is considered.
+ prefIsSetAddress = true;
+ isPrimary = true;
+ } else if (typeString.equalsIgnoreCase("HOME")) {
+ type = Contacts.ContactMethodsColumns.TYPE_HOME;
+ label = "";
+ } else if (typeString.equalsIgnoreCase("WORK") ||
+ typeString.equalsIgnoreCase("COMPANY")) {
+ // "COMPANY" seems emitted by Windows Mobile, which is not
+ // specifically supported by vCard 2.1. We assume this is same
+ // as "WORK".
+ type = Contacts.ContactMethodsColumns.TYPE_WORK;
+ label = "";
+ } else if (typeString.equalsIgnoreCase("POSTAL")) {
+ kind = Contacts.KIND_POSTAL;
+ } else if (typeString.equalsIgnoreCase("PARCEL") ||
+ typeString.equalsIgnoreCase("DOM") ||
+ typeString.equalsIgnoreCase("INTL")) {
+ // We do not have a kind or type matching these.
+ // TODO: fix this. We may need to split entries into two.
+ // (e.g. entries for KIND_POSTAL and KIND_PERCEL)
+ } else if (typeString.toUpperCase().startsWith("X-") &&
+ type < 0) {
+ type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
+ label = typeString.substring(2);
+ } else if (type < 0) {
+ // vCard 3.0 allows iana-token. Also some vCard 2.1 exporters
+ // emit non-standard types. We do not handle their values now.
+ type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
+ label = typeString;
+ }
+ }
+ // We use "HOME" as default
+ if (type < 0) {
+ type = Contacts.ContactMethodsColumns.TYPE_HOME;
+ }
+
+ // adr-value = 0*6(text-value ";") text-value
+ // ; PO Box, Extended Address, Street, Locality, Region, Postal
+ // ; Code, Country Name
+ String address;
+ List<String> list = propertyNode.propValue_vector;
+ int size = list.size();
+ if (size > 1) {
+ StringBuilder builder = new StringBuilder();
+ boolean builderIsEmpty = true;
+ if (Locale.getDefault().getCountry().equals(Locale.JAPAN.getCountry())) {
+ // In Japan, the order is reversed.
+ for (int i = size - 1; i >= 0; i--) {
+ String addressPart = list.get(i);
+ if (addressPart.length() > 0) {
+ if (!builderIsEmpty) {
+ builder.append(' ');
+ }
+ builder.append(addressPart);
+ builderIsEmpty = false;
+ }
+ }
+ } else {
+ for (int i = 0; i < size; i++) {
+ String addressPart = list.get(i);
+ if (addressPart.length() > 0) {
+ if (!builderIsEmpty) {
+ builder.append(' ');
+ }
+ builder.append(addressPart);
+ builderIsEmpty = false;
+ }
+ }
+ }
+ address = builder.toString().trim();
+ } else {
+ address = propertyNode.propValue;
+ }
+ contact.addContactmethod(kind, type, address, label, isPrimary);
+ } else if (name.equals("ORG")) {
+ // vCard specification does not specify other types.
+ int type = Contacts.OrganizationColumns.TYPE_WORK;
+ boolean isPrimary = false;
+
+ for (String typeString : propertyNode.paramMap_TYPE) {
+ if (typeString.equals("PREF") && !prefIsSetOrganization) {
+ // vCard specification officially does not have PREF in ORG.
+ // This is just for safety.
+ prefIsSetOrganization = true;
+ isPrimary = true;
+ }
+ // XXX: Should we cope with X- words?
+ }
+
+ List<String> list = propertyNode.propValue_vector;
+ int size = list.size();
+ StringBuilder builder = new StringBuilder();
+ for (Iterator<String> iter = list.iterator(); iter.hasNext();) {
+ builder.append(iter.next());
+ if (iter.hasNext()) {
+ builder.append(' ');
+ }
+ }
+
+ contact.addOrganization(type, builder.toString(), "", isPrimary);
+ } else if (name.equals("TITLE")) {
+ contact.setPosition(propertyNode.propValue);
+ } else if (name.equals("ROLE")) {
+ contact.setPosition(propertyNode.propValue);
+ } else if (name.equals("PHOTO")) {
+ // We prefer PHOTO to LOGO.
+ String valueType = propertyNode.paramMap.getAsString("VALUE");
+ if (valueType != null && valueType.equals("URL")) {
+ // TODO: do something.
+ } else {
+ // Assume PHOTO is stored in BASE64. In that case,
+ // data is already stored in propValue_bytes in binary form.
+ // It should be automatically done by VBuilder (VDataBuilder/VCardDatabuilder)
+ contact.photoBytes = propertyNode.propValue_bytes;
+ String type = propertyNode.paramMap.getAsString("TYPE");
+ if (type != null) {
+ contact.photoType = type;
+ }
+ }
+ } else if (name.equals("LOGO")) {
+ // When PHOTO is not available this is not URL,
+ // we use this instead of PHOTO.
+ String valueType = propertyNode.paramMap.getAsString("VALUE");
+ if (valueType != null && valueType.equals("URL")) {
+ // TODO: do something.
+ } else if (contact.photoBytes == null) {
+ contact.photoBytes = propertyNode.propValue_bytes;
+ String type = propertyNode.paramMap.getAsString("TYPE");
+ if (type != null) {
+ contact.photoType = type;
+ }
+ }
+ } else if (name.equals("EMAIL")) {
+ int type = -1;
+ String label = null;
+ boolean isPrimary = false;
+ for (String typeString : propertyNode.paramMap_TYPE) {
+ if (typeString.equals("PREF") && !prefIsSetEmail) {
+ // Only first "PREF" is considered.
+ prefIsSetEmail = true;
+ isPrimary = true;
+ } else if (typeString.equalsIgnoreCase("HOME")) {
+ type = Contacts.ContactMethodsColumns.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase("WORK")) {
+ type = Contacts.ContactMethodsColumns.TYPE_WORK;
+ } else if (typeString.equalsIgnoreCase("CELL")) {
+ // We do not have Contacts.ContactMethodsColumns.TYPE_MOBILE yet.
+ type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
+ label = Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME;
+ } else if (typeString.toUpperCase().startsWith("X-") &&
+ type < 0) {
+ type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
+ label = typeString.substring(2);
+ } else if (type < 0) {
+ // vCard 3.0 allows iana-token.
+ // We may have INTERNET (specified in vCard spec),
+ // SCHOOL, etc.
+ type = Contacts.ContactMethodsColumns.TYPE_CUSTOM;
+ label = typeString;
+ }
+ }
+ // We use "OTHER" as default.
+ if (type < 0) {
+ type = Contacts.ContactMethodsColumns.TYPE_OTHER;
+ }
+ contact.addContactmethod(Contacts.KIND_EMAIL,
+ type, propertyNode.propValue,label, isPrimary);
+ } else if (name.equals("TEL")) {
+ int type = -1;
+ String label = null;
+ boolean isPrimary = false;
+ boolean isFax = false;
+ for (String typeString : propertyNode.paramMap_TYPE) {
+ if (typeString.equals("PREF") && !prefIsSetPhone) {
+ // Only first "PREF" is considered.
+ prefIsSetPhone = true;
+ isPrimary = true;
+ } else if (typeString.equalsIgnoreCase("HOME")) {
+ type = Contacts.PhonesColumns.TYPE_HOME;
+ } else if (typeString.equalsIgnoreCase("WORK")) {
+ type = Contacts.PhonesColumns.TYPE_WORK;
+ } else if (typeString.equalsIgnoreCase("CELL")) {
+ type = Contacts.PhonesColumns.TYPE_MOBILE;
+ } else if (typeString.equalsIgnoreCase("PAGER")) {
+ type = Contacts.PhonesColumns.TYPE_PAGER;
+ } else if (typeString.equalsIgnoreCase("FAX")) {
+ isFax = true;
+ } else if (typeString.equalsIgnoreCase("VOICE") ||
+ typeString.equalsIgnoreCase("MSG")) {
+ // Defined in vCard 3.0. Ignore these because they
+ // conflict with "HOME", "WORK", etc.
+ // XXX: do something?
+ } else if (typeString.toUpperCase().startsWith("X-") &&
+ type < 0) {
+ type = Contacts.PhonesColumns.TYPE_CUSTOM;
+ label = typeString.substring(2);
+ } else if (type < 0){
+ // We may have MODEM, CAR, ISDN, etc...
+ type = Contacts.PhonesColumns.TYPE_CUSTOM;
+ label = typeString;
+ }
+ }
+ // We use "HOME" as default
+ if (type < 0) {
+ type = Contacts.PhonesColumns.TYPE_HOME;
+ }
+ if (isFax) {
+ if (type == Contacts.PhonesColumns.TYPE_HOME) {
+ type = Contacts.PhonesColumns.TYPE_FAX_HOME;
+ } else if (type == Contacts.PhonesColumns.TYPE_WORK) {
+ type = Contacts.PhonesColumns.TYPE_FAX_WORK;
+ }
+ }
+
+ contact.addPhone(type, propertyNode.propValue, label, isPrimary);
+ } else if (name.equals("NOTE")) {
+ contact.notes.add(propertyNode.propValue);
+ } else if (name.equals("BDAY")) {
+ contact.addExtension(propertyNode);
+ } else if (name.equals("URL")) {
+ contact.addExtension(propertyNode);
+ } else if (name.equals("REV")) {
+ // Revision of this VCard entry. I think we can ignore this.
+ contact.addExtension(propertyNode);
+ } else if (name.equals("UID")) {
+ contact.addExtension(propertyNode);
+ } else if (name.equals("KEY")) {
+ // Type is X509 or PGP? I don't know how to handle this...
+ contact.addExtension(propertyNode);
+ } else if (name.equals("MAILER")) {
+ contact.addExtension(propertyNode);
+ } else if (name.equals("TZ")) {
+ contact.addExtension(propertyNode);
+ } else if (name.equals("GEO")) {
+ contact.addExtension(propertyNode);
+ } else if (name.equals("NICKNAME")) {
+ // vCard 3.0 only.
+ contact.addExtension(propertyNode);
+ } else if (name.equals("CLASS")) {
+ // vCard 3.0 only.
+ // e.g. CLASS:CONFIDENTIAL
+ contact.addExtension(propertyNode);
+ } else if (name.equals("PROFILE")) {
+ // VCard 3.0 only. Must be "VCARD". I think we can ignore this.
+ contact.addExtension(propertyNode);
+ } else if (name.equals("CATEGORIES")) {
+ // VCard 3.0 only.
+ // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
+ contact.addExtension(propertyNode);
+ } else if (name.equals("SOURCE")) {
+ // VCard 3.0 only.
+ contact.addExtension(propertyNode);
+ } else if (name.equals("PRODID")) {
+ // VCard 3.0 only.
+ // To specify the identifier for the product that created
+ // the vCard object.
+ contact.addExtension(propertyNode);
+ } else if (name.equals("X-PHONETIC-FIRST-NAME")) {
+ xPhoneticFirstName = propertyNode.propValue;
+ } else if (name.equals("X-PHONETIC-MIDDLE-NAME")) {
+ xPhoneticMiddleName = propertyNode.propValue;
+ } else if (name.equals("X-PHONETIC-LAST-NAME")) {
+ xPhoneticLastName = propertyNode.propValue;
+ } else {
+ // Unknown X- words and IANA token.
+ contact.addExtension(propertyNode);
+ }
+ }
+
+ if (fullName != null) {
+ contact.name = fullName;
+ } else if(nameFromNProperty != null) {
+ contact.name = nameFromNProperty;
+ } else {
+ contact.name = "";
+ }
+
+ if (contact.phoneticName == null &&
+ (xPhoneticFirstName != null || xPhoneticMiddleName != null ||
+ xPhoneticLastName != null)) {
+ // Note: In Europe, this order should be "LAST FIRST MIDDLE". See the comment around
+ // NAME_ORDER_TYPE_* for more detail.
+ String first;
+ String second;
+ if (nameOrderType == NAME_ORDER_TYPE_JAPANESE) {
+ first = xPhoneticLastName;
+ second = xPhoneticFirstName;
+ } else {
+ first = xPhoneticFirstName;
+ second = xPhoneticLastName;
+ }
+ StringBuilder builder = new StringBuilder();
+ if (first != null) {
+ builder.append(first);
+ }
+ if (xPhoneticMiddleName != null) {
+ builder.append(xPhoneticMiddleName);
+ }
+ if (second != null) {
+ builder.append(second);
+ }
+ contact.phoneticName = builder.toString();
+ }
+
+ // Remove unnecessary white spaces.
+ // It is found that some mobile phone emits phonetic name with just one white space
+ // when a user does not specify one.
+ // This logic is effective toward such kind of weird data.
+ if (contact.phoneticName != null) {
+ contact.phoneticName = contact.phoneticName.trim();
+ }
+
+ // If there is no "PREF", we choose the first entries as primary.
+ if (!prefIsSetPhone &&
+ contact.phoneList != null &&
+ contact.phoneList.size() > 0) {
+ contact.phoneList.get(0).isPrimary = true;
+ }
+
+ if (!prefIsSetAddress && contact.contactmethodList != null) {
+ for (ContactMethod contactMethod : contact.contactmethodList) {
+ if (contactMethod.kind == Contacts.KIND_POSTAL) {
+ contactMethod.isPrimary = true;
+ break;
+ }
+ }
+ }
+ if (!prefIsSetEmail && contact.contactmethodList != null) {
+ for (ContactMethod contactMethod : contact.contactmethodList) {
+ if (contactMethod.kind == Contacts.KIND_EMAIL) {
+ contactMethod.isPrimary = true;
+ break;
+ }
+ }
+ }
+ if (!prefIsSetOrganization &&
+ contact.organizationList != null &&
+ contact.organizationList.size() > 0) {
+ contact.organizationList.get(0).isPrimary = true;
+ }
+
+ return contact;
+ }
+
+ public String displayString() {
+ if (name.length() > 0) {
+ return name;
+ }
+ if (contactmethodList != null && contactmethodList.size() > 0) {
+ for (ContactMethod contactMethod : contactmethodList) {
+ if (contactMethod.kind == Contacts.KIND_EMAIL && contactMethod.isPrimary) {
+ return contactMethod.data;
+ }
+ }
+ }
+ if (phoneList != null && phoneList.size() > 0) {
+ for (PhoneData phoneData : phoneList) {
+ if (phoneData.isPrimary) {
+ return phoneData.data;
+ }
+ }
+ }
+ return "";
+ }
+
+ private void pushIntoContentProviderOrResolver(Object contentSomething,
+ long myContactsGroupId) {
+ ContentResolver resolver = null;
+ AbstractSyncableContentProvider provider = null;
+ if (contentSomething instanceof ContentResolver) {
+ resolver = (ContentResolver)contentSomething;
+ } else if (contentSomething instanceof AbstractSyncableContentProvider) {
+ provider = (AbstractSyncableContentProvider)contentSomething;
+ } else {
+ Log.e(LOG_TAG, "Unsupported object came.");
+ return;
+ }
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(People.NAME, name);
+ contentValues.put(People.PHONETIC_NAME, phoneticName);
+
+ if (notes.size() > 1) {
+ StringBuilder builder = new StringBuilder();
+ for (String note : notes) {
+ builder.append(note);
+ builder.append("\n");
+ }
+ contentValues.put(People.NOTES, builder.toString());
+ } else if (notes.size() == 1){
+ contentValues.put(People.NOTES, notes.get(0));
+ }
+
+ Uri personUri;
+ long personId = 0;
+ if (resolver != null) {
+ personUri = Contacts.People.createPersonInMyContactsGroup(
+ resolver, contentValues);
+ if (personUri != null) {
+ personId = ContentUris.parseId(personUri);
+ }
+ } else {
+ personUri = provider.nonTransactionalInsert(People.CONTENT_URI, contentValues);
+ if (personUri != null) {
+ personId = ContentUris.parseId(personUri);
+ ContentValues values = new ContentValues();
+ values.put(GroupMembership.PERSON_ID, personId);
+ values.put(GroupMembership.GROUP_ID, myContactsGroupId);
+ Uri resultUri = provider.nonTransactionalInsert(
+ GroupMembership.CONTENT_URI, values);
+ if (resultUri == null) {
+ Log.e(LOG_TAG, "Faild to insert the person to MyContact.");
+ provider.nonTransactionalDelete(personUri, null, null);
+ personUri = null;
+ }
+ }
+ }
+
+ if (personUri == null) {
+ Log.e(LOG_TAG, "Failed to create the contact.");
+ return;
+ }
+
+ if (photoBytes != null) {
+ if (resolver != null) {
+ People.setPhotoData(resolver, personUri, photoBytes);
+ } else {
+ Uri photoUri = Uri.withAppendedPath(personUri, Contacts.Photos.CONTENT_DIRECTORY);
+ ContentValues values = new ContentValues();
+ values.put(Photos.DATA, photoBytes);
+ provider.update(photoUri, values, null, null);
+ }
+ }
+
+ long primaryPhoneId = -1;
+ if (phoneList != null && phoneList.size() > 0) {
+ for (PhoneData phoneData : phoneList) {
+ ContentValues values = new ContentValues();
+ values.put(Contacts.PhonesColumns.TYPE, phoneData.type);
+ if (phoneData.type == Contacts.PhonesColumns.TYPE_CUSTOM) {
+ values.put(Contacts.PhonesColumns.LABEL, phoneData.label);
+ }
+ // Already formatted.
+ values.put(Contacts.PhonesColumns.NUMBER, phoneData.data);
+
+ // Not sure about Contacts.PhonesColumns.NUMBER_KEY ...
+ values.put(Contacts.PhonesColumns.ISPRIMARY, 1);
+ values.put(Contacts.Phones.PERSON_ID, personId);
+ Uri phoneUri;
+ if (resolver != null) {
+ phoneUri = resolver.insert(Phones.CONTENT_URI, values);
+ } else {
+ phoneUri = provider.nonTransactionalInsert(Phones.CONTENT_URI, values);
+ }
+ if (phoneData.isPrimary) {
+ primaryPhoneId = Long.parseLong(phoneUri.getLastPathSegment());
+ }
+ }
+ }
+
+ long primaryOrganizationId = -1;
+ if (organizationList != null && organizationList.size() > 0) {
+ for (OrganizationData organizationData : organizationList) {
+ ContentValues values = new ContentValues();
+ // Currently, we do not use TYPE_CUSTOM.
+ values.put(Contacts.OrganizationColumns.TYPE,
+ organizationData.type);
+ values.put(Contacts.OrganizationColumns.COMPANY,
+ organizationData.companyName);
+ values.put(Contacts.OrganizationColumns.TITLE,
+ organizationData.positionName);
+ values.put(Contacts.OrganizationColumns.ISPRIMARY, 1);
+ values.put(Contacts.OrganizationColumns.PERSON_ID, personId);
+
+ Uri organizationUri;
+ if (resolver != null) {
+ organizationUri = resolver.insert(Organizations.CONTENT_URI, values);
+ } else {
+ organizationUri = provider.nonTransactionalInsert(
+ Organizations.CONTENT_URI, values);
+ }
+ if (organizationData.isPrimary) {
+ primaryOrganizationId = Long.parseLong(organizationUri.getLastPathSegment());
+ }
+ }
+ }
+
+ long primaryEmailId = -1;
+ if (contactmethodList != null && contactmethodList.size() > 0) {
+ for (ContactMethod contactMethod : contactmethodList) {
+ ContentValues values = new ContentValues();
+ values.put(Contacts.ContactMethodsColumns.KIND, contactMethod.kind);
+ values.put(Contacts.ContactMethodsColumns.TYPE, contactMethod.type);
+ if (contactMethod.type == Contacts.ContactMethodsColumns.TYPE_CUSTOM) {
+ values.put(Contacts.ContactMethodsColumns.LABEL, contactMethod.label);
+ }
+ values.put(Contacts.ContactMethodsColumns.DATA, contactMethod.data);
+ values.put(Contacts.ContactMethodsColumns.ISPRIMARY, 1);
+ values.put(Contacts.ContactMethods.PERSON_ID, personId);
+
+ if (contactMethod.kind == Contacts.KIND_EMAIL) {
+ Uri emailUri;
+ if (resolver != null) {
+ emailUri = resolver.insert(ContactMethods.CONTENT_URI, values);
+ } else {
+ emailUri = provider.nonTransactionalInsert(
+ ContactMethods.CONTENT_URI, values);
+ }
+ if (contactMethod.isPrimary) {
+ primaryEmailId = Long.parseLong(emailUri.getLastPathSegment());
+ }
+ } else { // probably KIND_POSTAL
+ if (resolver != null) {
+ resolver.insert(ContactMethods.CONTENT_URI, values);
+ } else {
+ provider.nonTransactionalInsert(
+ ContactMethods.CONTENT_URI, values);
+ }
+ }
+ }
+ }
+
+ if (extensionMap != null && extensionMap.size() > 0) {
+ ArrayList<ContentValues> contentValuesArray;
+ if (resolver != null) {
+ contentValuesArray = new ArrayList<ContentValues>();
+ } else {
+ contentValuesArray = null;
+ }
+ for (Entry<String, List<String>> entry : extensionMap.entrySet()) {
+ String key = entry.getKey();
+ List<String> list = entry.getValue();
+ for (String value : list) {
+ ContentValues values = new ContentValues();
+ values.put(Extensions.NAME, key);
+ values.put(Extensions.VALUE, value);
+ values.put(Extensions.PERSON_ID, personId);
+ if (resolver != null) {
+ contentValuesArray.add(values);
+ } else {
+ provider.nonTransactionalInsert(Extensions.CONTENT_URI, values);
+ }
+ }
+ }
+ if (resolver != null) {
+ resolver.bulkInsert(Extensions.CONTENT_URI,
+ contentValuesArray.toArray(new ContentValues[0]));
+ }
+ }
+
+ if (primaryPhoneId >= 0 || primaryOrganizationId >= 0 || primaryEmailId >= 0) {
+ ContentValues values = new ContentValues();
+ if (primaryPhoneId >= 0) {
+ values.put(People.PRIMARY_PHONE_ID, primaryPhoneId);
+ }
+ if (primaryOrganizationId >= 0) {
+ values.put(People.PRIMARY_ORGANIZATION_ID, primaryOrganizationId);
+ }
+ if (primaryEmailId >= 0) {
+ values.put(People.PRIMARY_EMAIL_ID, primaryEmailId);
+ }
+ if (resolver != null) {
+ resolver.update(personUri, values, null, null);
+ } else {
+ provider.nonTransactionalUpdate(personUri, values, null, null);
+ }
+ }
+ }
+
+ /**
+ * Push this object into database in the resolver.
+ */
+ public void pushIntoContentResolver(ContentResolver resolver) {
+ pushIntoContentProviderOrResolver(resolver, 0);
+ }
+
+ /**
+ * Push this object into AbstractSyncableContentProvider object.
+ */
+ public void pushIntoAbstractSyncableContentProvider(
+ AbstractSyncableContentProvider provider, long myContactsGroupId) {
+ boolean successful = false;
+ provider.beginTransaction();
+ try {
+ pushIntoContentProviderOrResolver(provider, myContactsGroupId);
+ successful = true;
+ } finally {
+ provider.endTransaction(successful);
+ }
+ }
+
+ public boolean isIgnorable() {
+ return TextUtils.isEmpty(name) &&
+ TextUtils.isEmpty(phoneticName) &&
+ (phoneList == null || phoneList.size() == 0) &&
+ (contactmethodList == null || contactmethodList.size() == 0);
}
}
diff --git a/core/java/android/syncml/pim/vcard/VCardComposer.java b/core/java/android/syncml/pim/vcard/VCardComposer.java
index 05e8f40..192736a 100644
--- a/core/java/android/syncml/pim/vcard/VCardComposer.java
+++ b/core/java/android/syncml/pim/vcard/VCardComposer.java
@@ -124,9 +124,9 @@ public class VCardComposer {
mResult.append("ORG:").append(struct.company).append(mNewline);
}
- if (!isNull(struct.notes)) {
+ if (struct.notes.size() > 0 && !isNull(struct.notes.get(0))) {
mResult.append("NOTE:").append(
- foldingString(struct.notes, vcardversion)).append(mNewline);
+ foldingString(struct.notes.get(0), vcardversion)).append(mNewline);
}
if (!isNull(struct.title)) {
@@ -190,7 +190,7 @@ public class VCardComposer {
*/
private void appendPhotoStr(byte[] bytes, String type, int version)
throws VCardException {
- String value, apptype, encodingStr;
+ String value, encodingStr;
try {
value = foldingString(new String(Base64.encodeBase64(bytes, true)),
version);
@@ -198,20 +198,23 @@ public class VCardComposer {
throw new VCardException(e.getMessage());
}
- if (isNull(type)) {
- type = "image/jpeg";
- }
- if (type.indexOf("jpeg") > 0) {
- apptype = "JPEG";
- } else if (type.indexOf("gif") > 0) {
- apptype = "GIF";
- } else if (type.indexOf("bmp") > 0) {
- apptype = "BMP";
+ if (isNull(type) || type.toUpperCase().indexOf("JPEG") >= 0) {
+ type = "JPEG";
+ } else if (type.toUpperCase().indexOf("GIF") >= 0) {
+ type = "GIF";
+ } else if (type.toUpperCase().indexOf("BMP") >= 0) {
+ type = "BMP";
} else {
- apptype = type.substring(type.indexOf("/")).toUpperCase();
+ // Handle the string like "image/tiff".
+ int indexOfSlash = type.indexOf("/");
+ if (indexOfSlash >= 0) {
+ type = type.substring(indexOfSlash + 1).toUpperCase();
+ } else {
+ type = type.toUpperCase();
+ }
}
- mResult.append("LOGO;TYPE=").append(apptype);
+ mResult.append("LOGO;TYPE=").append(type);
if (version == VERSION_VCARD21_INT) {
encodingStr = ";ENCODING=BASE64:";
value = value + mNewline;
@@ -281,7 +284,7 @@ public class VCardComposer {
private String getPhoneTypeStr(PhoneData phone) {
- int phoneType = Integer.parseInt(phone.type);
+ int phoneType = phone.type;
String typeStr, label;
if (phoneTypeMap.containsKey(phoneType)) {
@@ -308,7 +311,7 @@ public class VCardComposer {
String joinMark = version == VERSION_VCARD21_INT ? ";" : ",";
for (ContactStruct.ContactMethod contactMethod : contactMList) {
// same with v2.1 and v3.0
- switch (Integer.parseInt(contactMethod.kind)) {
+ switch (contactMethod.kind) {
case Contacts.KIND_EMAIL:
String mailType = "INTERNET";
if (!isNull(contactMethod.data)) {
diff --git a/core/java/android/syncml/pim/vcard/VCardDataBuilder.java b/core/java/android/syncml/pim/vcard/VCardDataBuilder.java
new file mode 100644
index 0000000..a0513f1
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardDataBuilder.java
@@ -0,0 +1,442 @@
+/*
+ * 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 android.syncml.pim.vcard;
+
+import android.app.ProgressDialog;
+import android.content.AbstractSyncableContentProvider;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.os.Handler;
+import android.provider.Contacts;
+import android.syncml.pim.PropertyNode;
+import android.syncml.pim.VBuilder;
+import android.syncml.pim.VNode;
+import android.syncml.pim.VParser;
+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.List;
+
+/**
+ * VBuilder for VCard. VCard may contain big photo images encoded by BASE64,
+ * If we store all VNode entries in memory like VDataBuilder.java,
+ * OutOfMemoryError may be thrown. Thus, this class push each VCard entry into
+ * ContentResolver immediately.
+ */
+public class VCardDataBuilder implements VBuilder {
+ static private String LOG_TAG = "VCardDataBuilder";
+
+ /**
+ * If there's no other information available, this class uses this charset for encoding
+ * byte arrays.
+ */
+ static public String DEFAULT_CHARSET = "UTF-8";
+
+ private class ProgressShower implements Runnable {
+ private ContactStruct mContact;
+
+ public ProgressShower(ContactStruct contact) {
+ mContact = contact;
+ }
+
+ public void run () {
+ mProgressDialog.setMessage(mProgressMessage + "\n" +
+ mContact.displayString());
+ }
+ }
+
+ /** type=VNode */
+ private VNode mCurrentVNode;
+ private PropertyNode mCurrentPropNode;
+ private String mCurrentParamType;
+
+ /**
+ * The charset using which VParser parses the text.
+ */
+ private String mSourceCharset;
+
+ /**
+ * The charset with which byte array is encoded to String.
+ */
+ private String mTargetCharset;
+ private boolean mStrictLineBreakParsing;
+ private ContentResolver mContentResolver;
+
+ // For letting VCardDataBuilder show the display name of VCard while handling it.
+ private Handler mHandler;
+ private ProgressDialog mProgressDialog;
+ private String mProgressMessage;
+ private Runnable mOnProgressRunnable;
+ private boolean mLastNameComesBeforeFirstName;
+
+ // Just for testing.
+ private long mTimeCreateContactStruct;
+ private long mTimePushIntoContentResolver;
+
+ // Ideally, this should be ContactsProvider but it seems Class loader cannot find it,
+ // even when it is subclass of ContactsProvider...
+ private AbstractSyncableContentProvider mProvider;
+ private long mMyContactsGroupId;
+
+ public VCardDataBuilder(ContentResolver resolver) {
+ mTargetCharset = DEFAULT_CHARSET;
+ mContentResolver = resolver;
+ }
+
+ /**
+ * Constructor which requires minimum requiredvariables.
+ *
+ * @param resolver insert each data into this ContentResolver
+ * @param progressDialog
+ * @param progressMessage
+ * @param handler if this importer works on the different thread than main one,
+ * set appropriate handler object. If not, it is ok to set this null.
+ */
+ public VCardDataBuilder(ContentResolver resolver,
+ ProgressDialog progressDialog,
+ String progressMessage,
+ Handler handler) {
+ this(resolver, progressDialog, progressMessage, handler,
+ null, null, false, false);
+ }
+
+ public VCardDataBuilder(ContentResolver resolver,
+ ProgressDialog progressDialog,
+ String progressMessage,
+ Handler handler,
+ String charset,
+ boolean strictLineBreakParsing,
+ boolean lastNameComesBeforeFirstName) {
+ this(resolver, progressDialog, progressMessage, handler,
+ null, charset, strictLineBreakParsing,
+ lastNameComesBeforeFirstName);
+ }
+
+ /**
+ * @hide
+ */
+ public VCardDataBuilder(ContentResolver resolver,
+ ProgressDialog progressDialog,
+ String progressMessage,
+ Handler handler,
+ String sourceCharset,
+ String targetCharset,
+ boolean strictLineBreakParsing,
+ boolean lastNameComesBeforeFirstName) {
+ if (sourceCharset != null) {
+ mSourceCharset = sourceCharset;
+ } else {
+ mSourceCharset = VParser.DEFAULT_CHARSET;
+ }
+ if (targetCharset != null) {
+ mTargetCharset = targetCharset;
+ } else {
+ mTargetCharset = DEFAULT_CHARSET;
+ }
+ mContentResolver = resolver;
+ mStrictLineBreakParsing = strictLineBreakParsing;
+ mHandler = handler;
+ mProgressDialog = progressDialog;
+ mProgressMessage = progressMessage;
+ mLastNameComesBeforeFirstName = lastNameComesBeforeFirstName;
+
+ tryGetOriginalProvider();
+ }
+
+ private void tryGetOriginalProvider() {
+ final ContentResolver resolver = mContentResolver;
+
+ if ((mMyContactsGroupId = Contacts.People.tryGetMyContactsGroupId(resolver)) == 0) {
+ Log.e(LOG_TAG, "Could not get group id of MyContact");
+ return;
+ }
+
+ IContentProvider iProviderForName = resolver.acquireProvider(Contacts.CONTENT_URI);
+ ContentProvider contentProvider =
+ ContentProvider.coerceToLocalContentProvider(iProviderForName);
+ if (contentProvider == null) {
+ Log.e(LOG_TAG, "Fail to get ContentProvider object.");
+ return;
+ }
+
+ if (!(contentProvider instanceof AbstractSyncableContentProvider)) {
+ Log.e(LOG_TAG,
+ "Acquired ContentProvider object is not AbstractSyncableContentProvider.");
+ return;
+ }
+
+ mProvider = (AbstractSyncableContentProvider)contentProvider;
+ }
+
+ public void setOnProgressRunnable(Runnable runnable) {
+ mOnProgressRunnable = runnable;
+ }
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ /**
+ * Assume that VCard is not nested. In other words, this code does not accept
+ */
+ public void startRecord(String type) {
+ if (mCurrentVNode != null) {
+ // This means startRecord() is called inside startRecord() - endRecord() block.
+ // TODO: should throw some Exception
+ Log.e(LOG_TAG, "Nested VCard code is not supported now.");
+ }
+ mCurrentVNode = new VNode();
+ mCurrentVNode.parseStatus = 1;
+ mCurrentVNode.VName = type;
+ }
+
+ public void endRecord() {
+ mCurrentVNode.parseStatus = 0;
+ long start = System.currentTimeMillis();
+ ContactStruct contact = ContactStruct.constructContactFromVNode(mCurrentVNode,
+ mLastNameComesBeforeFirstName ? ContactStruct.NAME_ORDER_TYPE_JAPANESE :
+ ContactStruct.NAME_ORDER_TYPE_ENGLISH);
+ mTimeCreateContactStruct += System.currentTimeMillis() - start;
+ if (!contact.isIgnorable()) {
+ if (mProgressDialog != null && mProgressMessage != null) {
+ if (mHandler != null) {
+ mHandler.post(new ProgressShower(contact));
+ } else {
+ mProgressDialog.setMessage(mProgressMessage + "\n" +
+ contact.displayString());
+ }
+ }
+ start = System.currentTimeMillis();
+ if (mProvider != null) {
+ contact.pushIntoAbstractSyncableContentProvider(
+ mProvider, mMyContactsGroupId);
+ } else {
+ contact.pushIntoContentResolver(mContentResolver);
+ }
+ mTimePushIntoContentResolver += System.currentTimeMillis() - start;
+ }
+ if (mOnProgressRunnable != null) {
+ mOnProgressRunnable.run();
+ }
+ mCurrentVNode = null;
+ }
+
+ public void startProperty() {
+ mCurrentPropNode = new PropertyNode();
+ }
+
+ public void endProperty() {
+ mCurrentVNode.propList.add(mCurrentPropNode);
+ mCurrentPropNode = null;
+ }
+
+ public void propertyName(String name) {
+ mCurrentPropNode.propName = name;
+ }
+
+ public void propertyGroup(String group) {
+ mCurrentPropNode.propGroupSet.add(group);
+ }
+
+ public void propertyParamType(String type) {
+ mCurrentParamType = type;
+ }
+
+ public void propertyParamValue(String value) {
+ if (mCurrentParamType == null ||
+ mCurrentParamType.equalsIgnoreCase("TYPE")) {
+ mCurrentPropNode.paramMap_TYPE.add(value);
+ } else {
+ mCurrentPropNode.paramMap.put(mCurrentParamType, value);
+ }
+
+ mCurrentParamType = null;
+ }
+
+ private String encodeString(String originalString, String targetCharset) {
+ if (mSourceCharset.equalsIgnoreCase(targetCharset)) {
+ return originalString;
+ }
+ Charset charset = Charset.forName(mSourceCharset);
+ 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, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(bytes);
+ }
+ }
+
+ private String handleOneValue(String value, String targetCharset, String encoding) {
+ if (encoding != null) {
+ if (encoding.equals("BASE64") || encoding.equals("B")) {
+ mCurrentPropNode.propValue_bytes =
+ Base64.decodeBase64(value.getBytes());
+ 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(mSourceCharset);
+ } catch (UnsupportedEncodingException e1) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
+ 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, targetCharset);
+ } catch (UnsupportedEncodingException e) {
+ Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
+ return new String(bytes);
+ }
+ }
+ // Unknown encoding. Fall back to default.
+ }
+ return encodeString(value, targetCharset);
+ }
+
+ public void propertyValues(List<String> values) {
+ if (values == null || values.size() == 0) {
+ mCurrentPropNode.propValue_bytes = null;
+ mCurrentPropNode.propValue_vector.clear();
+ mCurrentPropNode.propValue_vector.add("");
+ mCurrentPropNode.propValue = "";
+ return;
+ }
+
+ ContentValues paramMap = mCurrentPropNode.paramMap;
+
+ String targetCharset = CharsetUtils.nameForDefaultVendor(paramMap.getAsString("CHARSET"));
+ String encoding = paramMap.getAsString("ENCODING");
+
+ if (targetCharset == null || targetCharset.length() == 0) {
+ targetCharset = mTargetCharset;
+ }
+
+ for (String value : values) {
+ mCurrentPropNode.propValue_vector.add(
+ handleOneValue(value, targetCharset, encoding));
+ }
+
+ mCurrentPropNode.propValue = listToString(mCurrentPropNode.propValue_vector);
+ }
+
+ public void showDebugInfo() {
+ Log.d(LOG_TAG, "time for creating ContactStruct: " + mTimeCreateContactStruct + " ms");
+ Log.d(LOG_TAG, "time for insert ContactStruct to database: " +
+ mTimePushIntoContentResolver + " ms");
+ }
+
+ private String listToString(List<String> list){
+ int size = list.size();
+ if (size > 1) {
+ StringBuilder builder = new StringBuilder();
+ int i = 0;
+ for (String type : list) {
+ builder.append(type);
+ if (i < size - 1) {
+ builder.append(";");
+ }
+ }
+ return builder.toString();
+ } else if (size == 1) {
+ return list.get(0);
+ } else {
+ return "";
+ }
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardEntryCounter.java b/core/java/android/syncml/pim/vcard/VCardEntryCounter.java
new file mode 100644
index 0000000..03cd1d9
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardEntryCounter.java
@@ -0,0 +1,63 @@
+/*
+ * 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.syncml.pim.vcard;
+
+import java.util.List;
+
+import android.syncml.pim.VBuilder;
+
+public class VCardEntryCounter implements VBuilder {
+ private int mCount;
+
+ public int getCount() {
+ return mCount;
+ }
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ public void startRecord(String type) {
+ }
+
+ public void endRecord() {
+ mCount++;
+ }
+
+ public void startProperty() {
+ }
+
+ public void endProperty() {
+ }
+
+ public void propertyGroup(String group) {
+ }
+
+ public void propertyName(String name) {
+ }
+
+ public void propertyParamType(String type) {
+ }
+
+ public void propertyParamValue(String value) {
+ }
+
+ public void propertyValues(List<String> values) {
+ }
+} \ No newline at end of file
diff --git a/core/java/android/syncml/pim/vcard/VCardNestedException.java b/core/java/android/syncml/pim/vcard/VCardNestedException.java
new file mode 100644
index 0000000..def6f3b
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardNestedException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.syncml.pim.vcard;
+
+/**
+ * VCardException thrown when VCard is nested without VCardParser's being notified.
+ */
+public class VCardNestedException extends VCardException {
+ public VCardNestedException() {}
+ public VCardNestedException(String message) {
+ super(message);
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V21.java b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
index f853c5e..d865668 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V21.java
@@ -17,21 +17,26 @@
package android.syncml.pim.vcard;
import android.syncml.pim.VBuilder;
+import android.syncml.pim.VParser;
+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.HashSet;
-import java.util.regex.Pattern;
/**
- * This class is used to parse vcard. Please refer to vCard Specification 2.1
+ * This class is used to parse vcard. Please refer to vCard Specification 2.1.
*/
public class VCardParser_V21 {
-
+ private static final String LOG_TAG = "VCardParser_V21";
+
+ public static final String DEFAULT_CHARSET = VParser.DEFAULT_CHARSET;
+
/** Store the known-type */
private static final HashSet<String> sKnownTypeSet = new HashSet<String>(
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
@@ -42,19 +47,17 @@ public class VCardParser_V21 {
"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 name available in vCard 2.1 */
- // NICKNAME is not supported in vCard 2.1, but some vCard may contain.
+ /** Store the property names available in vCard 2.1 */
private static final HashSet<String> sAvailablePropertyNameV21 =
new HashSet<String>(Arrays.asList(
- "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
- "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER",
- "NICKNAME"));
+ "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
// Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
// We allow it for safety...
@@ -76,6 +79,30 @@ public class VCardParser_V21 {
// Should not directly read a line from this. Use getLine() instead.
protected BufferedReader mReader;
+ private boolean mCanceled;
+
+ // 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 HashSet<String> mWarningValueMap = new HashSet<String>();
+
+ // Just for debugging
+ private long mTimeTotal;
+ private long mTimeStartRecord;
+ private long mTimeEndRecord;
+ private long mTimeStartProperty;
+ private long mTimeEndProperty;
+ private long mTimeParseItems;
+ private long mTimeParseItem1;
+ private long mTimeParseItem2;
+ private long mTimeParseItem3;
+ private long mTimeHandlePropertyValue1;
+ private long mTimeHandlePropertyValue2;
+ private long mTimeHandlePropertyValue3;
+
/**
* Create a new VCard parser.
*/
@@ -83,12 +110,35 @@ public class VCardParser_V21 {
super();
}
+ public VCardParser_V21(VCardSourceDetector detector) {
+ super();
+ if (detector != null && detector.getType() == VCardSourceDetector.TYPE_FOMA) {
+ mNestCount = 1;
+ }
+ }
+
/**
* Parse the file at the given position
* vcard_file = [wsls] vcard [wsls]
*/
protected void parseVCardFile() throws IOException, VCardException {
- while (parseOneVCard()) {
+ 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;
+ }
}
}
@@ -100,7 +150,13 @@ public class VCardParser_V21 {
* @return true when the propertyName is a valid property name.
*/
protected boolean isValidPropertyName(String propertyName) {
- return sAvailablePropertyNameV21.contains(propertyName.toUpperCase());
+ if (!(sAvailablePropertyNameV21.contains(propertyName.toUpperCase()) ||
+ propertyName.startsWith("X-")) &&
+ !mWarningValueMap.contains(propertyName)) {
+ mWarningValueMap.add(propertyName);
+ Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
+ }
+ return true;
}
/**
@@ -129,7 +185,7 @@ public class VCardParser_V21 {
line = getLine();
if (line == null) {
throw new VCardException("Reached end of buffer.");
- } else if (line.trim().length() > 0) {
+ } else if (line.trim().length() > 0) {
return line;
}
}
@@ -140,12 +196,37 @@ public class VCardParser_V21 {
* items *CRLF
* "END" [ws] ":" [ws] "VCARD"
*/
- private boolean parseOneVCard() throws IOException, VCardException {
- if (!readBeginVCard()) {
+ 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.startRecord("VCARD");
+ mTimeStartRecord += System.currentTimeMillis() - start;
+ }
+ start = System.currentTimeMillis();
parseItems();
- readEndVCard();
+ mTimeParseItems += System.currentTimeMillis() - start;
+ readEndVCard(true, false);
+ if (mBuilder != null) {
+ start = System.currentTimeMillis();
+ mBuilder.endRecord();
+ mTimeEndRecord += System.currentTimeMillis() - start;
+ }
return true;
}
@@ -154,46 +235,102 @@ public class VCardParser_V21 {
* @throws IOException
* @throws VCardException
*/
- protected boolean readBeginVCard() throws IOException, VCardException {
+ protected boolean readBeginVCard(boolean allowGarbage)
+ throws IOException, VCardException {
String line;
- while (true) {
- line = getLine();
- if (line == null) {
- return false;
- } else if (line.trim().length() > 0) {
- break;
+ do {
+ while (true) {
+ line = getLine();
+ if (line == null) {
+ return false;
+ } else if (line.trim().length() > 0) {
+ break;
+ }
}
- }
- String[] strArray = line.split(":", 2);
-
- // Though vCard specification does not allow lower cases,
- // some data may have them, so we allow it.
- if (!(strArray.length == 2 &&
- strArray[0].trim().equalsIgnoreCase("BEGIN") &&
- strArray[1].trim().equalsIgnoreCase("VCARD"))) {
- throw new VCardException("BEGIN:VCARD != \"" + line + "\"");
- }
-
- if (mBuilder != null) {
- mBuilder.startRecord("VCARD");
- }
+ String[] strArray = line.split(":", 2);
+ int length = strArray.length;
- return true;
+ // Though vCard 2.1/3.0 specification does not allow lower cases,
+ // some data may have them, so we allow it (Actually, previous code
+ // had explicitly allowed "BEGIN:vCard" though there's no example).
+ //
+ // TODO: ignore non vCard entry (e.g. vcalendar).
+ // XXX: Not sure, but according to VDataBuilder.java, vcalendar
+ // entry
+ // may be nested. Just seeking "END:SOMETHING" may not be enough.
+ // e.g.
+ // BEGIN:VCARD
+ // ... (Valid. Must parse this)
+ // END:VCARD
+ // BEGIN:VSOMETHING
+ // ... (Must ignore this)
+ // BEGIN:VSOMETHING2
+ // ... (Must ignore this)
+ // END:VSOMETHING2
+ // ... (Must ignore this!)
+ // END:VSOMETHING
+ // BEGIN:VCARD
+ // ... (Valid. Must parse this)
+ // END:VCARD
+ // INVALID_STRING (VCardException should be thrown)
+ 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.");
}
-
- protected void readEndVCard() throws VCardException {
- // Though vCard specification does not allow lower cases,
- // some data may have them, so we allow it.
- String[] strArray = mPreviousLine.split(":", 2);
- if (!(strArray.length == 2 &&
- strArray[0].trim().equalsIgnoreCase("END") &&
- strArray[1].trim().equalsIgnoreCase("VCARD"))) {
- throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
- }
-
- if (mBuilder != null) {
- mBuilder.endRecord();
- }
+
+ /**
+ * 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);
}
/**
@@ -205,32 +342,33 @@ public class VCardParser_V21 {
boolean ended = false;
if (mBuilder != null) {
+ long start = System.currentTimeMillis();
mBuilder.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - start;
}
-
- try {
- ended = parseItem();
- } finally {
- if (mBuilder != null) {
- mBuilder.endProperty();
- }
+ 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();
- } finally {
- if (mBuilder != null) {
- mBuilder.endProperty();
- }
+ ended = parseItem();
+ 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
@@ -241,57 +379,134 @@ public class VCardParser_V21 {
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
- // params = ";" [ws] paramlist
String line = getNonEmptyLine();
- String[] strArray = line.split(":", 2);
- if (strArray.length < 2) {
- throw new VCardException("Invalid line(\":\" does not exist): " + line);
- }
- String propertyValue = strArray[1];
- String[] groupNameParamsArray = strArray[0].split(";");
- String groupAndName = groupNameParamsArray[0].trim();
- String[] groupNameArray = groupAndName.split("\\.");
- int length = groupNameArray.length;
- String propertyName = groupNameArray[length - 1];
- if (mBuilder != null) {
- mBuilder.propertyName(propertyName);
- for (int i = 0; i < length - 1; i++) {
- mBuilder.propertyGroup(groupNameArray[i]);
- }
- }
- if (propertyName.equalsIgnoreCase("END")) {
- mPreviousLine = line;
+ long start = System.currentTimeMillis();
+
+ String[] propertyNameAndValue = separateLineAndHandleGroup(line);
+ if (propertyNameAndValue == null) {
return true;
}
-
- length = groupNameParamsArray.length;
- for (int i = 1; i < length; i++) {
- handleParams(groupNameParamsArray[i]);
+ if (propertyNameAndValue.length != 2) {
+ throw new VCardException("Invalid line \"" + line + "\"");
}
-
- if (isValidPropertyName(propertyName) ||
- propertyName.startsWith("X-")) {
- if (propertyName.equals("VERSION") &&
- !propertyValue.equals(getVersion())) {
- throw new VCardVersionException("Incompatible version: " +
- propertyValue + " != " + getVersion());
- }
- handlePropertyValue(propertyName, propertyValue);
- return false;
- } else if (propertyName.equals("ADR") ||
+ String propertyName = propertyNameAndValue[0].toUpperCase();
+ String propertyValue = propertyNameAndValue[1];
+
+ mTimeParseItem1 += System.currentTimeMillis() - start;
+
+ if (propertyName.equals("ADR") ||
propertyName.equals("ORG") ||
propertyName.equals("N")) {
+ start = System.currentTimeMillis();
handleMultiplePropertyValue(propertyName, propertyValue);
+ mTimeParseItem3 += 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(getVersion())) {
+ throw new VCardVersionException("Incompatible version: " +
+ propertyValue + " != " + getVersion());
+ }
+ start = System.currentTimeMillis();
+ handlePropertyValue(propertyName, propertyValue);
+ mTimeParseItem2 += 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.1 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 length = line.length();
+ int state = STATE_GROUP_OR_PROPNAME;
+ int nameIndex = 0;
+
+ String[] propertyNameAndValue = new String[2];
+
+ for (int i = 0; i < length; i++) {
+ char ch = line.charAt(i);
+ switch (state) {
+ case STATE_GROUP_OR_PROPNAME:
+ 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;
+ 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 VCardException("Invalid line: \"" + line + "\"");
+ }
+
+
/**
* params = ";" [ws] paramlist
* paramlist = paramlist [ws] ";" [ws] param
@@ -330,18 +545,19 @@ public class VCardParser_V21 {
}
/**
- * typeval = knowntype / "X-" word
+ * ptypeval = knowntype / "X-" word
*/
- protected void handleType(String ptypeval) throws VCardException {
- if (sKnownTypeSet.contains(ptypeval.toUpperCase()) ||
- ptypeval.startsWith("X-")) {
- if (mBuilder != null) {
- mBuilder.propertyParamType("TYPE");
- mBuilder.propertyParamValue(ptypeval.toUpperCase());
- }
- } else {
- throw new VCardException("Unknown type: \"" + ptypeval + "\"");
- }
+ protected void handleType(String ptypeval) {
+ String upperTypeValue = ptypeval;
+ if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
+ !mWarningValueMap.contains(ptypeval)) {
+ mWarningValueMap.add(ptypeval);
+ Log.w(LOG_TAG, "Type unsupported by vCard 2.1: " + ptypeval);
+ }
+ if (mBuilder != null) {
+ mBuilder.propertyParamType("TYPE");
+ mBuilder.propertyParamValue(upperTypeValue);
+ }
}
/**
@@ -427,31 +643,48 @@ public class VCardParser_V21 {
protected void handlePropertyValue(
String propertyName, String propertyValue) throws
IOException, VCardException {
- if (mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
- || mEncoding.equalsIgnoreCase("8BIT")
- || mEncoding.toUpperCase().startsWith("X-")) {
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(maybeUnescapeText(propertyValue));
- mBuilder.propertyValues(v);
- }
- } else if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ long start = System.currentTimeMillis();
String result = getQuotedPrintable(propertyValue);
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
mBuilder.propertyValues(v);
}
+ mTimeHandlePropertyValue2 += System.currentTimeMillis() - start;
} else if (mEncoding.equalsIgnoreCase("BASE64") ||
mEncoding.equalsIgnoreCase("B")) {
- String result = getBase64(propertyValue);
+ 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 {
+ 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);
+ }
+ }
+ mTimeHandlePropertyValue3 += 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 + "\".");
+ }
+
+ long start = System.currentTimeMillis();
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
- v.add(result);
+ v.add(maybeUnescapeText(propertyValue));
mBuilder.propertyValues(v);
- }
- } else {
- throw new VCardException("Unknown encoding: \"" + mEncoding + "\"");
+ }
+ mTimeHandlePropertyValue1 += System.currentTimeMillis() - start;
}
}
@@ -546,57 +779,51 @@ public class VCardParser_V21 {
if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
propertyValue = getQuotedPrintable(propertyValue);
}
-
- if (propertyValue.endsWith("\\")) {
+
+ if (mBuilder != null) {
+ // TODO: limit should be set in accordance with propertyName?
StringBuilder builder = new StringBuilder();
- // builder.append(propertyValue);
- builder.append(propertyValue.substring(0, propertyValue.length() - 1));
- try {
- String line;
- while (true) {
- line = getNonEmptyLine();
- // builder.append("\r\n");
- // builder.append(line);
- if (!line.endsWith("\\")) {
- builder.append(line);
- break;
+ ArrayList<String> list = new ArrayList<String>();
+ int length = propertyValue.length();
+ for (int i = 0; i < length; i++) {
+ char ch = propertyValue.charAt(i);
+ if (ch == '\\' && i < length - 1) {
+ char nextCh = propertyValue.charAt(i + 1);
+ String unescapedString = maybeUnescape(nextCh);
+ if (unescapedString != null) {
+ builder.append(unescapedString);
+ i++;
} else {
- builder.append(line.substring(0, line.length() - 1));
+ builder.append(ch);
}
+ } else if (ch == ';') {
+ list.add(builder.toString());
+ builder = new StringBuilder();
+ } else {
+ builder.append(ch);
}
- } catch (IOException e) {
- throw new VCardException(
- "IOException is throw during reading propertyValue" + e);
}
- // Now, propertyValue may contain "\r\n"
- propertyValue = builder.toString();
- }
-
- if (mBuilder != null) {
- // In String#replaceAll() and Pattern class, "\\\\" means single slash.
-
- final String IMPOSSIBLE_STRING = "\0";
- // First replace two backslashes with impossible strings.
- propertyValue = propertyValue.replaceAll("\\\\\\\\", IMPOSSIBLE_STRING);
-
- // Now, split propertyValue with ; whose previous char is not back slash.
- Pattern pattern = Pattern.compile("(?<!\\\\);");
- // TODO: limit should be set in accordance with propertyName?
- String[] strArray = pattern.split(propertyValue, -1);
- ArrayList<String> arrayList = new ArrayList<String>();
- for (String str : strArray) {
- // Replace impossible strings with original two backslashes
- arrayList.add(
- unescapeText(str.replaceAll(IMPOSSIBLE_STRING, "\\\\\\\\")));
- }
- mBuilder.propertyValues(arrayList);
+ list.add(builder.toString());
+ mBuilder.propertyValues(list);
}
}
/**
* 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(String propertyValue) throws IOException, VCardException {
+ protected void handleAgent(String propertyValue) throws VCardException {
+ throw new VCardException("AGENT Property is not supported.");
+ /* This is insufficient support. Also, AGENT Property is very rare.
+ Ignore it for now.
+ TODO: fix this.
+
String[] strArray = propertyValue.split(":", 2);
if (!(strArray.length == 2 ||
strArray[0].trim().equalsIgnoreCase("BEGIN") &&
@@ -605,6 +832,7 @@ public class VCardParser_V21 {
}
parseItems();
readEndVCard();
+ */
}
/**
@@ -615,17 +843,18 @@ public class VCardParser_V21 {
}
/**
- * Convert escaped text into unescaped 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 unescapeText(String text) {
+ protected String maybeUnescape(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.
- // In String#replaceAll(), "\\\\" means single slash.
- return text.replaceAll("\\\\;", ";")
- .replaceAll("\\\\:", ":")
- .replaceAll("\\\\,", ",")
- .replaceAll("\\\\\\\\", "\\\\");
+ if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
+ return String.valueOf(ch);
+ } else {
+ return null;
+ }
}
/**
@@ -656,12 +885,15 @@ public class VCardParser_V21 {
*/
public boolean parse(InputStream is, String charset, VBuilder builder)
throws IOException, VCardException {
+ // TODO: make this count error entries instead of just throwing VCardException.
+
// TODO: If we really need to allow only CRLF as line break,
// we will have to develop our own BufferedReader().
- mReader = new BufferedReader(new InputStreamReader(is, charset));
+ mReader = new CustomBufferedReader(new InputStreamReader(is, charset));
mBuilder = builder;
+ long start = System.currentTimeMillis();
if (mBuilder != null) {
mBuilder.start();
}
@@ -669,9 +901,50 @@ public class VCardParser_V21 {
if (mBuilder != null) {
mBuilder.end();
}
+ mTimeTotal += System.currentTimeMillis() - start;
+
return true;
}
+ public boolean parse(InputStream is, VBuilder builder) throws IOException, VCardException {
+ return parse(is, DEFAULT_CHARSET, builder);
+ }
+
+ /**
+ * Cancel parsing.
+ * Actual cancel is done after the end of the current one vcard entry parsing.
+ */
+ public void cancel() {
+ mCanceled = true;
+ }
+
+ /**
+ * It is very, very rare case, but there is a case where
+ * canceled may be already true outside this object.
+ * @hide
+ */
+ public void parse(InputStream is, String charset, VBuilder builder, boolean canceled)
+ throws IOException, VCardException {
+ mCanceled = canceled;
+ parse(is, charset, builder);
+ }
+
+ public void showDebugInfo() {
+ 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, "mTimeStartRecord: " + mTimeStartRecord + " ms");
+ Log.d(LOG_TAG, "mTimeEndRecord: " + mTimeEndRecord + " ms");
+ Log.d(LOG_TAG, "mTimeParseItem1: " + mTimeParseItem1 + " ms");
+ Log.d(LOG_TAG, "mTimeParseItem2: " + mTimeParseItem2 + " ms");
+ Log.d(LOG_TAG, "mTimeParseItem3: " + mTimeParseItem3 + " ms");
+ Log.d(LOG_TAG, "mTimeHandlePropertyValue1: " + mTimeHandlePropertyValue1 + " ms");
+ Log.d(LOG_TAG, "mTimeHandlePropertyValue2: " + mTimeHandlePropertyValue2 + " ms");
+ Log.d(LOG_TAG, "mTimeHandlePropertyValue3: " + mTimeHandlePropertyValue3 + " ms");
+ }
+
private boolean isLetter(char ch) {
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return true;
@@ -679,3 +952,24 @@ public class VCardParser_V21 {
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;
+ }
+}
diff --git a/core/java/android/syncml/pim/vcard/VCardParser_V30.java b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
index 901bd49..e67525e 100644
--- a/core/java/android/syncml/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/syncml/pim/vcard/VCardParser_V30.java
@@ -16,8 +16,9 @@
package android.syncml.pim.vcard;
+import android.util.Log;
+
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -26,9 +27,11 @@ import java.util.HashSet;
* Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426)
*/
public class VCardParser_V30 extends VCardParser_V21 {
+ private static final String LOG_TAG = "VCardParser_V30";
+
private static final HashSet<String> acceptablePropsWithParam = new HashSet<String>(
Arrays.asList(
- "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
+ "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",
@@ -51,8 +54,14 @@ public class VCardParser_V30 extends VCardParser_V21 {
@Override
protected boolean isValidPropertyName(String propertyName) {
- return acceptablePropsWithParam.contains(propertyName) ||
- acceptablePropsWithoutParam.contains(propertyName);
+ if (!(acceptablePropsWithParam.contains(propertyName) ||
+ acceptablePropsWithoutParam.contains(propertyName) ||
+ propertyName.startsWith("X-")) &&
+ !mWarningValueMap.contains(propertyName)) {
+ mWarningValueMap.add(propertyName);
+ Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
+ }
+ return true;
}
@Override
@@ -100,7 +109,21 @@ public class VCardParser_V30 extends VCardParser_V21 {
}
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
if (builder != null) {
- // TODO: Check whether MIME requires only one whitespace.
+ // 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();
@@ -113,10 +136,13 @@ public class VCardParser_V30 extends VCardParser_V21 {
} else {
if (mPreviousLine == null) {
mPreviousLine = line;
+ if (builder != null) {
+ return builder.toString();
+ }
} else {
String ret = mPreviousLine;
mPreviousLine = line;
- return ret;
+ return ret;
}
}
}
@@ -130,15 +156,16 @@ public class VCardParser_V30 extends VCardParser_V21 {
* [group "."] "END" ":" "VCARD" 1*CRLF
*/
@Override
- protected boolean readBeginVCard() throws IOException, VCardException {
+ protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
- return super.readBeginVCard();
+ return super.readBeginVCard(allowGarbage);
}
@Override
- protected void readEndVCard() throws VCardException {
+ protected void readEndVCard(boolean useCache, boolean allowGarbage)
+ throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
- super.readEndVCard();
+ super.readEndVCard(useCache, allowGarbage);
}
/**
@@ -214,23 +241,6 @@ public class VCardParser_V30 extends VCardParser_V21 {
throw new VCardException("AGENT in vCard 3.0 is not supported yet.");
}
- // vCard 3.0 supports "B" as BASE64 encoding.
- @Override
- protected void handlePropertyValue(
- String propertyName, String propertyValue) throws
- IOException, VCardException {
- if (mEncoding != null && mEncoding.equalsIgnoreCase("B")) {
- String result = getBase64(propertyValue);
- if (mBuilder != null) {
- ArrayList<String> v = new ArrayList<String>();
- v.add(result);
- mBuilder.propertyValues(v);
- }
- }
-
- super.handlePropertyValue(propertyName, propertyValue);
- }
-
/**
* vCard 3.0 does not require two CRLF at the last of BASE64 data.
* It only requires that data should be MIME-encoded.
@@ -259,27 +269,38 @@ public class VCardParser_V30 extends VCardParser_V21 {
}
/**
- * Return unescapeText(text).
- * In vCard 3.0, 8bit text is always encoded.
- */
- @Override
- protected String maybeUnescapeText(String text) {
- return unescapeText(text);
- }
-
- /**
* ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
* ; \\ encodes \, \n or \N encodes newline
* ; \; encodes ;, \, encodes ,
- */
+ *
+ * Note: Apple escape ':' into '\:' while does not escape '\'
+ */
@Override
- protected String unescapeText(String text) {
- // In String#replaceAll(), "\\\\" means single slash.
- return text.replaceAll("\\\\;", ";")
- .replaceAll("\\\\:", ":")
- .replaceAll("\\\\,", ",")
- .replaceAll("\\\\n", "\r\n")
- .replaceAll("\\\\N", "\r\n")
- .replaceAll("\\\\\\\\", "\\\\");
+ protected String maybeUnescapeText(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("\r\n");
+ } else {
+ builder.append(next_ch);
+ }
+ } else {
+ builder.append(ch);
+ }
+ }
+ return builder.toString();
+ }
+
+ @Override
+ protected String maybeUnescape(char ch) {
+ if (ch == 'n' || ch == 'N') {
+ return "\r\n";
+ } else {
+ return String.valueOf(ch);
+ }
}
}
diff --git a/core/java/android/syncml/pim/vcard/VCardSourceDetector.java b/core/java/android/syncml/pim/vcard/VCardSourceDetector.java
new file mode 100644
index 0000000..8c48391
--- /dev/null
+++ b/core/java/android/syncml/pim/vcard/VCardSourceDetector.java
@@ -0,0 +1,140 @@
+/*
+ * 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.syncml.pim.vcard;
+
+import android.syncml.pim.VBuilder;
+
+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
+ */
+public class VCardSourceDetector implements VBuilder {
+ // Should only be used in package.
+ static final int TYPE_UNKNOWN = 0;
+ static final int TYPE_APPLE = 1;
+ static final int TYPE_JAPANESE_MOBILE_PHONE = 2; // Used in Japanese mobile phones.
+ static final int TYPE_FOMA = 3; // Used in some Japanese FOMA mobile phones.
+ static final int TYPE_WINDOWS_MOBILE_JP = 4;
+ // TODO: Excel, etc.
+
+ 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"));
+
+ private static Set<String> JAPANESE_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
+ "X-GNO", "X-GN", "X-REDUCTION"));
+
+ private static Set<String> WINDOWS_MOBILE_PHONE_SIGNS = new HashSet<String>(Arrays.asList(
+ "X-MICROSOFT-ASST_TEL", "X-MICROSOFT-ASSISTANT", "X-MICROSOFT-OFFICELOC"));
+
+ // Note: these signes appears before the signs of the other type (e.g. "X-GN").
+ // In other words, Japanese FOMA mobile phones are detected as FOMA, not JAPANESE_MOBILE_PHONES.
+ private static Set<String> FOMA_SIGNS = new HashSet<String>(Arrays.asList(
+ "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 = TYPE_UNKNOWN;
+ // Some mobile phones (like FOMA) tells us the charset of the data.
+ private boolean mNeedParseSpecifiedCharset;
+ private String mSpecifiedCharset;
+
+ public void start() {
+ }
+
+ public void end() {
+ }
+
+ public void startRecord(String type) {
+ }
+
+ public void startProperty() {
+ mNeedParseSpecifiedCharset = false;
+ }
+
+ public void endProperty() {
+ }
+
+ public void endRecord() {
+ }
+
+ public void propertyGroup(String group) {
+ }
+
+ public void propertyName(String name) {
+ if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
+ mType = TYPE_FOMA;
+ mNeedParseSpecifiedCharset = true;
+ return;
+ }
+ if (mType != TYPE_UNKNOWN) {
+ return;
+ }
+ if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
+ mType = TYPE_WINDOWS_MOBILE_JP;
+ } else if (FOMA_SIGNS.contains(name)) {
+ mType = TYPE_FOMA;
+ } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
+ mType = TYPE_JAPANESE_MOBILE_PHONE;
+ } else if (APPLE_SIGNS.contains(name)) {
+ mType = TYPE_APPLE;
+ }
+ }
+
+ public void propertyParamType(String type) {
+ }
+
+ public void propertyParamValue(String value) {
+ }
+
+ public void propertyValues(List<String> values) {
+ if (mNeedParseSpecifiedCharset && values.size() > 0) {
+ mSpecifiedCharset = values.get(0);
+ }
+ }
+
+ int getType() {
+ return mType;
+ }
+
+ /**
+ * Return charset String guessed from the source's properties.
+ * This method must be called after parsing target file(s).
+ * @return Charset String. Null is returned if guessing the source fails.
+ */
+ public String getEstimatedCharset() {
+ if (mSpecifiedCharset != null) {
+ return mSpecifiedCharset;
+ }
+ switch (mType) {
+ case TYPE_WINDOWS_MOBILE_JP:
+ case TYPE_FOMA:
+ case TYPE_JAPANESE_MOBILE_PHONE:
+ return "SHIFT_JIS";
+ case TYPE_APPLE:
+ return "UTF-8";
+ default:
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/test/AndroidTestCase.java b/core/java/android/test/AndroidTestCase.java
index 9bafa32..de0587a 100644
--- a/core/java/android/test/AndroidTestCase.java
+++ b/core/java/android/test/AndroidTestCase.java
@@ -16,12 +16,14 @@
package android.test;
+import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import junit.framework.TestCase;
import java.lang.reflect.Field;
-import junit.framework.TestCase;
-
/**
* Extend this if you need to access Resources or other things that depend on Activity Context.
*/
@@ -53,6 +55,72 @@ public class AndroidTestCase extends TestCase {
}
/**
+ * Asserts that launching a given activity is protected by a particular permission by
+ * attempting to start the activity and validating that a {@link SecurityException}
+ * is thrown that mentions the permission in its error message.
+ *
+ * Note that an instrumentation isn't needed because all we are looking for is a security error
+ * and we don't need to wait for the activity to launch and get a handle to the activity.
+ *
+ * @param packageName The package name of the activity to launch.
+ * @param className The class of the activity to launch.
+ * @param permission The name of the permission.
+ */
+ public void assertActivityRequiresPermission(
+ String packageName, String className, String permission) {
+ final Intent intent = new Intent();
+ intent.setClassName(packageName, className);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ try {
+ getContext().startActivity(intent);
+ fail("expected security exception for " + permission);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ assertTrue("error message should contain " + permission + ".",
+ expected.getMessage().contains(permission));
+ }
+ }
+
+
+ /**
+ * Asserts that reading from the content uri requires a particular permission by querying the
+ * uri and ensuring a {@link SecurityException} is thrown mentioning the particular permission.
+ *
+ * @param uri The uri that requires a permission to query.
+ * @param permission The permission that should be required.
+ */
+ public void assertReadingContentUriRequiresPermission(Uri uri, String permission) {
+ try {
+ getContext().getContentResolver().query(uri, null, null, null, null);
+ fail("expected SecurityException requiring " + permission);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ assertTrue("error message should contain " + permission + ".",
+ expected.getMessage().contains(permission));
+ }
+ }
+
+ /**
+ * Asserts that writing to the content uri requires a particular permission by inserting into
+ * the uri and ensuring a {@link SecurityException} is thrown mentioning the particular
+ * permission.
+ *
+ * @param uri The uri that requires a permission to query.
+ * @param permission The permission that should be required.
+ */
+ public void assertWritingContentUriRequiresPermission(Uri uri, String permission) {
+ try {
+ getContext().getContentResolver().insert(uri, new ContentValues());
+ fail("expected SecurityException requiring " + permission);
+ } catch (SecurityException expected) {
+ assertNotNull("security exception's error message.", expected.getMessage());
+ assertTrue("error message should contain " + permission + ".",
+ expected.getMessage().contains(permission));
+ }
+ }
+
+ /**
* This function is called by various TestCase implementations, at tearDown() time, in order
* to scrub out any class variables. This protects against memory leaks in the case where a
* test case creates a non-static inner class (thus referencing the test case) and gives it to
diff --git a/core/java/android/test/InstrumentationTestCase.java b/core/java/android/test/InstrumentationTestCase.java
index 470ab0d..2145d7c 100644
--- a/core/java/android/test/InstrumentationTestCase.java
+++ b/core/java/android/test/InstrumentationTestCase.java
@@ -241,7 +241,13 @@ public class InstrumentationTestCase extends TestCase {
try {
final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key);
final int keyCode = keyCodeField.getInt(null);
- instrumentation.sendKeyDownUpSync(keyCode);
+ try {
+ instrumentation.sendKeyDownUpSync(keyCode);
+ } catch (SecurityException e) {
+ // Ignore security exceptions that are now thrown
+ // when trying to send to another app, to retain
+ // compatibility with existing tests.
+ }
} catch (NoSuchFieldException e) {
Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
break;
@@ -266,7 +272,13 @@ public class InstrumentationTestCase extends TestCase {
final Instrumentation instrumentation = getInstrumentation();
for (int i = 0; i < count; i++) {
- instrumentation.sendKeyDownUpSync(keys[i]);
+ try {
+ instrumentation.sendKeyDownUpSync(keys[i]);
+ } catch (SecurityException e) {
+ // Ignore security exceptions that are now thrown
+ // when trying to send to another app, to retain
+ // compatibility with existing tests.
+ }
}
instrumentation.waitForIdleSync();
@@ -292,7 +304,13 @@ public class InstrumentationTestCase extends TestCase {
final int keyCount = keys[i];
final int keyCode = keys[i + 1];
for (int j = 0; j < keyCount; j++) {
- instrumentation.sendKeyDownUpSync(keyCode);
+ try {
+ instrumentation.sendKeyDownUpSync(keyCode);
+ } catch (SecurityException e) {
+ // Ignore security exceptions that are now thrown
+ // when trying to send to another app, to retain
+ // compatibility with existing tests.
+ }
}
}
diff --git a/core/java/android/text/LoginFilter.java b/core/java/android/text/LoginFilter.java
index 27c703f..9045c09 100644
--- a/core/java/android/text/LoginFilter.java
+++ b/core/java/android/text/LoginFilter.java
@@ -49,10 +49,6 @@ public abstract class LoginFilter implements InputFilter {
*/
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
- char[] out = new char[end - start]; // reserve enough space for whole string
- int outidx = 0;
- boolean changed = false;
-
onStart();
// Scan through beginning characters in dest, calling onInvalidCharacter()
@@ -63,14 +59,26 @@ public abstract class LoginFilter implements InputFilter {
}
// Scan through changed characters rejecting disallowed chars
+ SpannableStringBuilder modification = null;
+ int modoff = 0;
+
for (int i = start; i < end; i++) {
char c = source.charAt(i);
if (isAllowed(c)) {
- // Character allowed. Add it to the sequence.
- out[outidx++] = c;
+ // Character allowed.
+ modoff++;
} else {
- if (mAppendInvalid) out[outidx++] = c;
- else changed = true; // we changed the original string
+ if (mAppendInvalid) {
+ modoff++;
+ } else {
+ if (modification == null) {
+ modification = new SpannableStringBuilder(source, start, end);
+ modoff = i - start;
+ }
+
+ modification.delete(modoff, modoff + 1);
+ }
+
onInvalidCharacter(c);
}
}
@@ -84,20 +92,9 @@ public abstract class LoginFilter implements InputFilter {
onStop();
- if (!changed) {
- return null;
- }
-
- String s = new String(out, 0, outidx);
-
- if (source instanceof Spanned) {
- SpannableString sp = new SpannableString(s);
- TextUtils.copySpansFrom((Spanned) source,
- start, end, null, sp, 0);
- return sp;
- } else {
- return s;
- }
+ // Either returns null if we made no changes,
+ // or what we wanted to change it to if there were changes.
+ return modification;
}
/**
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 5b4c380..53096dd 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -916,6 +916,17 @@ public class TextUtils {
sp.setSpan(o, p.readInt(), p.readInt(), p.readInt());
}
+ /**
+ * Copies the spans from the region <code>start...end</code> in
+ * <code>source</code> to the region
+ * <code>destoff...destoff+end-start</code> in <code>dest</code>.
+ * Spans in <code>source</code> that begin before <code>start</code>
+ * or end after <code>end</code> but overlap this range are trimmed
+ * as if they began at <code>start</code> or ended at <code>end</code>.
+ *
+ * @throws IndexOutOfBoundsException if any of the copied spans
+ * are out of range in <code>dest</code>.
+ */
public static void copySpansFrom(Spanned source, int start, int end,
Class kind,
Spannable dest, int destoff) {
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 0dc96c3..3d10f17 100644
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -242,7 +242,7 @@ public class DateFormat {
/**
* Returns a {@link java.text.DateFormat} object that can format the time according
- * to the current user preference.
+ * to the current locale and the user's 12-/24-hour clock preference.
* @param context the application context
* @return the {@link java.text.DateFormat} object that properly formats the time.
*/
@@ -260,46 +260,88 @@ public class DateFormat {
}
/**
- * Returns a {@link java.text.DateFormat} object that can format the date according
- * to the current user preference.
+ * Returns a {@link java.text.DateFormat} object that can format the date
+ * in short form (such as 12/31/1999) according
+ * to the current locale and the user's date-order preference.
* @param context the application context
* @return the {@link java.text.DateFormat} object that properly formats the date.
*/
public static final java.text.DateFormat getDateFormat(Context context) {
- String value = getDateFormatString(context);
+ String value = Settings.System.getString(context.getContentResolver(),
+ Settings.System.DATE_FORMAT);
+
+ return getDateFormatForSetting(context, value);
+ }
+
+ /**
+ * Returns a {@link java.text.DateFormat} object to format the date
+ * as if the date format setting were set to <code>value</code>,
+ * including null to use the locale's default format.
+ * @param context the application context
+ * @param value the date format setting string to interpret for
+ * the current locale
+ * @hide
+ */
+ public static java.text.DateFormat getDateFormatForSetting(Context context,
+ String value) {
+ if (value != null) {
+ int month = value.indexOf('M');
+ int day = value.indexOf('d');
+ int year = value.indexOf('y');
+
+ if (month >= 0 && day >= 0 && year >= 0) {
+ String template = context.getString(R.string.numeric_date_template);
+ if (year < month) {
+ if (month < day) {
+ value = String.format(template, "yyyy", "MM", "dd");
+ } else {
+ value = String.format(template, "yyyy", "dd", "MM");
+ }
+ } else if (month < day) {
+ if (day < year) {
+ value = String.format(template, "MM", "dd", "yyyy");
+ } else { // unlikely
+ value = String.format(template, "MM", "yyyy", "dd");
+ }
+ } else { // day < month
+ if (month < year) {
+ value = String.format(template, "dd", "MM", "yyyy");
+ } else { // unlikely
+ value = String.format(template, "dd", "yyyy", "MM");
+ }
+ }
+
+ return new java.text.SimpleDateFormat(value);
+ }
+ }
+
+ /*
+ * The setting is not set; use the default.
+ * We use a resource string here instead of just DateFormat.SHORT
+ * so that we get a four-digit year instead a two-digit year.
+ */
+ value = context.getString(R.string.numeric_date_format);
return new java.text.SimpleDateFormat(value);
}
/**
* Returns a {@link java.text.DateFormat} object that can format the date
- * in long form (such as December 31, 1999) based on user preference.
+ * in long form (such as December 31, 1999) for the current locale.
* @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form.
*/
public static final java.text.DateFormat getLongDateFormat(Context context) {
- String value = getDateFormatString(context);
- if (value.indexOf('M') < value.indexOf('d')) {
- value = context.getString(R.string.full_date_month_first);
- } else {
- value = context.getString(R.string.full_date_day_first);
- }
- return new java.text.SimpleDateFormat(value);
+ return java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
}
/**
* Returns a {@link java.text.DateFormat} object that can format the date
- * in medium form (such as Dec. 31, 1999) based on user preference.
+ * in medium form (such as Dec. 31, 1999) for the current locale.
* @param context the application context
* @return the {@link java.text.DateFormat} object that formats the date in long form.
*/
public static final java.text.DateFormat getMediumDateFormat(Context context) {
- String value = getDateFormatString(context);
- if (value.indexOf('M') < value.indexOf('d')) {
- value = context.getString(R.string.medium_date_month_first);
- } else {
- value = context.getString(R.string.medium_date_day_first);
- }
- return new java.text.SimpleDateFormat(value);
+ return java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM);
}
/**
@@ -338,6 +380,12 @@ public class DateFormat {
}
private static String getDateFormatString(Context context) {
+ java.text.DateFormat df;
+ df = java.text.DateFormat.getDateInstance(java.text.DateFormat.SHORT);
+ if (df instanceof SimpleDateFormat) {
+ return ((SimpleDateFormat) df).toPattern();
+ }
+
String value = Settings.System.getString(context.getContentResolver(),
Settings.System.DATE_FORMAT);
if (value == null || value.length() < 6) {
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 8a7cdd9..1a4eb69 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -62,15 +62,6 @@ public class DateUtils
com.android.internal.R.string.day_of_week_short_friday,
com.android.internal.R.string.day_of_week_short_saturday,
};
- private static final int[] sDaysShorter = new int[] {
- com.android.internal.R.string.day_of_week_shorter_sunday,
- com.android.internal.R.string.day_of_week_shorter_monday,
- com.android.internal.R.string.day_of_week_shorter_tuesday,
- com.android.internal.R.string.day_of_week_shorter_wednesday,
- com.android.internal.R.string.day_of_week_shorter_thursday,
- com.android.internal.R.string.day_of_week_shorter_friday,
- com.android.internal.R.string.day_of_week_shorter_saturday,
- };
private static final int[] sDaysShortest = new int[] {
com.android.internal.R.string.day_of_week_shortest_sunday,
com.android.internal.R.string.day_of_week_shortest_monday,
@@ -80,6 +71,20 @@ public class DateUtils
com.android.internal.R.string.day_of_week_shortest_friday,
com.android.internal.R.string.day_of_week_shortest_saturday,
};
+ private static final int[] sMonthsStandaloneLong = new int [] {
+ com.android.internal.R.string.month_long_standalone_january,
+ com.android.internal.R.string.month_long_standalone_february,
+ com.android.internal.R.string.month_long_standalone_march,
+ com.android.internal.R.string.month_long_standalone_april,
+ com.android.internal.R.string.month_long_standalone_may,
+ com.android.internal.R.string.month_long_standalone_june,
+ com.android.internal.R.string.month_long_standalone_july,
+ com.android.internal.R.string.month_long_standalone_august,
+ com.android.internal.R.string.month_long_standalone_september,
+ com.android.internal.R.string.month_long_standalone_october,
+ com.android.internal.R.string.month_long_standalone_november,
+ com.android.internal.R.string.month_long_standalone_december,
+ };
private static final int[] sMonthsLong = new int [] {
com.android.internal.R.string.month_long_january,
com.android.internal.R.string.month_long_february,
@@ -127,7 +132,7 @@ public class DateUtils
com.android.internal.R.string.pm,
};
private static Configuration sLastConfig;
- private static String sStatusTimeFormat;
+ private static java.text.DateFormat sStatusTimeFormat;
private static String sElapsedFormatMMSS;
private static String sElapsedFormatHMMSS;
@@ -142,6 +147,9 @@ public class DateUtils
public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7;
+ /**
+ * This constant is actually the length of 364 days, not of a year!
+ */
public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52;
// The following FORMAT_* symbols are used for specifying the format of
@@ -171,8 +179,14 @@ public class DateUtils
// Date and time format strings that are constant and don't need to be
// translated.
+ /**
+ * This is not actually the preferred 24-hour date format in all locales.
+ */
public static final String HOUR_MINUTE_24 = "%H:%M";
public static final String MONTH_FORMAT = "%B";
+ /**
+ * This is not actually a useful month name in all locales.
+ */
public static final String ABBREV_MONTH_FORMAT = "%b";
public static final String NUMERIC_MONTH_FORMAT = "%m";
public static final String MONTH_DAY_FORMAT = "%-d";
@@ -255,18 +269,15 @@ public class DateUtils
* For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
* <p>e.g. "Su" or "Jan"
- * <p>In some languages, the results returned for LENGTH_SHORT may be the same as
- * return for {@link #LENGTH_MEDIUM}.
+ * <p>In most languages, the results returned for LENGTH_SHORT will be the same as
+ * the results returned for {@link #LENGTH_MEDIUM}.
*/
public static final int LENGTH_SHORT = 30;
/**
* Request an even shorter abbreviated version of the name.
- * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
- * @more
- * <p>e.g. "M", "Tu", "Th" or "J"
- * <p>In some languages, the results returned for LENGTH_SHORTEST may be the same as
- * return for {@link #LENGTH_SHORTER}.
+ * Do not use this. Currently this will always return the same result
+ * as {@link #LENGTH_SHORT}.
*/
public static final int LENGTH_SHORTER = 40;
@@ -275,8 +286,8 @@ public class DateUtils
* For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}.
* @more
* <p>e.g. "S", "T", "T" or "J"
- * <p>In some languages, the results returned for LENGTH_SHORTEST may be the same as
- * return for {@link #LENGTH_SHORTER}.
+ * <p>In some languages, the results returned for LENGTH_SHORTEST will be the same as
+ * the results returned for {@link #LENGTH_SHORT}.
*/
public static final int LENGTH_SHORTEST = 50;
@@ -284,9 +295,12 @@ public class DateUtils
* Return a string for the day of the week.
* @param dayOfWeek One of {@link Calendar#SUNDAY Calendar.SUNDAY},
* {@link Calendar#MONDAY Calendar.MONDAY}, etc.
- * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, {@link #LENGTH_SHORTER}
- * or {@link #LENGTH_SHORTEST}. For forward compatibility, anything else
- * will return the same as {#LENGTH_MEDIUM}.
+ * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT},
+ * {@link #LENGTH_MEDIUM}, or {@link #LENGTH_SHORTEST}.
+ * Note that in most languages, {@link #LENGTH_SHORT}
+ * will return the same as {@link #LENGTH_MEDIUM}.
+ * Undefined lengths will return {@link #LENGTH_MEDIUM}
+ * but may return something different in the future.
* @throws IndexOutOfBoundsException if the dayOfWeek is out of bounds.
*/
public static String getDayOfWeekString(int dayOfWeek, int abbrev) {
@@ -295,7 +309,7 @@ public class DateUtils
case LENGTH_LONG: list = sDaysLong; break;
case LENGTH_MEDIUM: list = sDaysMedium; break;
case LENGTH_SHORT: list = sDaysShort; break;
- case LENGTH_SHORTER: list = sDaysShorter; break;
+ case LENGTH_SHORTER: list = sDaysShort; break;
case LENGTH_SHORTEST: list = sDaysShortest; break;
default: list = sDaysMedium; break;
}
@@ -316,13 +330,14 @@ public class DateUtils
}
/**
- * Return a localized string for the day of the week.
+ * Return a localized string for the month of the year.
* @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
* {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
- * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_SHORT}, {@link #LENGTH_SHORTER}
- * or {@link #LENGTH_SHORTEST}. For forward compatibility, anything else
- * will return the same as {#LENGTH_MEDIUM}.
- * @return Localized day of the week.
+ * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM},
+ * or {@link #LENGTH_SHORTEST}.
+ * Undefined lengths will return {@link #LENGTH_MEDIUM}
+ * but may return something different in the future.
+ * @return Localized month of the year.
*/
public static String getMonthString(int month, int abbrev) {
// Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER.
@@ -344,6 +359,40 @@ public class DateUtils
}
/**
+ * Return a localized string for the month of the year, for
+ * contexts where the month is not formatted together with
+ * a day of the month.
+ *
+ * @param month One of {@link Calendar#JANUARY Calendar.JANUARY},
+ * {@link Calendar#FEBRUARY Calendar.FEBRUARY}, etc.
+ * @param abbrev One of {@link #LENGTH_LONG}, {@link #LENGTH_MEDIUM},
+ * or {@link #LENGTH_SHORTEST}.
+ * Undefined lengths will return {@link #LENGTH_MEDIUM}
+ * but may return something different in the future.
+ * @return Localized month of the year.
+ * @hide Pending API council approval
+ */
+ public static String getStandaloneMonthString(int month, int abbrev) {
+ // Note that here we use sMonthsMedium for MEDIUM, SHORT and SHORTER.
+ // This is a shortcut to not spam the translators with too many variations
+ // of the same string. If we find that in a language the distinction
+ // is necessary, we can can add more without changing this API.
+ int[] list;
+ switch (abbrev) {
+ case LENGTH_LONG: list = sMonthsStandaloneLong;
+ break;
+ case LENGTH_MEDIUM: list = sMonthsMedium; break;
+ case LENGTH_SHORT: list = sMonthsMedium; break;
+ case LENGTH_SHORTER: list = sMonthsMedium; break;
+ case LENGTH_SHORTEST: list = sMonthsShortest; break;
+ default: list = sMonthsMedium; break;
+ }
+
+ Resources r = Resources.getSystem();
+ return r.getString(list[month - Calendar.JANUARY]);
+ }
+
+ /**
* Returns a string describing the elapsed time since startTime.
* @param startTime some time in the past.
* @return a String object containing the elapsed time.
@@ -572,7 +621,7 @@ public class DateUtils
Configuration cfg = r.getConfiguration();
if (sLastConfig == null || !sLastConfig.equals(cfg)) {
sLastConfig = cfg;
- sStatusTimeFormat = r.getString(com.android.internal.R.string.status_bar_time_format);
+ sStatusTimeFormat = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT);
sElapsedFormatMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_mm_ss);
sElapsedFormatHMMSS = r.getString(com.android.internal.R.string.elapsed_time_short_format_h_mm_ss);
}
@@ -586,7 +635,7 @@ public class DateUtils
*/
public static final CharSequence timeString(long millis) {
initFormatStrings();
- return DateFormat.format(sStatusTimeFormat, millis);
+ return sStatusTimeFormat.format(millis);
}
/**
@@ -1066,7 +1115,9 @@ public class DateUtils
*
* <p>
* If FORMAT_CAP_AMPM is set and 12-hour time is used, then the "AM"
- * and "PM" are capitalized.
+ * and "PM" are capitalized. You should not use this flag
+ * because in some locales these terms cannot be capitalized, and in
+ * many others it doesn't make sense to do so even though it is possible.
*
* <p>
* If FORMAT_NO_NOON is set and 12-hour time is used, then "12pm" is
@@ -1074,15 +1125,19 @@ public class DateUtils
*
* <p>
* If FORMAT_CAP_NOON is set and 12-hour time is used, then "Noon" is
- * shown instead of "noon".
+ * shown instead of "noon". You should probably not use this flag
+ * because in many locales it will not make sense to capitalize
+ * the term.
*
* <p>
* If FORMAT_NO_MIDNIGHT is set and 12-hour time is used, then "12am" is
* shown instead of "midnight".
*
* <p>
- * If FORMAT_CAP_NOON is set and 12-hour time is used, then "Midnight" is
- * shown instead of "midnight".
+ * If FORMAT_CAP_MIDNIGHT is set and 12-hour time is used, then "Midnight"
+ * is shown instead of "midnight". You should probably not use this
+ * flag because in many locales it will not make sense to capitalize
+ * the term.
*
* <p>
* If FORMAT_12HOUR is set and the time is shown, then the time is
@@ -1224,8 +1279,8 @@ public class DateUtils
use24Hour = DateFormat.is24HourFormat(context);
}
if (use24Hour) {
- startTimeFormat = HOUR_MINUTE_24;
- endTimeFormat = HOUR_MINUTE_24;
+ startTimeFormat = endTimeFormat =
+ res.getString(com.android.internal.R.string.hour_minute_24);
} else {
boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0;
boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0;
@@ -1392,7 +1447,8 @@ public class DateUtils
if (numericDate) {
monthFormat = NUMERIC_MONTH_FORMAT;
} else if (abbrevMonth) {
- monthFormat = ABBREV_MONTH_FORMAT;
+ monthFormat =
+ res.getString(com.android.internal.R.string.short_format_month);
} else {
monthFormat = MONTH_FORMAT;
}
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index 1b30aa0..367b26c 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -59,9 +59,15 @@ public final class Formatter {
result = result / 1024;
}
if (result < 100) {
- return String.format("%.2f%s", result, context.getText(suffix).toString());
+ String value = String.format("%.2f", result);
+ return context.getResources().
+ getString(com.android.internal.R.string.fileSizeSuffix,
+ value, context.getString(suffix));
}
- return String.format("%.0f%s", result, context.getText(suffix).toString());
+ String value = String.format("%.0f", result);
+ return context.getResources().
+ getString(com.android.internal.R.string.fileSizeSuffix,
+ value, context.getString(suffix));
}
/**
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index daa99c2..8eae111 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -135,6 +135,7 @@ public class Time {
private static Locale sLocale;
private static String[] sShortMonths;
private static String[] sLongMonths;
+ private static String[] sLongStandaloneMonths;
private static String[] sShortWeekdays;
private static String[] sLongWeekdays;
private static String sTimeOnlyFormat;
@@ -321,6 +322,20 @@ public class Time {
r.getString(com.android.internal.R.string.month_long_november),
r.getString(com.android.internal.R.string.month_long_december),
};
+ sLongStandaloneMonths = new String[] {
+ r.getString(com.android.internal.R.string.month_long_standalone_january),
+ r.getString(com.android.internal.R.string.month_long_standalone_february),
+ r.getString(com.android.internal.R.string.month_long_standalone_march),
+ r.getString(com.android.internal.R.string.month_long_standalone_april),
+ r.getString(com.android.internal.R.string.month_long_standalone_may),
+ r.getString(com.android.internal.R.string.month_long_standalone_june),
+ r.getString(com.android.internal.R.string.month_long_standalone_july),
+ r.getString(com.android.internal.R.string.month_long_standalone_august),
+ r.getString(com.android.internal.R.string.month_long_standalone_september),
+ r.getString(com.android.internal.R.string.month_long_standalone_october),
+ r.getString(com.android.internal.R.string.month_long_standalone_november),
+ r.getString(com.android.internal.R.string.month_long_standalone_december),
+ };
sShortWeekdays = new String[] {
r.getString(com.android.internal.R.string.day_of_week_medium_sunday),
r.getString(com.android.internal.R.string.day_of_week_medium_monday),
@@ -438,6 +453,7 @@ public class Time {
*
* @param s the string to parse
* @return true if the resulting time value is in UTC time
+ * @throws android.util.TimeFormatException if s cannot be parsed.
*/
public boolean parse3339(String s) {
if (nativeParse3339(s)) {
diff --git a/core/java/android/text/method/DialerKeyListener.java b/core/java/android/text/method/DialerKeyListener.java
index b121e60..584e83f 100644
--- a/core/java/android/text/method/DialerKeyListener.java
+++ b/core/java/android/text/method/DialerKeyListener.java
@@ -106,7 +106,7 @@ public class DialerKeyListener extends NumberKeyListener
*/
public static final char[] CHARACTERS = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*',
- '+', '-', '(', ')', ',', '/', 'N', '.', ' '
+ '+', '-', '(', ')', ',', '/', 'N', '.', ' ', ';'
};
private static DialerKeyListener sInstance;
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index f2fb9cb..dfc16f5 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -81,6 +81,12 @@ public class Touch {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
+ ds = buffer.getSpans(0, buffer.length(), DragState.class);
+
+ for (int i = 0; i < ds.length; i++) {
+ buffer.removeSpan(ds[i]);
+ }
+
buffer.setSpan(new DragState(event.getX(), event.getY(),
widget.getScrollX(), widget.getScrollY()),
0, 0, Spannable.SPAN_MARK_MARK);
diff --git a/core/java/android/util/CharsetUtils.java b/core/java/android/util/CharsetUtils.java
index 7553029..9d91aca 100644
--- a/core/java/android/util/CharsetUtils.java
+++ b/core/java/android/util/CharsetUtils.java
@@ -142,20 +142,25 @@ public final class CharsetUtils {
/**
* Returns whether the given character set name indicates the Shift-JIS
- * encoding.
+ * encoding. Returns false if the name is null.
*
* @param charsetName the character set name
* @return {@code true} if the name corresponds to Shift-JIS or
* {@code false} if not
*/
private static boolean isShiftJis(String charsetName) {
- if (charsetName.length() != 9) {
- // Bail quickly if the length doesn't match.
+ // Bail quickly if the length doesn't match.
+ if (charsetName == null) {
+ return false;
+ }
+ int length = charsetName.length();
+ if (length != 4 && length != 9) {
return false;
}
return charsetName.equalsIgnoreCase("shift_jis")
- || charsetName.equalsIgnoreCase("shift-jis");
+ || charsetName.equalsIgnoreCase("shift-jis")
+ || charsetName.equalsIgnoreCase("sjis");
}
/**
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index e4dd020..4179edb 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -16,6 +16,8 @@
package android.util;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.os.*;
@@ -35,8 +37,7 @@ public class DisplayMetrics {
* The device's density.
* @hide
*/
- public static final int DEVICE_DENSITY = SystemProperties.getInt("ro.sf.lcd_density",
- DEFAULT_DENSITY);
+ public static final int DEVICE_DENSITY = getDeviceDensity();
/**
* The absolute width of the display in pixels.
@@ -101,22 +102,83 @@ public class DisplayMetrics {
}
/**
- * Set the display metrics' density and update parameters depend on it.
- * @hide
+ * Update the display metrics based on the compatibility info and orientation
+ * NOTE: DO NOT EXPOSE THIS API! It is introducing a circular dependency
+ * with the higher-level android.res package.
+ * {@hide}
*/
- public void updateDensity(float newDensity) {
- float ratio = newDensity / density;
- density = newDensity;
- scaledDensity = density;
- widthPixels *= ratio;
- heightPixels *= ratio;
- xdpi *= ratio;
- ydpi *= ratio;
+ public void updateMetrics(CompatibilityInfo compatibilityInfo, int orientation,
+ int screenLayout) {
+ int xOffset = 0;
+ if (!compatibilityInfo.isConfiguredExpandable()) {
+ // Note: this assume that configuration is updated before calling
+ // updateMetrics method.
+ if (screenLayout == Configuration.SCREENLAYOUT_LARGE) {
+ // This is a large screen device and the app is not
+ // compatible with large screens, to diddle it.
+
+ compatibilityInfo.setExpandable(false);
+ // Figure out the compatibility width and height of the screen.
+ int defaultWidth;
+ int defaultHeight;
+ switch (orientation) {
+ case Configuration.ORIENTATION_LANDSCAPE: {
+ defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density);
+ defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density);
+ break;
+ }
+ case Configuration.ORIENTATION_PORTRAIT:
+ case Configuration.ORIENTATION_SQUARE:
+ default: {
+ defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density);
+ defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density);
+ break;
+ }
+ case Configuration.ORIENTATION_UNDEFINED: {
+ // don't change
+ return;
+ }
+ }
+
+ if (defaultWidth < widthPixels) {
+ // content/window's x offset in original pixels
+ xOffset = ((widthPixels - defaultWidth) / 2);
+ widthPixels = defaultWidth;
+ }
+ if (defaultHeight < heightPixels) {
+ heightPixels = defaultHeight;
+ }
+
+ } else {
+ // the screen size is same as expected size. make it expandable
+ compatibilityInfo.setExpandable(true);
+ }
+ }
+ compatibilityInfo.setVisibleRect(xOffset, widthPixels, heightPixels);
+ if (compatibilityInfo.isScalingRequired()) {
+ float invertedRatio = compatibilityInfo.applicationInvertedScale;
+ density *= invertedRatio;
+ scaledDensity *= invertedRatio;
+ xdpi *= invertedRatio;
+ ydpi *= invertedRatio;
+ widthPixels *= invertedRatio;
+ heightPixels *= invertedRatio;
+ }
}
+ @Override
public String toString() {
return "DisplayMetrics{density=" + density + ", width=" + widthPixels +
", height=" + heightPixels + ", scaledDensity=" + scaledDensity +
", xdpi=" + xdpi + ", ydpi=" + ydpi + "}";
}
+
+ private static int getDeviceDensity() {
+ // qemu.sf.lcd_density can be used to override ro.sf.lcd_density
+ // when running in the emulator, allowing for dynamic configurations.
+ // The reason for this is that ro.sf.lcd_density is write-once and is
+ // set by the init process when it parses build.prop before anything else.
+ return SystemProperties.getInt("qemu.sf.lcd_density",
+ SystemProperties.getInt("ro.sf.lcd_density", DEFAULT_DENSITY));
+ }
}
diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java
new file mode 100644
index 0000000..d90045f
--- /dev/null
+++ b/core/java/android/util/LongSparseArray.java
@@ -0,0 +1,342 @@
+/*
+ * 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.util;
+
+import com.android.internal.util.ArrayUtils;
+
+/**
+ * SparseArrays map longs to Objects. Unlike a normal array of Objects,
+ * there can be gaps in the indices. It is intended to be more efficient
+ * than using a HashMap to map Longs to Objects.
+ *
+ * @hide
+ */
+public class LongSparseArray<E> {
+ private static final Object DELETED = new Object();
+ private boolean mGarbage = false;
+
+ /**
+ * Creates a new SparseArray containing no mappings.
+ */
+ public LongSparseArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings.
+ */
+ public LongSparseArray(int initialCapacity) {
+ initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity);
+
+ mKeys = new long[initialCapacity];
+ mValues = new Object[initialCapacity];
+ mSize = 0;
+ }
+
+ /**
+ * Gets the Object mapped from the specified key, or <code>null</code>
+ * if no such mapping has been made.
+ */
+ public E get(long key) {
+ return get(key, null);
+ }
+
+ /**
+ * Gets the Object mapped from the specified key, or the specified Object
+ * if no such mapping has been made.
+ */
+ public E get(long key, E valueIfKeyNotFound) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i < 0 || mValues[i] == DELETED) {
+ return valueIfKeyNotFound;
+ } else {
+ return (E) mValues[i];
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public void delete(long key) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ if (mValues[i] != DELETED) {
+ mValues[i] = DELETED;
+ mGarbage = true;
+ }
+ }
+ }
+
+ /**
+ * Alias for {@link #delete(long)}.
+ */
+ public void remove(long key) {
+ delete(key);
+ }
+
+ private void gc() {
+ // Log.e("SparseArray", "gc start with " + mSize);
+
+ int n = mSize;
+ int o = 0;
+ long[] keys = mKeys;
+ Object[] values = mValues;
+
+ for (int i = 0; i < n; i++) {
+ Object val = values[i];
+
+ if (val != DELETED) {
+ if (i != o) {
+ keys[o] = keys[i];
+ values[o] = val;
+ }
+
+ o++;
+ }
+ }
+
+ mGarbage = false;
+ mSize = o;
+
+ // Log.e("SparseArray", "gc end with " + mSize);
+ }
+
+ /**
+ * Adds a mapping from the specified key to the specified value,
+ * replacing the previous mapping from the specified key if there
+ * was one.
+ */
+ public void put(long key, E value) {
+ int i = binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ mValues[i] = value;
+ } else {
+ i = ~i;
+
+ if (i < mSize && mValues[i] == DELETED) {
+ mKeys[i] = key;
+ mValues[i] = value;
+ return;
+ }
+
+ if (mGarbage && mSize >= mKeys.length) {
+ gc();
+
+ // Search again because indices may have changed.
+ i = ~binarySearch(mKeys, 0, mSize, key);
+ }
+
+ if (mSize >= mKeys.length) {
+ int n = ArrayUtils.idealIntArraySize(mSize + 1);
+
+ long[] nkeys = new long[n];
+ Object[] nvalues = new Object[n];
+
+ // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+
+ if (mSize - i != 0) {
+ // Log.e("SparseArray", "move " + (mSize - i));
+ System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
+ System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
+ }
+
+ mKeys[i] = key;
+ mValues[i] = value;
+ mSize++;
+ }
+ }
+
+ /**
+ * Returns the number of key-value mappings that this SparseArray
+ * currently stores.
+ */
+ public int size() {
+ if (mGarbage) {
+ gc();
+ }
+
+ return mSize;
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the key from the <code>index</code>th key-value mapping that this
+ * SparseArray stores.
+ */
+ public long keyAt(int index) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return mKeys[index];
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, returns
+ * the value from the <code>index</code>th key-value mapping that this
+ * SparseArray stores.
+ */
+ public E valueAt(int index) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return (E) mValues[index];
+ }
+
+ /**
+ * Given an index in the range <code>0...size()-1</code>, sets a new
+ * value for the <code>index</code>th key-value mapping that this
+ * SparseArray stores.
+ */
+ public void setValueAt(int index, E value) {
+ if (mGarbage) {
+ gc();
+ }
+
+ mValues[index] = value;
+ }
+
+ /**
+ * Returns the index for which {@link #keyAt} would return the
+ * specified key, or a negative number if the specified
+ * key is not mapped.
+ */
+ public int indexOfKey(long key) {
+ if (mGarbage) {
+ gc();
+ }
+
+ return binarySearch(mKeys, 0, mSize, key);
+ }
+
+ /**
+ * Returns an index for which {@link #valueAt} would return the
+ * specified key, or a negative number if no keys map to the
+ * specified value.
+ * Beware that this is a linear search, unlike lookups by key,
+ * and that multiple keys can map to the same value and this will
+ * find only one of them.
+ */
+ public int indexOfValue(E value) {
+ if (mGarbage) {
+ gc();
+ }
+
+ for (int i = 0; i < mSize; i++)
+ if (mValues[i] == value)
+ return i;
+
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseArray.
+ */
+ public void clear() {
+ int n = mSize;
+ Object[] values = mValues;
+
+ for (int i = 0; i < n; i++) {
+ values[i] = null;
+ }
+
+ mSize = 0;
+ mGarbage = false;
+ }
+
+ /**
+ * Puts a key/value pair into the array, optimizing for the case where
+ * the key is greater than all existing keys in the array.
+ */
+ public void append(long key, E value) {
+ if (mSize != 0 && key <= mKeys[mSize - 1]) {
+ put(key, value);
+ return;
+ }
+
+ if (mGarbage && mSize >= mKeys.length) {
+ gc();
+ }
+
+ int pos = mSize;
+ if (pos >= mKeys.length) {
+ int n = ArrayUtils.idealIntArraySize(pos + 1);
+
+ long[] nkeys = new long[n];
+ Object[] nvalues = new Object[n];
+
+ // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+
+ mKeys[pos] = key;
+ mValues[pos] = value;
+ mSize = pos + 1;
+ }
+
+ private static int binarySearch(long[] a, int start, int len, long key) {
+ int high = start + len, low = start - 1, guess;
+
+ while (high - low > 1) {
+ guess = (high + low) / 2;
+
+ if (a[guess] < key)
+ low = guess;
+ else
+ high = guess;
+ }
+
+ if (high == start + len)
+ return ~(start + len);
+ else if (a[high] == key)
+ return high;
+ else
+ return ~high;
+ }
+
+ private void checkIntegrity() {
+ for (int i = 1; i < mSize; i++) {
+ if (mKeys[i] <= mKeys[i - 1]) {
+ for (int j = 0; j < mSize; j++) {
+ Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]);
+ }
+
+ throw new RuntimeException();
+ }
+ }
+ }
+
+ private long[] mKeys;
+ private Object[] mValues;
+ private int mSize;
+} \ No newline at end of file
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 23f3e3c..1e558be 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -198,6 +198,7 @@ public class GestureDetector {
private int mTouchSlopSquare;
private int mDoubleTapSlopSquare;
private int mMinimumFlingVelocity;
+ private int mMaximumFlingVelocity;
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
@@ -361,11 +362,13 @@ public class GestureDetector {
doubleTapSlop = ViewConfiguration.getDoubleTapSlop();
//noinspection deprecation
mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity();
+ mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity();
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapSlop = configuration.getScaledDoubleTapSlop();
mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity();
}
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
@@ -505,7 +508,7 @@ public class GestureDetector {
// A fling must travel the minimum tap distance
final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000);
+ velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
final float velocityY = velocityTracker.getYVelocity();
final float velocityX = velocityTracker.getXVelocity();
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 86261c4..a224ed3 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -59,32 +59,32 @@ public final class MotionEvent implements Parcelable {
public static final int ACTION_OUTSIDE = 4;
private static final boolean TRACK_RECYCLED_LOCATION = false;
-
+
/**
* Flag indicating the motion event intersected the top edge of the screen.
*/
public static final int EDGE_TOP = 0x00000001;
-
+
/**
* Flag indicating the motion event intersected the bottom edge of the screen.
*/
public static final int EDGE_BOTTOM = 0x00000002;
-
+
/**
* Flag indicating the motion event intersected the left edge of the screen.
*/
public static final int EDGE_LEFT = 0x00000004;
-
+
/**
* Flag indicating the motion event intersected the right edge of the screen.
*/
public static final int EDGE_RIGHT = 0x00000008;
-
+
static private final int MAX_RECYCLED = 10;
static private Object gRecyclerLock = new Object();
static private int gRecyclerUsed = 0;
static private MotionEvent gRecyclerTop = null;
-
+
private long mDownTime;
private long mEventTime;
private int mAction;
@@ -109,7 +109,7 @@ public final class MotionEvent implements Parcelable {
private MotionEvent() {
}
-
+
static private MotionEvent obtain() {
synchronized (gRecyclerLock) {
if (gRecyclerTop == null) {
@@ -123,26 +123,26 @@ public final class MotionEvent implements Parcelable {
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
+ *
+ * @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 action The kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
* {@link #ACTION_CANCEL}.
* @param x The X coordinate of this event.
* @param y The Y coordinate of this event.
- * @param pressure The current pressure of this event. 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
+ * @param pressure The current pressure of this event. 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.
* @param size A scaled value of the approximate size of the area being pressed when
- * touched with the finger. The actual value in pixels corresponding to the finger
+ * touched with the finger. The actual value in pixels corresponding to the finger
* touch is normalized with a device specific range of values
* and scaled to a value between 0 and 1.
* @param metaState The state of any meta / modifier keys that were in effect when
@@ -174,15 +174,15 @@ public final class MotionEvent implements Parcelable {
return ev;
}
-
+
/**
* Create a new MotionEvent, filling in a subset of the basic motion
* values. Those not specified here are: device id (always 0), pressure
* and size (always 1), x and y precision (always 1), and edgeFlags (always 0).
- *
- * @param downTime The time (in ms) when the user originally pressed down to start
+ *
+ * @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 action The kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
@@ -212,27 +212,47 @@ public final class MotionEvent implements Parcelable {
}
/**
- * Scales down the cood of this event by the given scale.
+ * Scales down the coordination of this event by the given scale.
*
* @hide
*/
public void scale(float scale) {
- if (scale != 1.0f) {
- mX *= scale;
- mY *= scale;
- mRawX *= scale;
- mRawY *= scale;
- mSize *= scale;
- mXPrecision *= scale;
- mYPrecision *= scale;
- if (mHistory != null) {
- float[] history = mHistory;
- int length = history.length;
- for (int i = 0; i < length; i += 4) {
- history[i] *= scale;
- history[i + 2] *= scale;
- history[i + 3] *= scale;
- }
+ mX *= scale;
+ mY *= scale;
+ mRawX *= scale;
+ mRawY *= scale;
+ mSize *= scale;
+ mXPrecision *= scale;
+ mYPrecision *= scale;
+ if (mHistory != null) {
+ float[] history = mHistory;
+ int length = history.length;
+ for (int i = 0; i < length; i += 4) {
+ history[i] *= scale; // X
+ history[i + 1] *= scale; // Y
+ // no need to scale pressure ([i+2])
+ history[i + 3] *= scale; // Size, TODO: square this?
+ }
+ }
+ }
+
+ /**
+ * Translate the coordination of the event by given x and y.
+ *
+ * @hide
+ */
+ public void translate(float dx, float dy) {
+ mX += dx;
+ mY += dy;
+ mRawX += dx;
+ mRawY += dx;
+ if (mHistory != null) {
+ float[] history = mHistory;
+ int length = history.length;
+ for (int i = 0; i < length; i += 4) {
+ history[i] += dx; // X
+ history[i + 1] += dy; // Y
+ // no need to translate pressure (i+2) and size (i+3)
}
}
}
@@ -265,7 +285,7 @@ public final class MotionEvent implements Parcelable {
}
return ev;
}
-
+
/**
* Recycle the MotionEvent, to be re-used by a later caller. After calling
* this function you must not ever touch the event again.
@@ -291,7 +311,7 @@ public final class MotionEvent implements Parcelable {
}
}
}
-
+
/**
* Return the kind of action being performed -- one of either
* {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
@@ -302,8 +322,8 @@ public final class MotionEvent implements Parcelable {
}
/**
- * Returns the time (in ms) when the user originally pressed down to start
- * a stream of position events.
+ * Returns the time (in ms) when the user originally pressed down to start
+ * a stream of position events.
*/
public final long getDownTime() {
return mDownTime;
@@ -317,25 +337,25 @@ public final class MotionEvent implements Parcelable {
}
/**
- * Returns the X coordinate of this event. Whole numbers are pixels; the
- * value may have a fraction for input devices that are sub-pixel precise.
+ * Returns the X coordinate of this event. Whole numbers are pixels; the
+ * value may have a fraction for input devices that are sub-pixel precise.
*/
public final float getX() {
return mX;
}
/**
- * Returns the Y coordinate of this event. Whole numbers are pixels; the
- * value may have a fraction for input devices that are sub-pixel precise.
+ * Returns the Y coordinate of this event. Whole numbers are pixels; the
+ * value may have a fraction for input devices that are sub-pixel precise.
*/
public final float getY() {
return mY;
}
/**
- * Returns the current pressure of this event. 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
+ * Returns the current pressure of this event. 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 final float getPressure() {
@@ -344,9 +364,9 @@ public final class MotionEvent implements Parcelable {
/**
* Returns a scaled value of the approximate size, of the area being pressed when
- * touched with the finger. The actual value in pixels corresponding to the finger
+ * touched with the finger. The actual value in pixels corresponding to the finger
* 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
+ * and scaled to a value between 0 and 1. The value of size can be used to
* determine fat touch events.
*/
public final float getSize() {
@@ -396,7 +416,7 @@ public final class MotionEvent implements Parcelable {
public final float getXPrecision() {
return mXPrecision;
}
-
+
/**
* Return the precision of the Y coordinates being reported. You can
* multiple this number with {@link #getY} to find the actual hardware
@@ -406,89 +426,89 @@ public final class MotionEvent implements Parcelable {
public final float getYPrecision() {
return mYPrecision;
}
-
+
/**
* Returns the number of historical points in this event. These are
* movements that have occurred between this event and the previous event.
* This only applies to ACTION_MOVE events -- all other actions will have
* a size of 0.
- *
+ *
* @return Returns the number of historical points in the event.
*/
public final int getHistorySize() {
return mNumHistory;
}
-
+
/**
* Returns the time that a historical movement occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
- *
+ *
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
- *
+ *
* @see #getHistorySize
* @see #getEventTime
*/
public final long getHistoricalEventTime(int pos) {
return mHistoryTimes[pos];
}
-
+
/**
* Returns a historical X coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
- *
+ *
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
- *
+ *
* @see #getHistorySize
* @see #getX
*/
public final float getHistoricalX(int pos) {
return mHistory[pos*4];
}
-
+
/**
* Returns a historical Y coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
- *
+ *
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
- *
+ *
* @see #getHistorySize
* @see #getY
*/
public final float getHistoricalY(int pos) {
return mHistory[pos*4 + 1];
}
-
+
/**
* Returns a historical pressure coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
- *
+ *
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
- *
+ *
* @see #getHistorySize
* @see #getPressure
*/
public final float getHistoricalPressure(int pos) {
return mHistory[pos*4 + 2];
}
-
+
/**
* Returns a historical size coordinate that occurred between this event
* and the previous event. Only applies to ACTION_MOVE events.
- *
+ *
* @param pos Which historical value to return; must be less than
* {@link #getHistorySize}
- *
+ *
* @see #getHistorySize
* @see #getSize
*/
public final float getHistoricalSize(int pos) {
return mHistory[pos*4 + 3];
}
-
+
/**
* 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
@@ -497,12 +517,12 @@ public final class MotionEvent implements Parcelable {
public final int getDeviceId() {
return mDeviceId;
}
-
+
/**
* Returns a bitfield indicating which edges, if any, where 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.
- *
+ * MotionEvent. For touch events, clients can use this to determine if the
+ * user's finger was touching the edge of the display.
+ *
* @see #EDGE_LEFT
* @see #EDGE_TOP
* @see #EDGE_RIGHT
@@ -511,12 +531,12 @@ public final class MotionEvent implements Parcelable {
public final int getEdgeFlags() {
return mEdgeFlags;
}
-
+
/**
* Sets the bitfield indicating which edges, if any, where touched by this
- * MotionEvent.
- *
+ * MotionEvent.
+ *
* @see #getEdgeFlags()
*/
public final void setEdgeFlags(int flags) {
@@ -548,11 +568,11 @@ public final class MotionEvent implements Parcelable {
pos[i+1] += deltaY;
}
}
-
+
/**
* Set this event's location. Applies {@link #offsetLocation} with a
* delta from the current location to the given new location.
- *
+ *
* @param x New absolute X location.
* @param y New absolute Y location.
*/
@@ -563,13 +583,13 @@ public final class MotionEvent implements Parcelable {
offsetLocation(deltaX, deltaY);
}
}
-
+
/**
* 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.
- *
+ *
* @param x The new X position.
* @param y The new Y position.
* @param pressure The new pressure.
@@ -599,16 +619,16 @@ public final class MotionEvent implements Parcelable {
mHistoryTimes = historyTimes = newHistoryTimes;
}
}
-
+
historyTimes[N] = mEventTime;
-
+
final int pos = N*4;
history[pos] = mX;
history[pos+1] = mY;
history[pos+2] = mPressure;
history[pos+3] = mSize;
mNumHistory = N+1;
-
+
mEventTime = eventTime;
mX = mRawX = x;
mY = mRawY = y;
@@ -616,7 +636,7 @@ public final class MotionEvent implements Parcelable {
mSize = size;
mMetaState |= metaState;
}
-
+
@Override
public String toString() {
return "MotionEvent{" + Integer.toHexString(System.identityHashCode(this))
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 3d023f7..45b0f0a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -17,6 +17,8 @@
package android.view;
import android.content.Context;
+import android.content.res.CompatibilityInfo;
+import android.content.res.CompatibilityInfo.Translator;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
@@ -100,6 +102,8 @@ public class SurfaceView extends View {
static final int KEEP_SCREEN_ON_MSG = 1;
static final int GET_NEW_SURFACE_MSG = 2;
+ int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+
boolean mIsCreating = false;
final Handler mHandler = new Handler() {
@@ -135,28 +139,21 @@ public class SurfaceView extends View {
int mFormat = -1;
int mType = -1;
final Rect mSurfaceFrame = new Rect();
- private final float mAppScale;
- private final float mAppScaleInverted;
+ private Translator mTranslator;
public SurfaceView(Context context) {
super(context);
setWillNotDraw(true);
- mAppScale = context.getApplicationScale();
- mAppScaleInverted = 1.0f / mAppScale;
}
public SurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(true);
- mAppScale = context.getApplicationScale();
- mAppScaleInverted = 1.0f / mAppScale;
}
public SurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setWillNotDraw(true);
- mAppScale = context.getApplicationScale();
- mAppScaleInverted = 1.0f / mAppScale;
}
/**
@@ -259,9 +256,9 @@ public class SurfaceView extends View {
public boolean dispatchTouchEvent(MotionEvent event) {
// SurfaceView uses pre-scaled size unless fixed size is requested. This hook
// scales the event back to the pre-scaled coordinates for such surface.
- if (mRequestedWidth < 0 && mAppScale != 1.0f) {
+ if (mRequestedWidth < 0 && mTranslator != null) {
MotionEvent scaledBack = MotionEvent.obtain(event);
- scaledBack.scale(mAppScale);
+ scaledBack.scale(mTranslator.applicationScale);
try {
return super.dispatchTouchEvent(scaledBack);
} finally {
@@ -285,20 +282,33 @@ public class SurfaceView extends View {
super.dispatchDraw(canvas);
}
+ /**
+ * Hack to allow special layering of windows. The type is one of the
+ * types in WindowManager.LayoutParams. This is a hack so:
+ * @hide
+ */
+ public void setWindowType(int type) {
+ mWindowType = type;
+ }
+
private void updateWindow(boolean force) {
if (!mHaveFrame) {
return;
}
+ mTranslator = ((ViewRoot)getRootView().getParent()).mTranslator;
+
+ float appScale = mTranslator == null ? 1.0f : mTranslator.applicationScale;
int myWidth = mRequestedWidth;
if (myWidth <= 0) myWidth = getWidth();
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
- // Use original size for surface unless fixed size is requested.
- if (mRequestedWidth <= 0) {
- myWidth *= mAppScale;
- myHeight *= mAppScale;
+ // Use original size if the app specified the size of the view,
+ // and let the flinger to scale up.
+ if (mRequestedWidth <= 0 && mTranslator != null && mTranslator.scalingRequired) {
+ myWidth *= appScale;
+ myHeight *= appScale;
}
getLocationInWindow(mLocation);
@@ -316,7 +326,7 @@ public class SurfaceView extends View {
+ " visible=" + visibleChanged
+ " left=" + (mLeft != mLocation[0])
+ " top=" + (mTop != mLocation[1]));
-
+
try {
final boolean visible = mVisible = mRequestedVisible;
mLeft = mLocation[0];
@@ -326,23 +336,30 @@ public class SurfaceView extends View {
mFormat = mRequestedFormat;
mType = mRequestedType;
- // Scaling window's layout here beause mLayout is not used elsewhere.
- mLayout.x = (int) (mLeft * mAppScale);
- mLayout.y = (int) (mTop * mAppScale);
- mLayout.width = (int) (getWidth() * mAppScale);
- mLayout.height = (int) (getHeight() * mAppScale);
+ // Scaling/Translate window's layout here because mLayout is not used elsewhere.
+
+ // Places the window relative
+ mLayout.x = mLeft;
+ mLayout.y = mTop;
+ mLayout.width = getWidth();
+ mLayout.height = getHeight();
+ if (mTranslator != null) {
+ mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
+ }
+
mLayout.format = mRequestedFormat;
mLayout.flags |=WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_SCALED
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING
;
mLayout.memoryType = mRequestedType;
if (mWindow == null) {
mWindow = new MyWindow(this);
- mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+ mLayout.type = mWindowType;
mLayout.gravity = Gravity.LEFT|Gravity.TOP;
mSession.add(mWindow, mLayout,
mVisible ? VISIBLE : GONE, mContentInsets);
@@ -356,15 +373,12 @@ public class SurfaceView extends View {
mSurfaceLock.lock();
mDrawingStopped = !visible;
+
final int relayoutResult = mSession.relayout(
mWindow, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
mVisibleInsets, mSurface);
- mContentInsets.scale(mAppScaleInverted);
- mVisibleInsets.scale(mAppScaleInverted);
- mWinFrame.scale(mAppScaleInverted);
-
if (localLOGV) Log.i(TAG, "New surface: " + mSurface
+ ", vis=" + visible + ", frame=" + mWinFrame);
mSurfaceFrame.left = 0;
@@ -433,24 +447,14 @@ public class SurfaceView extends View {
private static class MyWindow extends IWindow.Stub {
private final WeakReference<SurfaceView> mSurfaceView;
- private final float mAppScale;
- private final float mAppScaleInverted;
public MyWindow(SurfaceView surfaceView) {
mSurfaceView = new WeakReference<SurfaceView>(surfaceView);
- mAppScale = surfaceView.getContext().getApplicationScale();
- mAppScaleInverted = 1.0f / mAppScale;
}
public void resized(int w, int h, Rect coveredInsets,
Rect visibleInsets, boolean reportDraw) {
SurfaceView surfaceView = mSurfaceView.get();
- float scale = mAppScaleInverted;
- w *= scale;
- h *= scale;
- coveredInsets.scale(scale);
- visibleInsets.scale(scale);
-
if (surfaceView != null) {
if (localLOGV) Log.v(
"SurfaceView", surfaceView + " got resized: w=" +
@@ -613,7 +617,6 @@ public class SurfaceView extends View {
Canvas c = null;
if (!mDrawingStopped && mWindow != null) {
Rect frame = dirty != null ? dirty : mSurfaceFrame;
- frame.scale(mAppScale);
try {
c = mSurface.lockCanvas(frame);
} catch (Exception e) {
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index c708f54..5d89c46 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -165,7 +165,17 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
pastTime[i] = 0;
}
}
-
+
+ /**
+ * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
+ * velocity of Float.MAX_VALUE.
+ *
+ * @see #computeCurrentVelocity(int, float)
+ */
+ public void computeCurrentVelocity(int units) {
+ computeCurrentVelocity(units, Float.MAX_VALUE);
+ }
+
/**
* Compute the current velocity based on the points that have been
* collected. Only call this when you actually want to retrieve velocity
@@ -175,8 +185,11 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
*
* @param units The units you would like the velocity in. A value of 1
* provides pixels per millisecond, 1000 provides pixels per second, etc.
+ * @param maxVelocity The maximum velocity that can be computed by this method.
+ * This value must be declared in the same unit as the units parameter. This value
+ * must be positive.
*/
- public void computeCurrentVelocity(int units) {
+ public void computeCurrentVelocity(int units, float maxVelocity) {
final float[] pastX = mPastX;
final float[] pastY = mPastY;
final long[] pastTime = mPastTime;
@@ -210,8 +223,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
if (accumY == 0) accumY = vel;
else accumY = (accumY + vel) * .5f;
}
- mXVelocity = accumX;
- mYVelocity = accumY;
+ mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
+ mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
+ mXVelocity + " N=" + N);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9e709cf..ff8868b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,9 @@
package android.view;
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -25,12 +28,12 @@ import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
+import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Shader;
-import android.graphics.Point;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -42,47 +45,47 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.AttributeSet;
+import android.util.Config;
import android.util.EventLog;
import android.util.Log;
-import android.util.SparseArray;
-import android.util.Poolable;
import android.util.Pool;
-import android.util.Pools;
+import android.util.Poolable;
import android.util.PoolableManager;
-import android.util.Config;
+import android.util.Pools;
+import android.util.SparseArray;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityEventSource;
+import android.view.accessibility.AccessibilityManager;
import android.view.animation.Animation;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.EditorInfo;
import android.widget.ScrollBarDrawable;
-import com.android.internal.R;
-import com.android.internal.view.menu.MenuBuilder;
-
+import java.lang.ref.SoftReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.WeakHashMap;
-import java.lang.ref.SoftReference;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
/**
* <p>
* This class represents the basic building block for user interface components. A View
* occupies a rectangular area on the screen and is responsible for drawing and
* event handling. View is the base class for <em>widgets</em>, which are
- * used to create interactive UI components (buttons, text fields, etc.). The
+ * used to create interactive UI components (buttons, text fields, etc.). The
* {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which
* are invisible containers that hold other Views (or other ViewGroups) and define
* their layout properties.
* </p>
*
* <div class="special">
- * <p>For an introduction to using this class to develop your
- * application's user interface, read the Developer Guide documentation on
+ * <p>For an introduction to using this class to develop your
+ * application's user interface, read the Developer Guide documentation on
* <strong><a href="{@docRoot}guide/topics/ui/index.html">User Interface</a></strong>. Special topics
- * include:
+ * include:
* <br/><a href="{@docRoot}guide/topics/ui/declaring-layout.html">Declaring Layout</a>
* <br/><a href="{@docRoot}guide/topics/ui/menus.html">Creating Menus</a>
* <br/><a href="{@docRoot}guide/topics/ui/layout-objects.html">Common Layout Objects</a>
@@ -93,7 +96,7 @@ import java.lang.reflect.InvocationTargetException;
* <br/><a href="{@docRoot}guide/topics/ui/how-android-draws.html">How Android Draws Views</a>.
* </p>
* </div>
- *
+ *
* <a name="Using"></a>
* <h3>Using Views</h3>
* <p>
@@ -419,7 +422,7 @@ import java.lang.reflect.InvocationTargetException;
* </p>
*
* <p>
- * Note that the framework will not draw views that are not in the invalid region.
+ * Note that the framework will not draw views that are not in the invalid region.
* </p>
*
* <p>
@@ -535,25 +538,52 @@ import java.lang.reflect.InvocationTargetException;
* take care of redrawing the appropriate views until the animation completes.
* </p>
*
+ * @attr ref android.R.styleable#View_background
+ * @attr ref android.R.styleable#View_clickable
+ * @attr ref android.R.styleable#View_contentDescription
+ * @attr ref android.R.styleable#View_drawingCacheQuality
+ * @attr ref android.R.styleable#View_duplicateParentState
+ * @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_fitsSystemWindows
+ * @attr ref android.R.styleable#View_isScrollContainer
+ * @attr ref android.R.styleable#View_focusable
+ * @attr ref android.R.styleable#View_focusableInTouchMode
+ * @attr ref android.R.styleable#View_hapticFeedbackEnabled
+ * @attr ref android.R.styleable#View_keepScreenOn
+ * @attr ref android.R.styleable#View_longClickable
+ * @attr ref android.R.styleable#View_minHeight
+ * @attr ref android.R.styleable#View_minWidth
* @attr ref android.R.styleable#View_nextFocusDown
* @attr ref android.R.styleable#View_nextFocusLeft
* @attr ref android.R.styleable#View_nextFocusRight
* @attr ref android.R.styleable#View_nextFocusUp
+ * @attr ref android.R.styleable#View_onClick
+ * @attr ref android.R.styleable#View_padding
+ * @attr ref android.R.styleable#View_paddingBottom
+ * @attr ref android.R.styleable#View_paddingLeft
+ * @attr ref android.R.styleable#View_paddingRight
+ * @attr ref android.R.styleable#View_paddingTop
+ * @attr ref android.R.styleable#View_saveEnabled
* @attr ref android.R.styleable#View_scrollX
* @attr ref android.R.styleable#View_scrollY
- * @attr ref android.R.styleable#View_scrollbarTrackHorizontal
- * @attr ref android.R.styleable#View_scrollbarThumbHorizontal
* @attr ref android.R.styleable#View_scrollbarSize
+ * @attr ref android.R.styleable#View_scrollbarStyle
* @attr ref android.R.styleable#View_scrollbars
+ * @attr ref android.R.styleable#View_scrollbarTrackHorizontal
+ * @attr ref android.R.styleable#View_scrollbarThumbHorizontal
* @attr ref android.R.styleable#View_scrollbarThumbVertical
* @attr ref android.R.styleable#View_scrollbarTrackVertical
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
* @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
+ * @attr ref android.R.styleable#View_soundEffectsEnabled
+ * @attr ref android.R.styleable#View_tag
+ * @attr ref android.R.styleable#View_visibility
*
* @see android.view.ViewGroup
*/
-public class View implements Drawable.Callback, KeyEvent.Callback {
+public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
private static final boolean DBG = false;
/**
@@ -851,6 +881,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public static final int HAPTIC_FEEDBACK_ENABLED = 0x10000000;
/**
+ * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+ * should add all focusable Views regardless if they are focusable in touch mode.
+ */
+ public static final int FOCUSABLES_ALL = 0x00000000;
+
+ /**
+ * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+ * should add only Views focusable in touch mode.
+ */
+ public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
+
+ /**
* Use with {@link #focusSearch}. Move focus to the previous selectable
* item.
*/
@@ -1428,6 +1470,27 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
static final int DIRTY_MASK = 0x00600000;
/**
+ * Indicates whether the background is opaque.
+ *
+ * @hide
+ */
+ static final int OPAQUE_BACKGROUND = 0x00800000;
+
+ /**
+ * Indicates whether the scrollbars are opaque.
+ *
+ * @hide
+ */
+ static final int OPAQUE_SCROLLBARS = 0x01000000;
+
+ /**
+ * Indicates whether the view is opaque.
+ *
+ * @hide
+ */
+ static final int OPAQUE_MASK = 0x01800000;
+
+ /**
* The parent this view is attached to.
* {@hide}
*
@@ -1449,7 +1512,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
@ViewDebug.FlagToString(mask = LAYOUT_REQUIRED, equals = LAYOUT_REQUIRED,
name = "LAYOUT_REQUIRED"),
@ViewDebug.FlagToString(mask = DRAWING_CACHE_VALID, equals = DRAWING_CACHE_VALID,
- name = "DRAWING_CACHE_VALID", outputIf = false),
+ name = "DRAWING_CACHE_INVALID", outputIf = false),
@ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "DRAWN", outputIf = true),
@ViewDebug.FlagToString(mask = DRAWN, equals = DRAWN, name = "NOT_DRAWN", outputIf = false),
@ViewDebug.FlagToString(mask = DIRTY_MASK, equals = DIRTY_OPAQUE, name = "DIRTY_OPAQUE"),
@@ -1551,6 +1614,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
protected int mPaddingBottom;
/**
+ * Briefly describes the view and is primarily used for accessibility support.
+ */
+ private CharSequence mContentDescription;
+
+ /**
* Cache the paddingRight set by the user to append to the scrollbar's size.
*/
@ViewDebug.ExportedProperty
@@ -1622,6 +1690,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
private int[] mDrawableState = null;
private SoftReference<Bitmap> mDrawingCache;
+ private SoftReference<Bitmap> mUnscaledDrawingCache;
/**
* When this view has focus and the next focus is {@link #FOCUS_LEFT},
@@ -1701,7 +1770,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public View(Context context) {
mContext = context;
mResources = context != null ? context.getResources() : null;
- mViewFlags = SOUND_EFFECTS_ENABLED|HAPTIC_FEEDBACK_ENABLED;
+ mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
++sInstanceCount;
}
@@ -1762,7 +1831,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
int viewFlagMasks = 0;
boolean setScrollContainer = false;
-
+
int x = 0;
int y = 0;
@@ -1858,16 +1927,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
viewFlagMasks |= DRAWING_CACHE_QUALITY_MASK;
}
break;
+ case com.android.internal.R.styleable.View_contentDescription:
+ mContentDescription = a.getString(attr);
+ break;
case com.android.internal.R.styleable.View_soundEffectsEnabled:
if (!a.getBoolean(attr, true)) {
viewFlagValues &= ~SOUND_EFFECTS_ENABLED;
viewFlagMasks |= SOUND_EFFECTS_ENABLED;
}
+ break;
case com.android.internal.R.styleable.View_hapticFeedbackEnabled:
if (!a.getBoolean(attr, true)) {
viewFlagValues &= ~HAPTIC_FEEDBACK_ENABLED;
viewFlagMasks |= HAPTIC_FEEDBACK_ENABLED;
}
+ break;
case R.styleable.View_scrollbars:
final int scrollbars = a.getInt(attr, SCROLLBARS_NONE);
if (scrollbars != SCROLLBARS_NONE) {
@@ -1922,6 +1996,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mMinHeight = a.getDimensionPixelSize(attr, 0);
break;
case R.styleable.View_onClick:
+ if (context.isRestricted()) {
+ throw new IllegalStateException("The android:onClick attribute cannot "
+ + "be used within a restricted context");
+ }
+
final String handlerName = a.getString(attr);
if (handlerName != null) {
setOnClickListener(new OnClickListener() {
@@ -1990,7 +2069,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
setScrollContainer(true);
}
-
+
+ computeOpaqueFlags();
+
a.recycle();
}
@@ -2255,6 +2336,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* otherwise is returned.
*/
public boolean performClick() {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
+
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
@@ -2272,6 +2355,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* otherwise is returned.
*/
public boolean performLongClick() {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+
boolean handled = false;
if (mOnLongClickListener != null) {
handled = mOnLongClickListener.onLongClick(View.this);
@@ -2387,7 +2472,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (!(parent instanceof View)) {
break;
}
-
+
child = (View) parent;
parent = child.getParent();
}
@@ -2479,7 +2564,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* and previouslyFocusedRect provide insight into where the focus is coming from.
* When overriding, be sure to call up through to the super class so that
* the standard focus handling will occur.
- *
+ *
* @param gainFocus True if the View has focus; false otherwise.
* @param direction The direction focus has moved when requestFocus()
* is called to give this view focus. Values are
@@ -2492,6 +2577,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* from (in addition to direction). Will be <code>null</code> otherwise.
*/
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
+ if (gainFocus) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+
InputMethodManager imm = InputMethodManager.peekInstance();
if (!gainFocus) {
if (isPressed()) {
@@ -2506,7 +2595,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
&& mAttachInfo.mHasWindowFocus) {
imm.focusIn(this);
}
-
+
invalidate();
if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(this, gainFocus);
@@ -2514,6 +2603,79 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
+ * {@inheritDoc}
+ */
+ public void sendAccessibilityEvent(int eventType) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+ event.setClassName(getClass().getName());
+ event.setPackageName(getContext().getPackageName());
+ event.setEnabled(isEnabled());
+ event.setContentDescription(mContentDescription);
+
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
+ ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList;
+ getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL);
+ event.setItemCount(focusablesTempList.size());
+ event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+ focusablesTempList.clear();
+ }
+
+ dispatchPopulateAccessibilityEvent(event);
+
+ AccessibilityManager.getInstance(mContext).sendAccessibilityEvent(event);
+ }
+
+ /**
+ * Dispatches an {@link AccessibilityEvent} to the {@link View} children
+ * to be populated.
+ *
+ * @param event The event.
+ *
+ * @return True if the event population was completed.
+ */
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ /**
+ * Gets the {@link View} description. It briefly describes the view and is
+ * primarily used for accessibility support. Set this property to enable
+ * better accessibility support for your application. This is especially
+ * true for views that do not have textual representation (For example,
+ * ImageButton).
+ *
+ * @return The content descriptiopn.
+ *
+ * @attr ref android.R.styleable#View_contentDescription
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the {@link View} description. It briefly describes the view and is
+ * primarily used for accessibility support. Set this property to enable
+ * better accessibility support for your application. This is especially
+ * true for views that do not have textual representation (For example,
+ * ImageButton).
+ *
+ * @param contentDescription The content description.
+ *
+ * @attr ref android.R.styleable#View_contentDescription
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ }
+
+ /**
* Invoked whenever this view loses focus, either by losing window focus or by losing
* focus within its window. This method can be used to clear any state tied to the
* focus. For instance, if a button is held pressed with the trackball and the window
@@ -2522,7 +2684,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* Subclasses of View overriding this method should always call super.onFocusLost().
*
* @see #onFocusChanged(boolean, int, android.graphics.Rect)
- * @see #onWindowFocusChanged(boolean)
+ * @see #onWindowFocusChanged(boolean)
*
* @hide pending API council approval
*/
@@ -3222,11 +3384,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @param direction The direction of the focus
*/
public void addFocusables(ArrayList<View> views, int direction) {
- if (!isFocusable()) return;
+ addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
+ }
- if (isInTouchMode() && !isFocusableInTouchMode()) return;
+ /**
+ * Adds any focusable views that are descendants of this view (possibly
+ * including this view if it is focusable itself) to views. This method
+ * adds all focusable views regardless if we are in touch mode or
+ * only views focusable in touch mode if we are in touch mode depending on
+ * the focusable mode paramater.
+ *
+ * @param views Focusable views found so far or null if all we are interested is
+ * the number of focusables.
+ * @param direction The direction of the focus.
+ * @param focusableMode The type of focusables to be added.
+ *
+ * @see #FOCUSABLES_ALL
+ * @see #FOCUSABLES_TOUCH_MODE
+ */
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+ if (!isFocusable()) {
+ return;
+ }
- views.add(this);
+ if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
+ isInTouchMode() && !isFocusableInTouchMode()) {
+ return;
+ }
+
+ if (views != null) {
+ views.add(this);
+ }
}
/**
@@ -3398,14 +3586,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
*/
public void onStartTemporaryDetach() {
}
-
+
/**
* Called after {@link #onStartTemporaryDetach} when the container is done
* changing the view.
*/
public void onFinishTemporaryDetach() {
}
-
+
/**
* capture information of this view for later analysis: developement only
* check dynamic switch to make sure we only dump view
@@ -3790,25 +3978,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* a call on that method would return a non-null InputConnection, and
* they are really a first-class editor that the user would normally
* start typing on when the go into a window containing your view.
- *
+ *
* <p>The default implementation always returns false. This does
* <em>not</em> mean that its {@link #onCreateInputConnection(EditorInfo)}
* will not be called or the user can not otherwise perform edits on your
* view; it is just a hint to the system that this is not the primary
* purpose of this view.
- *
+ *
* @return Returns true if this view is a text editor, else false.
*/
public boolean onCheckIsTextEditor() {
return false;
}
-
+
/**
* Create a new InputConnection for an InputMethod to interact
* with the view. The default implementation returns null, since it doesn't
* support input methods. You can override this to implement such support.
* This is only needed for views that take focus and text input.
- *
+ *
* <p>When implementing this, you probably also want to implement
* {@link #onCheckIsTextEditor()} to indicate you will return a
* non-null InputConnection.
@@ -3832,7 +4020,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public boolean checkInputConnectionProxy(View view) {
return false;
}
-
+
/**
* Show the context menu for this view. It is not safe to hold on to the
* menu after returning from this method.
@@ -4563,14 +4751,42 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* whether an instance is opaque. Opaque Views are treated in a special way by
* the View hierarchy, possibly allowing it to perform optimizations during
* invalidate/draw passes.
- *
+ *
* @return True if this View is guaranteed to be fully opaque, false otherwise.
*
* @hide Pending API council approval
*/
@ViewDebug.ExportedProperty
public boolean isOpaque() {
- return mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE;
+ return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK;
+ }
+
+ private void computeOpaqueFlags() {
+ // Opaque if:
+ // - Has a background
+ // - Background is opaque
+ // - Doesn't have scrollbars or scrollbars are inside overlay
+
+ if (mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE) {
+ mPrivateFlags |= OPAQUE_BACKGROUND;
+ } else {
+ mPrivateFlags &= ~OPAQUE_BACKGROUND;
+ }
+
+ final int flags = mViewFlags;
+ if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
+ (flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY) {
+ mPrivateFlags |= OPAQUE_SCROLLBARS;
+ } else {
+ mPrivateFlags &= ~OPAQUE_SCROLLBARS;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ protected boolean hasOpaqueScrollbars() {
+ return (mPrivateFlags & OPAQUE_SCROLLBARS) == OPAQUE_SCROLLBARS;
}
/**
@@ -4897,6 +5113,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
if (isHorizontalScrollBarEnabled() != horizontalScrollBarEnabled) {
mViewFlags ^= SCROLLBARS_HORIZONTAL;
+ computeOpaqueFlags();
recomputePadding();
}
}
@@ -4926,6 +5143,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
if (isVerticalScrollBarEnabled() != verticalScrollBarEnabled) {
mViewFlags ^= SCROLLBARS_VERTICAL;
+ computeOpaqueFlags();
recomputePadding();
}
}
@@ -4954,6 +5172,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
public void setScrollBarStyle(int style) {
if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) {
mViewFlags = (mViewFlags & ~SCROLLBARS_STYLE_MASK) | (style & SCROLLBARS_STYLE_MASK);
+ computeOpaqueFlags();
recomputePadding();
}
}
@@ -5132,9 +5351,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
}
}
-
+
/**
- * Override this if the vertical scrollbar needs to be hidden in a subclass, like when
+ * Override this if the vertical scrollbar needs to be hidden in a subclass, like when
* FastScroller is visible.
* @return whether to temporarily hide the vertical scrollbar
* @hide
@@ -5570,28 +5789,52 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
+ * <p>Calling this method is equivalent to calling <code>getDrawingCache(false)</code>.</p>
+ *
+ * @return A non-scaled bitmap representing this view or null if cache is disabled.
+ *
+ * @see #getDrawingCache(boolean)
+ */
+ public Bitmap getDrawingCache() {
+ return getDrawingCache(false);
+ }
+
+ /**
* <p>Returns the bitmap in which this view drawing is cached. The returned bitmap
* is null when caching is disabled. If caching is enabled and the cache is not ready,
* this method will create it. Calling {@link #draw(android.graphics.Canvas)} will not
* draw from the cache when the cache is enabled. To benefit from the cache, you must
* request the drawing cache by calling this method and draw it on screen if the
* returned bitmap is not null.</p>
+ *
+ * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
+ * this method will create a bitmap of the same size as this view. Because this bitmap
+ * will be drawn scaled by the parent ViewGroup, the result on screen might show
+ * scaling artifacts. To avoid such artifacts, you should call this method by setting
+ * the auto scaling to true. Doing so, however, will generate a bitmap of a different
+ * size than the view. This implies that your application must be able to handle this
+ * size.</p>
+ *
+ * @param autoScale Indicates whether the generated bitmap should be scaled based on
+ * the current density of the screen when the application is in compatibility
+ * mode.
*
- * @return a bitmap representing this view or null if cache is disabled
- *
+ * @return A bitmap representing this view or null if cache is disabled.
+ *
* @see #setDrawingCacheEnabled(boolean)
* @see #isDrawingCacheEnabled()
- * @see #buildDrawingCache()
+ * @see #buildDrawingCache(boolean)
* @see #destroyDrawingCache()
*/
- public Bitmap getDrawingCache() {
+ public Bitmap getDrawingCache(boolean autoScale) {
if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {
return null;
}
if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {
- buildDrawingCache();
+ buildDrawingCache(autoScale);
}
- return mDrawingCache == null ? null : mDrawingCache.get();
+ return autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) :
+ (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get());
}
/**
@@ -5610,6 +5853,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
if (bitmap != null) bitmap.recycle();
mDrawingCache = null;
}
+ if (mUnscaledDrawingCache != null) {
+ final Bitmap bitmap = mUnscaledDrawingCache.get();
+ if (bitmap != null) bitmap.recycle();
+ mUnscaledDrawingCache = null;
+ }
}
/**
@@ -5637,18 +5885,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
/**
+ * <p>Calling this method is equivalent to calling <code>buildDrawingCache(false)</code>.</p>
+ *
+ * @see #buildDrawingCache(boolean)
+ */
+ public void buildDrawingCache() {
+ buildDrawingCache(false);
+ }
+
+ /**
* <p>Forces the drawing cache to be built if the drawing cache is invalid.</p>
*
* <p>If you call {@link #buildDrawingCache()} manually without calling
* {@link #setDrawingCacheEnabled(boolean) setDrawingCacheEnabled(true)}, you
* should cleanup the cache by calling {@link #destroyDrawingCache()} afterwards.</p>
+ *
+ * <p>Note about auto scaling in compatibility mode: When auto scaling is not enabled,
+ * this method will create a bitmap of the same size as this view. Because this bitmap
+ * will be drawn scaled by the parent ViewGroup, the result on screen might show
+ * scaling artifacts. To avoid such artifacts, you should call this method by setting
+ * the auto scaling to true. Doing so, however, will generate a bitmap of a different
+ * size than the view. This implies that your application must be able to handle this
+ * size.</p>
*
* @see #getDrawingCache()
* @see #destroyDrawingCache()
*/
- public void buildDrawingCache() {
- if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mDrawingCache == null ||
- mDrawingCache.get() == null) {
+ public void buildDrawingCache(boolean autoScale) {
+ if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || (autoScale ?
+ (mDrawingCache == null || mDrawingCache.get() == null) :
+ (mUnscaledDrawingCache == null || mUnscaledDrawingCache.get() == null))) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE);
@@ -5657,8 +5923,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
EventLog.writeEvent(60002, hashCode());
}
- final int width = mRight - mLeft;
- final int height = mBottom - mTop;
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+
+ final AttachInfo attachInfo = mAttachInfo;
+ final boolean scalingRequired = attachInfo != null && attachInfo.mScalingRequired;
+
+ if (autoScale && scalingRequired) {
+ width = (int) ((width * attachInfo.mApplicationScale) + 0.5f);
+ height = (int) ((height * attachInfo.mApplicationScale) + 0.5f);
+ }
final int drawingCacheBackgroundColor = mDrawingCacheBackgroundColor;
final boolean opaque = drawingCacheBackgroundColor != 0 ||
@@ -5672,7 +5946,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
boolean clear = true;
- Bitmap bitmap = mDrawingCache == null ? null : mDrawingCache.get();
+ Bitmap bitmap = autoScale ? (mDrawingCache == null ? null : mDrawingCache.get()) :
+ (mUnscaledDrawingCache == null ? null : mUnscaledDrawingCache.get());
if (bitmap == null || bitmap.getWidth() != width || bitmap.getHeight() != height) {
@@ -5701,12 +5976,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
try {
bitmap = Bitmap.createBitmap(width, height, quality);
- mDrawingCache = new SoftReference<Bitmap>(bitmap);
+ if (autoScale) {
+ mDrawingCache = new SoftReference<Bitmap>(bitmap);
+ } else {
+ mUnscaledDrawingCache = new SoftReference<Bitmap>(bitmap);
+ }
} catch (OutOfMemoryError e) {
// If there is not enough memory to create the bitmap cache, just
// ignore the issue as bitmap caches are not required to draw the
// view hierarchy
- mDrawingCache = null;
+ if (autoScale) {
+ mDrawingCache = null;
+ } else {
+ mUnscaledDrawingCache = null;
+ }
return;
}
@@ -5714,7 +5997,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
}
Canvas canvas;
- final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
canvas = attachInfo.mCanvas;
if (canvas == null) {
@@ -5737,15 +6019,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
computeScroll();
final int restoreCount = canvas.save();
+
+ if (autoScale && scalingRequired) {
+ final float scale = attachInfo.mApplicationScale;
+ canvas.scale(scale, scale);
+ }
+
canvas.translate(-mScrollX, -mScrollY);
- mPrivateFlags = (mPrivateFlags & ~DIRTY_MASK) | DRAWN;
+ mPrivateFlags |= DRAWN;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
+ mPrivateFlags &= ~DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
@@ -5792,7 +6081,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
canvas = new Canvas(bitmap);
}
- if ((backgroundColor&0xff000000) != 0) {
+ if ((backgroundColor & 0xff000000) != 0) {
bitmap.eraseColor(backgroundColor);
}
@@ -5800,6 +6089,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
final int restoreCount = canvas.save();
canvas.translate(-mScrollX, -mScrollY);
+ // Temporarily remove the dirty mask
+ int flags = mPrivateFlags;
+ mPrivateFlags &= ~DIRTY_MASK;
+
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
dispatchDraw(canvas);
@@ -5807,13 +6100,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
draw(canvas);
}
+ mPrivateFlags = flags;
+
canvas.restoreToCount(restoreCount);
if (attachInfo != null) {
// Restore the cached Canvas for our siblings
attachInfo.mCanvas = canvas;
}
-
+
return bitmap;
}
@@ -5927,8 +6222,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
- final boolean dirtyOpaque = (mPrivateFlags & DIRTY_MASK) == DIRTY_OPAQUE;
- mPrivateFlags = (mPrivateFlags & ~DIRTY_MASK) | DRAWN;
+ final int privateFlags = mPrivateFlags;
+ final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
+ (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
+ mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
@@ -6306,7 +6603,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
boolean changed = false;
if (DBG) {
- System.out.println(this + " View.setFrame(" + left + "," + top + ","
+ Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
@@ -6709,6 +7006,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
requestLayout = true;
}
+ computeOpaqueFlags();
+
if (requestLayout) {
requestLayout();
}
@@ -6749,7 +7048,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
mUserPaddingBottom = bottom;
final int viewFlags = mViewFlags;
-
+
// Common case is there are no scroll bars.
if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) {
// TODO: Deal with RTL languages to adjust left padding instead of right.
@@ -6762,7 +7061,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
? 0 : getHorizontalScrollbarHeight();
}
}
-
+
if (mPaddingLeft != left) {
changed = true;
mPaddingLeft = left;
@@ -6899,7 +7198,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
return v;
}
}
-
+
View parent = this;
while (parent.mParent != null && parent.mParent instanceof View) {
@@ -6920,8 +7219,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
getLocationInWindow(location);
final AttachInfo info = mAttachInfo;
- location[0] += info.mWindowLeft;
- location[1] += info.mWindowTop;
+ if (info != null) {
+ location[0] += info.mWindowLeft;
+ location[1] += info.mWindowTop;
+ }
}
/**
@@ -6947,7 +7248,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
location[1] += view.mTop - view.mScrollY;
viewParent = view.mParent;
}
-
+
if (viewParent instanceof ViewRoot) {
// *cough*
final ViewRoot vr = (ViewRoot)viewParent;
@@ -7098,7 +7399,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* @return the Object stored in this view as a tag
*
* @see #setTag(int, Object)
- * @see #getTag()
+ * @see #getTag()
*/
public Object getTag(int key) {
SparseArray<Object> tags = null;
@@ -7154,7 +7455,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
+ "resource id.");
}
- setTagInternal(this, key, tag);
+ setTagInternal(this, key, tag);
}
private static void setTagInternal(View view, int key, Object tag) {
@@ -7189,7 +7490,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* Method that subclasses should implement to check their consistency. The type of
* consistency check is indicated by the bit field passed as a parameter.
- *
+ *
* @param consistency The type of consistency. See ViewDebug for more information.
*
* @throws IllegalStateException if the view is in an inconsistent state.
@@ -7744,7 +8045,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* BZZZTT!!1!
- *
+ *
* <p>Provide haptic feedback to the user for this view.
*
* <p>The framework will provide haptic feedback for some built in actions,
@@ -7763,7 +8064,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
/**
* BZZZTT!!1!
- *
+ *
* <p>Like {@link #performHapticFeedback(int)}, with additional options.
*
* @param feedbackConstant One of the constants defined in
@@ -8158,7 +8459,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* window.
*/
static class AttachInfo {
-
interface Callbacks {
void playSoundEffect(int effectId);
boolean performHapticFeedback(int effectId, boolean always);
@@ -8227,11 +8527,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* The top view of the hierarchy.
*/
View mRootView;
-
+
IBinder mPanelParentWindowToken;
Surface mSurface;
/**
+ * Scale factor used by the compatibility mode
+ */
+ float mApplicationScale;
+
+ /**
+ * Indicates whether the application is in compatibility mode
+ */
+ boolean mScalingRequired;
+
+ /**
* Left position of this view's window
*/
int mWindowLeft;
@@ -8288,6 +8598,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
long mDrawingTime;
/**
+ * Indicates whether or not ignoring the DIRTY_MASK flags.
+ */
+ boolean mIgnoreDirtyState;
+
+ /**
* Indicates whether the view's window is currently in touch mode.
*/
boolean mInTouchMode;
@@ -8365,7 +8680,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
* calling up the hierarchy.
*/
final Rect mTmpInvalRect = new Rect();
-
+
+ /**
+ * Temporary list for use in collecting focusable descendents of a view.
+ */
+ final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
+
/**
* Creates a new set of attachment information with the specified
* events handler and thread.
@@ -8408,18 +8728,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback {
// use use a height of 1, and then wack the matrix each time we
// actually use it.
shader = new LinearGradient(0, 0, 0, 1, 0xFF000000, 0, Shader.TileMode.CLAMP);
-
+
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
}
-
+
public void setFadeColor(int color) {
if (color != 0 && color != mLastColor) {
mLastColor = color;
color |= 0xFF000000;
-
+
shader = new LinearGradient(0, 0, 0, 1, color, 0, Shader.TileMode.CLAMP);
-
+
paint.setShader(shader);
// Restore the default transfer mode (src_over)
paint.setXfermode(null);
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 8e1524b..0e36ec2 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -106,6 +106,11 @@ public class ViewConfiguration {
* Minimum velocity to initiate a fling, as measured in pixels per second
*/
private static final int MINIMUM_FLING_VELOCITY = 50;
+
+ /**
+ * Maximum velocity to initiate a fling, as measured in pixels per second
+ */
+ private static final int MAXIMUM_FLING_VELOCITY = 4000;
/**
* The maximum size of View's drawing cache, expressed in bytes. This size
@@ -122,6 +127,7 @@ public class ViewConfiguration {
private final int mEdgeSlop;
private final int mFadingEdgeLength;
private final int mMinimumFlingVelocity;
+ private final int mMaximumFlingVelocity;
private final int mScrollbarSize;
private final int mTouchSlop;
private final int mDoubleTapSlop;
@@ -139,6 +145,7 @@ public class ViewConfiguration {
mEdgeSlop = EDGE_SLOP;
mFadingEdgeLength = FADING_EDGE_LENGTH;
mMinimumFlingVelocity = MINIMUM_FLING_VELOCITY;
+ mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
mScrollbarSize = SCROLL_BAR_SIZE;
mTouchSlop = TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
@@ -164,6 +171,7 @@ public class ViewConfiguration {
mEdgeSlop = (int) (density * EDGE_SLOP + 0.5f);
mFadingEdgeLength = (int) (density * FADING_EDGE_LENGTH + 0.5f);
mMinimumFlingVelocity = (int) (density * MINIMUM_FLING_VELOCITY + 0.5f);
+ mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
mTouchSlop = (int) (density * TOUCH_SLOP + 0.5f);
mDoubleTapSlop = (int) (density * DOUBLE_TAP_SLOP + 0.5f);
@@ -367,6 +375,23 @@ public class ViewConfiguration {
}
/**
+ * @return Maximum velocity to initiate a fling, as measured in pixels per second.
+ *
+ * @deprecated Use {@link #getScaledMaximumFlingVelocity()} instead.
+ */
+ @Deprecated
+ public static int getMaximumFlingVelocity() {
+ return MAXIMUM_FLING_VELOCITY;
+ }
+
+ /**
+ * @return Maximum velocity to initiate a fling, as measured in pixels per second.
+ */
+ public int getScaledMaximumFlingVelocity() {
+ return mMaximumFlingVelocity;
+ }
+
+ /**
* The maximum drawing cache size expressed in bytes.
*
* @return the maximum size of View's drawing cache expressed in bytes
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 74a248f..46aea02 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -87,17 +87,17 @@ public class ViewDebug {
* check that this value is set to true as not to affect performance.
*/
public static final boolean TRACE_RECYCLER = false;
-
+
/**
* The system property of dynamic switch for capturing view information
* when it is set, we dump interested fields and methods for the view on focus
- */
+ */
static final String SYSTEM_PROPERTY_CAPTURE_VIEW = "debug.captureview";
-
+
/**
* The system property of dynamic switch for capturing event information
* when it is set, we log key events, touch/motion and trackball events
- */
+ */
static final String SYSTEM_PROPERTY_CAPTURE_EVENT = "debug.captureevent";
/**
@@ -216,7 +216,7 @@ public class ViewDebug {
* <pre>
*
* A specified String is output when the following is true:
- *
+ *
* @return An array of int to String mappings
*/
FlagToString[] flagMapping() default { };
@@ -228,7 +228,7 @@ public class ViewDebug {
*
* @return true if the properties of this property should be dumped
*
- * @see #prefix()
+ * @see #prefix()
*/
boolean deepExport() default false;
@@ -313,15 +313,15 @@ public class ViewDebug {
@Retention(RetentionPolicy.RUNTIME)
public @interface CapturedViewProperty {
/**
- * When retrieveReturn is true, we need to retrieve second level methods
+ * When retrieveReturn is true, we need to retrieve second level methods
* e.g., we need myView.getFirstLevelMethod().getSecondLevelMethod()
- * we will set retrieveReturn = true on the annotation of
+ * we will set retrieveReturn = true on the annotation of
* myView.getFirstLevelMethod()
- * @return true if we need the second level methods
+ * @return true if we need the second level methods
*/
- boolean retrieveReturn() default false;
+ boolean retrieveReturn() default false;
}
-
+
private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
@@ -401,7 +401,7 @@ public class ViewDebug {
*/
public static long getViewRootInstanceCount() {
return ViewRoot.getInstanceCount();
- }
+ }
/**
* Outputs a trace to the currently opened recycler traces. The trace records the type of
@@ -624,7 +624,7 @@ public class ViewDebug {
*
* This method will return immediately if TRACE_HIERARCHY is false.
*
- * @see #startHierarchyTracing(String, View)
+ * @see #startHierarchyTracing(String, View)
* @see #trace(View, android.view.ViewDebug.HierarchyTraceType)
*/
public static void stopHierarchyTracing() {
@@ -671,7 +671,7 @@ public class ViewDebug {
sHierarhcyRoot = null;
}
-
+
static void dispatchCommand(View view, String command, String parameters,
OutputStream clientStream) throws IOException {
@@ -1039,10 +1039,10 @@ public class ViewDebug {
final ArrayList<Method> foundMethods = new ArrayList<Method>();
methods = klass.getDeclaredMethods();
-
+
int count = methods.length;
for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ final Method method = methods[i];
if (method.getParameterTypes().length == 0 &&
method.isAnnotationPresent(ExportedProperty.class) &&
method.getReturnType() != Void.class) {
@@ -1075,7 +1075,7 @@ public class ViewDebug {
klass = klass.getSuperclass();
} while (klass != Object.class);
}
-
+
private static void exportMethods(Context context, Object view, BufferedWriter out,
Class<?> klass, String prefix) throws IOException {
@@ -1235,10 +1235,11 @@ public class ViewDebug {
for (int j = 0; j < count; j++) {
final FlagToString flagMapping = mapping[j];
final boolean ifTrue = flagMapping.outputIf();
- final boolean test = (intValue & flagMapping.mask()) == flagMapping.equals();
+ final int maskResult = intValue & flagMapping.mask();
+ final boolean test = maskResult == flagMapping.equals();
if ((test && ifTrue) || (!test && !ifTrue)) {
final String name = flagMapping.name();
- final String value = ifTrue ? "true" : "false";
+ final String value = "0x" + Integer.toHexString(maskResult);
writeEntry(out, prefix, name, "", value);
}
}
@@ -1259,7 +1260,7 @@ public class ViewDebug {
for (int j = 0; j < valuesCount; j++) {
String name;
- String value;
+ String value = null;
final int intValue = array[j];
@@ -1275,7 +1276,6 @@ public class ViewDebug {
}
}
- value = String.valueOf(intValue);
if (hasMapping) {
int mappingCount = mapping.length;
for (int k = 0; k < mappingCount; k++) {
@@ -1288,7 +1288,9 @@ public class ViewDebug {
}
if (resolveId) {
- value = (String) resolveId(context, intValue);
+ if (value == null) value = (String) resolveId(context, intValue);
+ } else {
+ value = String.valueOf(intValue);
}
writeEntry(out, prefix, name, suffix, value);
@@ -1396,10 +1398,10 @@ public class ViewDebug {
final ArrayList<Method> foundMethods = new ArrayList<Method>();
methods = klass.getMethods();
-
+
int count = methods.length;
for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ final Method method = methods[i];
if (method.getParameterTypes().length == 0 &&
method.isAnnotationPresent(CapturedViewProperty.class) &&
method.getReturnType() != Void.class) {
@@ -1413,14 +1415,14 @@ public class ViewDebug {
return methods;
}
-
- private static String capturedViewExportMethods(Object obj, Class<?> klass,
+
+ private static String capturedViewExportMethods(Object obj, Class<?> klass,
String prefix) {
if (obj == null) {
return "null";
}
-
+
StringBuilder sb = new StringBuilder();
final Method[] methods = capturedViewGetPropertyMethods(klass);
@@ -1430,41 +1432,41 @@ public class ViewDebug {
try {
Object methodValue = method.invoke(obj, (Object[]) null);
final Class<?> returnType = method.getReturnType();
-
+
CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
if (property.retrieveReturn()) {
//we are interested in the second level data only
sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
- } else {
+ } else {
sb.append(prefix);
sb.append(method.getName());
sb.append("()=");
-
+
if (methodValue != null) {
- final String value = methodValue.toString().replace("\n", "\\n");
- sb.append(value);
+ final String value = methodValue.toString().replace("\n", "\\n");
+ sb.append(value);
} else {
sb.append("null");
}
sb.append("; ");
}
} catch (IllegalAccessException e) {
- //Exception IllegalAccess, it is OK here
+ //Exception IllegalAccess, it is OK here
//we simply ignore this method
} catch (InvocationTargetException e) {
- //Exception InvocationTarget, it is OK here
+ //Exception InvocationTarget, it is OK here
//we simply ignore this method
- }
- }
+ }
+ }
return sb.toString();
}
private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
-
+
if (obj == null) {
return "null";
}
-
+
StringBuilder sb = new StringBuilder();
final Field[] fields = capturedViewGetPropertyFields(klass);
@@ -1486,25 +1488,25 @@ public class ViewDebug {
}
sb.append(' ');
} catch (IllegalAccessException e) {
- //Exception IllegalAccess, it is OK here
+ //Exception IllegalAccess, it is OK here
//we simply ignore this field
}
}
return sb.toString();
}
-
+
/**
- * Dump view info for id based instrument test generation
+ * Dump view info for id based instrument test generation
* (and possibly further data analysis). The results are dumped
- * to the log.
+ * to the log.
* @param tag for log
* @param view for dump
*/
- public static void dumpCapturedView(String tag, Object view) {
+ public static void dumpCapturedView(String tag, Object view) {
Class<?> klass = view.getClass();
StringBuilder sb = new StringBuilder(klass.getName() + ": ");
sb.append(capturedViewExportFields(view, klass, ""));
- sb.append(capturedViewExportMethods(view, klass, ""));
- Log.d(tag, sb.toString());
+ sb.append(capturedViewExportMethods(view, klass, ""));
+ Log.d(tag, sb.toString());
}
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 26fe776..f7b7f02 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -24,15 +24,16 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.RectF;
+import android.graphics.Region;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
+import android.util.Config;
import android.util.EventLog;
import android.util.Log;
import android.util.SparseArray;
-import android.util.Config;
+import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
@@ -52,6 +53,15 @@ import java.util.ArrayList;
* <p>
* Also see {@link LayoutParams} for layout attributes.
* </p>
+ *
+ * @attr ref android.R.styleable#ViewGroup_clipChildren
+ * @attr ref android.R.styleable#ViewGroup_clipToPadding
+ * @attr ref android.R.styleable#ViewGroup_layoutAnimation
+ * @attr ref android.R.styleable#ViewGroup_animationCache
+ * @attr ref android.R.styleable#ViewGroup_persistentDrawingCache
+ * @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache
+ * @attr ref android.R.styleable#ViewGroup_addStatesFromChildren
+ * @attr ref android.R.styleable#ViewGroup_descendantFocusability
*/
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private static final boolean DBG = false;
@@ -89,7 +99,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Internal flags.
- *
+ *
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@@ -142,7 +152,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* to get the index of the child to draw for that iteration.
*/
protected static final int FLAG_USE_CHILD_DRAWING_ORDER = 0x400;
-
+
/**
* When set, this ViewGroup supports static transformations on children; this causes
* {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} to be
@@ -151,7 +161,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* Any subclass overriding
* {@link #getChildStaticTransformation(View, android.view.animation.Transformation)} should
* set this flags in {@link #mGroupFlags}.
- *
+ *
* {@hide}
*/
protected static final int FLAG_SUPPORT_STATIC_TRANSFORMATIONS = 0x800;
@@ -212,7 +222,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* When set, this ViewGroup should not intercept touch events.
*/
private static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
-
+
/**
* Indicates which types of drawing caches are to be kept in memory.
* This field should be made private, so it is hidden from the SDK.
@@ -601,6 +611,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
public void addFocusables(ArrayList<View> views, int direction) {
+ addFocusables(views, direction, FOCUSABLES_TOUCH_MODE);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
final int focusableCount = views.size();
final int descendantFocusability = getDescendantFocusability();
@@ -612,7 +630,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
- child.addFocusables(views, direction);
+ child.addFocusables(views, direction, focusableMode);
}
}
}
@@ -625,7 +643,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
// No focusable descendants
(focusableCount == views.size())) {
- super.addFocusables(views, direction);
+ super.addFocusables(views, direction, focusableMode);
}
}
@@ -680,7 +698,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
ViewParent parent = mParent;
if (parent != null) parent.recomputeViewAttributes(this);
}
-
+
@Override
void dispatchCollectViewAttributes(int visibility) {
visibility |= mViewFlags&VISIBILITY_MASK;
@@ -812,16 +830,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
}
-
+
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
+ (action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
-
+
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
@@ -868,18 +886,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@inheritDoc}
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
-
+
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
-
+
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
-
+
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
@@ -1020,6 +1038,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ boolean populated = false;
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ populated |= getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ }
+ return populated;
+ }
+
/**
* {@inheritDoc}
*/
@@ -1139,7 +1166,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
child.setDrawingCacheEnabled(true);
- child.buildDrawingCache();
+ child.buildDrawingCache(true);
}
}
@@ -1181,7 +1208,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
bindLayoutAnimation(child);
if (cache) {
child.setDrawingCacheEnabled(true);
- child.buildDrawingCache();
+ child.buildDrawingCache(true);
}
}
}
@@ -1274,7 +1301,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
post(end);
}
}
-
+
/**
* Returns the index of the child to draw for this iteration. Override this
* if you want to change the drawing order of children. By default, it
@@ -1282,14 +1309,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* <p>
* NOTE: In order for this method to be called, the
* {@link #FLAG_USE_CHILD_DRAWING_ORDER} must be set.
- *
+ *
* @param i The current iteration.
* @return The index of the child to draw this iteration.
*/
protected int getChildDrawingOrder(int childCount, int i) {
return i;
}
-
+
private void notifyAnimationListener() {
mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
mGroupFlags |= FLAG_ANIMATION_DONE;
@@ -1403,9 +1430,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- // Clear the flag as early as possible to allow draw() implementations
+ // Sets the flag as early as possible to allow draw() implementations
// to call invalidate() successfully when doing animations
- child.mPrivateFlags = (child.mPrivateFlags & ~DIRTY_MASK) | DRAWN;
+ child.mPrivateFlags |= DRAWN;
if (!concatMatrix && canvas.quickReject(cl, ct, cr, cb, Canvas.EdgeType.BW) &&
(child.mPrivateFlags & DRAW_ANIMATION) == 0) {
@@ -1417,10 +1444,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final int sx = child.mScrollX;
final int sy = child.mScrollY;
+ boolean scalingRequired = false;
Bitmap cache = null;
if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
(flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
- cache = child.getDrawingCache();
+ cache = child.getDrawingCache(true);
+ if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
}
final boolean hasNoCache = cache == null;
@@ -1430,6 +1459,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
canvas.translate(cl - sx, ct - sy);
} else {
canvas.translate(cl, ct);
+ if (scalingRequired) {
+ // mAttachInfo cannot be null, otherwise scalingRequired == false
+ final float scale = 1.0f / mAttachInfo.mApplicationScale;
+ canvas.scale(scale, scale);
+ }
}
float alpha = 1.0f;
@@ -1472,7 +1506,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (hasNoCache) {
canvas.clipRect(sx, sy, sx + (cr - cl), sy + (cb - ct));
} else {
- canvas.clipRect(0, 0, cr - cl, cb - ct);
+ if (!scalingRequired) {
+ canvas.clipRect(0, 0, cr - cl, cb - ct);
+ } else {
+ canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
+ }
}
}
@@ -1482,6 +1520,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
}
+ child.mPrivateFlags &= ~DIRTY_MASK;
child.dispatchDraw(canvas);
} else {
child.draw(canvas);
@@ -1546,7 +1585,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
children[i].setSelected(selected);
}
}
-
+
@Override
protected void dispatchSetPressed(boolean pressed) {
final View[] children = mChildren;
@@ -1577,7 +1616,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* {@inheritDoc}
*
- * @see #setStaticTransformationsEnabled(boolean)
+ * @see #setStaticTransformationsEnabled(boolean)
*/
protected boolean getChildStaticTransformation(View child, Transformation t) {
return false;
@@ -1844,10 +1883,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (child.hasFocus()) {
requestChildFocus(child, child.findFocus());
}
-
+
AttachInfo ai = mAttachInfo;
if (ai != null) {
- boolean lastKeepOn = ai.mKeepScreenOn;
+ boolean lastKeepOn = ai.mKeepScreenOn;
ai.mKeepScreenOn = false;
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
if (ai.mKeepScreenOn) {
@@ -2047,7 +2086,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
needGlobalAttributesUpdate(false);
-
+
removeFromArray(index);
if (clearChildFocus) {
@@ -2080,7 +2119,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
needGlobalAttributesUpdate(false);
-
+
if (notifyListener) {
onHierarchyChangeListener.onChildViewRemoved(this, view);
}
@@ -2128,7 +2167,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
View clearChildFocus = null;
needGlobalAttributesUpdate(false);
-
+
for (int i = count - 1; i >= 0; i--) {
final View view = children[i];
@@ -2173,7 +2212,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (child == mFocused) {
child.clearFocus();
}
-
+
if (animate && child.getAnimation() != null) {
addDisappearingView(child);
} else if (child.mAttachInfo != null) {
@@ -2323,7 +2362,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final boolean drawAnimation = (child.mPrivateFlags & DRAW_ANIMATION) == DRAW_ANIMATION;
// Check whether the child that requests the invalidate is fully opaque
- final boolean isOpaque = child.isOpaque();
+ final boolean isOpaque = child.isOpaque() && !drawAnimation &&
+ child.getAnimation() != null;
// Mark the child as dirty, using the appropriate flag
// Make sure we do not set both flags at the same time
final int opaqueFlag = isOpaque ? DIRTY_OPAQUE : DIRTY;
@@ -3135,7 +3175,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
}
-
+
@Override
protected boolean fitSystemWindows(Rect insets) {
@@ -3269,7 +3309,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* laid out. See
* {@link android.R.styleable#ViewGroup_Layout ViewGroup Layout Attributes}
* for a list of all child view attributes that this class supports.
- *
+ *
* <p>
* The base LayoutParams class just describes how big the view wants to be
* for both width and height. For each dimension, it can specify one of:
@@ -3400,7 +3440,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @param output the String to prepend to the internal representation
* @return a String with the following format: output +
* "ViewGroup.LayoutParams={ width=WIDTH, height=HEIGHT }"
- *
+ *
* @hide
*/
public String debug(String output) {
@@ -3413,7 +3453,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* @param size the size to convert
* @return a String instance representing the supplied size
- *
+ *
* @hide
*/
protected static String sizeToString(int size) {
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 5090c56..6f6e224 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -30,14 +30,18 @@ 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.SparseArray;
import android.view.View.MeasureSpec;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
import android.content.Context;
import android.app.ActivityManagerNative;
import android.Manifest;
@@ -90,18 +94,18 @@ public final class ViewRoot extends Handler implements ViewParent,
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
- private static int sDrawTime;
+ private static int sDrawTime;
long mLastTrackballTime = 0;
final TrackballAxis mTrackballAxisX = new TrackballAxis();
final TrackballAxis mTrackballAxisY = new TrackballAxis();
final int[] mTmpLocation = new int[2];
-
+
final InputMethodCallback mInputMethodCallback;
final SparseArray<Object> mPendingEvents = new SparseArray<Object>();
int mPendingEventSeq = 0;
-
+
final Thread mThread;
final WindowLeaked mLocation;
@@ -123,16 +127,13 @@ public final class ViewRoot extends Handler implements ViewParent,
int mHeight;
Rect mDirty; // will be a graphics.Region soon
boolean mIsAnimating;
- // TODO: change these to scalar class.
- private float mAppScale;
- private float mAppScaleInverted; // = 1.0f / mAppScale
- private int[] mWindowLayoutParamsBackup = null;
+
+ CompatibilityInfo.Translator mTranslator;
final View.AttachInfo mAttachInfo;
final Rect mTempRect; // used in the transaction to not thrash the heap.
final Rect mVisRect; // used to retrieve visible rect of focused view.
- final Point mVisPoint; // used to retrieve global offset of focused view.
boolean mTraversalScheduled;
boolean mWillDrawSoon;
@@ -168,7 +169,7 @@ public final class ViewRoot extends Handler implements ViewParent,
int mScrollY;
int mCurScrollY;
Scroller mScroller;
-
+
EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLContext mEglContext;
@@ -178,7 +179,7 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean mUseGL;
boolean mGlWanted;
- final ViewConfiguration mViewConfiguration;
+ final ViewConfiguration mViewConfiguration;
/**
* see {@link #playSoundEffect(int)}
@@ -216,7 +217,6 @@ public final class ViewRoot extends Handler implements ViewParent,
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
- mVisPoint = new Point();
mWinFrame = new Rect();
mWindow = new W(this, context);
mInputMethodCallback = new InputMethodCallback(this);
@@ -384,29 +384,39 @@ public final class ViewRoot extends Handler implements ViewParent,
synchronized (this) {
if (mView == null) {
mView = view;
- mAppScale = mView.getContext().getApplicationScale();
- if (mAppScale != 1.0f) {
- mWindowLayoutParamsBackup = new int[4];
- }
- mAppScaleInverted = 1.0f / mAppScale;
mWindowAttributes.copyFrom(attrs);
+
+ CompatibilityInfo compatibilityInfo =
+ mView.getContext().getResources().getCompatibilityInfo();
+ mTranslator = compatibilityInfo.getTranslator(attrs);
+ boolean restore = false;
+ if (attrs != null && mTranslator != null) {
+ restore = true;
+ attrs.backup();
+ mTranslator.translateWindowLayout(attrs);
+ }
+ if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs);
+
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mAttachInfo.mRootView = view;
+ mAttachInfo.mScalingRequired =
+ mTranslator == null ? false : mTranslator.scalingRequired;
+ mAttachInfo.mApplicationScale =
+ mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
-
+
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
-
try {
- res = sWindowSession.add(mWindow, attrs,
+ res = sWindowSession.add(mWindow, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets);
} catch (RemoteException e) {
mAdded = false;
@@ -414,8 +424,15 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mRootView = null;
unscheduleTraversals();
throw new RuntimeException("Adding window failed", e);
+ } finally {
+ if (restore) {
+ attrs.restore();
+ }
+ }
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
- mAttachInfo.mContentInsets.scale(mAppScaleInverted);
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow);
@@ -526,18 +543,20 @@ public final class ViewRoot extends Handler implements ViewParent,
public void invalidateChild(View child, Rect dirty) {
checkThread();
- if (LOCAL_LOGV) Log.v(TAG, "Invalidate child: " + dirty);
- if (mCurScrollY != 0 || mAppScale != 1.0f) {
+ if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
+ if (mCurScrollY != 0 || mTranslator != null) {
mTempRect.set(dirty);
+ dirty = mTempRect;
if (mCurScrollY != 0) {
- mTempRect.offset(0, -mCurScrollY);
+ dirty.offset(0, -mCurScrollY);
}
- if (mAppScale != 1.0f) {
- mTempRect.scale(mAppScale);
+ if (mTranslator != null) {
+ mTranslator.translateRectInAppWindowToScreen(dirty);
+ }
+ if (mAttachInfo.mScalingRequired) {
+ dirty.inset(-1, -1);
}
- dirty = mTempRect;
}
- // TODO: When doing a union with mDirty != empty, we must cancel all the DIRTY_OPAQUE flags
mDirty.union(dirty);
if (!mWillDrawSoon) {
scheduleTraversals();
@@ -553,7 +572,7 @@ public final class ViewRoot extends Handler implements ViewParent,
return null;
}
- public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
if (child != mView) {
throw new RuntimeException("child is not mine, honest!");
}
@@ -582,7 +601,7 @@ public final class ViewRoot extends Handler implements ViewParent,
int getHostVisibility() {
return mAppVisible ? mView.getVisibility() : View.GONE;
}
-
+
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
@@ -614,19 +633,22 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean viewVisibilityChanged = mViewVisibility != viewVisibility
|| mNewSurfaceNeeded;
+ float appScale = mAttachInfo.mApplicationScale;
+
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
params = lp;
}
-
+ Rect frame = mWinFrame;
if (mFirst) {
fullRedrawNeeded = true;
mLayoutRequested = true;
- Display d = new Display(0);
- desiredWindowWidth = (int) (d.getWidth() * mAppScaleInverted);
- desiredWindowHeight = (int) (d.getHeight() * mAppScaleInverted);
+ DisplayMetrics packageMetrics =
+ mView.getContext().getResources().getDisplayMetrics();
+ desiredWindowWidth = packageMetrics.widthPixels;
+ desiredWindowHeight = packageMetrics.heightPixels;
// For the very first time, tell the view hierarchy that it
// is attached to the window. Note that at this point the surface
@@ -641,12 +663,13 @@ public final class ViewRoot extends Handler implements ViewParent,
host.dispatchAttachedToWindow(attachInfo, 0);
getRunQueue().executeActions(attachInfo.mHandler);
//Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn);
+
} else {
- desiredWindowWidth = mWinFrame.width();
- desiredWindowHeight = mWinFrame.height();
+ desiredWindowWidth = frame.width();
+ desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v("ViewRoot",
- "View " + host + " resized to: " + mWinFrame);
+ "View " + host + " resized to: " + frame);
fullRedrawNeeded = true;
mLayoutRequested = true;
windowResizesToFitContent = true;
@@ -669,7 +692,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
boolean insetsChanged = false;
-
+
if (mLayoutRequested) {
if (mFirst) {
host.fitSystemWindows(mAttachInfo.mContentInsets);
@@ -694,9 +717,10 @@ public final class ViewRoot extends Handler implements ViewParent,
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowResizesToFitContent = true;
- Display d = new Display(0);
- desiredWindowWidth = (int) (d.getWidth() * mAppScaleInverted);
- desiredWindowHeight = (int) (d.getHeight() * mAppScaleInverted);
+ DisplayMetrics packageMetrics =
+ mView.getContext().getResources().getDisplayMetrics();
+ desiredWindowWidth = packageMetrics.widthPixels;
+ desiredWindowHeight = packageMetrics.heightPixels;
}
}
@@ -753,7 +777,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
-
+
if (params != null && (host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
if (!PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
@@ -782,7 +806,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// computed insets.
insetsPending = computesInternalInsets
&& (mFirst || viewVisibilityChanged);
-
+
if (mWindowAttributes.memoryType == WindowManager.LayoutParams.MEMORY_TYPE_GPU) {
if (params == null) {
params = mWindowAttributes;
@@ -791,7 +815,6 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
- final Rect frame = mWinFrame;
boolean initialized = false;
boolean contentInsetsChanged = false;
boolean visibleInsetsChanged;
@@ -818,7 +841,7 @@ public final class ViewRoot extends Handler implements ViewParent,
+ " content=" + mPendingContentInsets.toShortString()
+ " visible=" + mPendingVisibleInsets.toShortString()
+ " surface=" + mSurface);
-
+
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
visibleInsetsChanged = !mPendingVisibleInsets.equals(
@@ -846,7 +869,7 @@ public final class ViewRoot extends Handler implements ViewParent,
// all at once.
newSurface = true;
fullRedrawNeeded = true;
-
+
if (mGlWanted && !mUseGL) {
initializeGL();
initialized = mGlCanvas != null;
@@ -864,7 +887,7 @@ public final class ViewRoot extends Handler implements ViewParent,
} catch (RemoteException e) {
}
if (DEBUG_ORIENTATION) Log.v(
- "ViewRoot", "Relayout returned: frame=" + mWinFrame + ", surface=" + mSurface);
+ "ViewRoot", "Relayout returned: frame=" + frame + ", surface=" + mSurface);
attachInfo.mWindowLeft = frame.left;
attachInfo.mWindowTop = frame.top;
@@ -876,7 +899,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mHeight = frame.height();
if (initialized) {
- mGlCanvas.setViewport((int) (mWidth * mAppScale), (int) (mHeight * mAppScale));
+ mGlCanvas.setViewport((int) (mWidth * appScale), (int) (mHeight * appScale));
}
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
@@ -891,7 +914,7 @@ public final class ViewRoot extends Handler implements ViewParent,
+ " mHeight=" + mHeight
+ " measuredHeight" + host.mMeasuredHeight
+ " coveredInsetsChanged=" + contentInsetsChanged);
-
+
// Ask host how big it wants to be
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
@@ -939,7 +962,6 @@ public final class ViewRoot extends Handler implements ViewParent,
if (Config.DEBUG && ViewDebug.profileLayout) {
startTime = SystemClock.elapsedRealtime();
}
-
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
@@ -966,11 +988,10 @@ public final class ViewRoot extends Handler implements ViewParent,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
+ if (mTranslator != null) {
+ mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
+ }
- // TODO: scale the region, like:
- // Region uses native methods. We probabl should have ScalableRegion class.
-
- // Region does not have equals method ?
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
// reconfigure window manager
@@ -981,7 +1002,6 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
-
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
@@ -1001,20 +1021,23 @@ public final class ViewRoot extends Handler implements ViewParent,
givenContent.left = givenContent.top = givenContent.right
= givenContent.bottom = givenVisible.left = givenVisible.top
= givenVisible.right = givenVisible.bottom = 0;
- insets.contentInsets.scale(mAppScale);
- insets.visibleInsets.scale(mAppScale);
-
attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
+ Rect contentInsets = insets.contentInsets;
+ Rect visibleInsets = insets.visibleInsets;
+ if (mTranslator != null) {
+ contentInsets = mTranslator.getTranslatedContentInsets(contentInsets);
+ visibleInsets = mTranslator.getTranslatedVisbileInsets(visibleInsets);
+ }
if (insetsPending || !mLastGivenInsets.equals(insets)) {
mLastGivenInsets.set(insets);
try {
sWindowSession.setInsets(mWindow, insets.mTouchableInsets,
- insets.contentInsets, insets.visibleInsets);
+ contentInsets, visibleInsets);
} catch (RemoteException e) {
}
}
}
-
+
if (mFirst) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: mView.hasFocus()="
@@ -1052,7 +1075,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
-
+
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
if (!cancelDraw && !newSurface) {
@@ -1140,10 +1163,9 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
-
+
int yoff;
- final boolean scrolling = mScroller != null
- && mScroller.computeScrollOffset();
+ final boolean scrolling = mScroller != null && mScroller.computeScrollOffset();
if (scrolling) {
yoff = mScroller.getCurrY();
} else {
@@ -1153,26 +1175,28 @@ public final class ViewRoot extends Handler implements ViewParent,
mCurScrollY = yoff;
fullRedrawNeeded = true;
}
+ float appScale = mAttachInfo.mApplicationScale;
+ boolean scalingRequired = mAttachInfo.mScalingRequired;
Rect dirty = mDirty;
if (mUseGL) {
if (!dirty.isEmpty()) {
Canvas canvas = mGlCanvas;
- if (mGL!=null && canvas != null) {
+ if (mGL != null && canvas != null) {
mGL.glDisable(GL_SCISSOR_TEST);
mGL.glClearColor(0, 0, 0, 0);
mGL.glClear(GL_COLOR_BUFFER_BIT);
mGL.glEnable(GL_SCISSOR_TEST);
mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
+ mAttachInfo.mIgnoreDirtyState = true;
mView.mPrivateFlags |= View.DRAWN;
- float scale = mAppScale;
int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
try {
canvas.translate(0, -yoff);
- if (scale != 1.0f) {
- canvas.scale(scale, scale);
+ if (mTranslator != null) {
+ mTranslator.translateCanvas(canvas);
}
mView.draw(canvas);
if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
@@ -1182,6 +1206,8 @@ public final class ViewRoot extends Handler implements ViewParent,
canvas.restoreToCount(saveCount);
}
+ mAttachInfo.mIgnoreDirtyState = false;
+
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
checkEglErrors();
@@ -1201,20 +1227,33 @@ public final class ViewRoot extends Handler implements ViewParent,
return;
}
- if (fullRedrawNeeded)
- dirty.union(0, 0, (int) (mWidth * mAppScale), (int) (mHeight * mAppScale));
+ if (fullRedrawNeeded) {
+ mAttachInfo.mIgnoreDirtyState = true;
+ dirty.union(0, 0, (int) (mWidth * appScale), (int) (mHeight * appScale));
+ }
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v("ViewRoot", "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
- + surface + " surface.isValid()=" + surface.isValid());
+ + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
+ 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.setDensityScale(mDensity);
} catch (Surface.OutOfResourcesException e) {
@@ -1242,12 +1281,11 @@ public final class ViewRoot extends Handler implements ViewParent,
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
- if (!canvas.isOpaque()) {
- canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
- } else if (yoff != 0) {
- // If we are applying an offset, we need to clear the area
- // where the offset doesn't appear to avoid having garbage
- // left in the blank areas.
+ // or
+ // If we are applying an offset, we need to clear the area
+ // where the offset doesn't appear to avoid having garbage
+ // left in the blank areas.
+ if (!canvas.isOpaque() || yoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
@@ -1256,27 +1294,27 @@ public final class ViewRoot extends Handler implements ViewParent,
mAttachInfo.mDrawingTime = SystemClock.uptimeMillis();
mView.mPrivateFlags |= View.DRAWN;
- float scale = mAppScale;
- Context cxt = mView.getContext();
if (DEBUG_DRAW) {
- Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + ", appScale=" + mAppScale);
+ Context cxt = mView.getContext();
+ Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
+ ", metrics=" + mView.getContext().getResources().getDisplayMetrics());
}
- int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
+ int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
try {
canvas.translate(0, -yoff);
- if (scale != 1.0f) {
- // re-scale this
- canvas.scale(scale, scale);
+ if (mTranslator != null) {
+ mTranslator.translateCanvas(canvas);
}
mView.draw(canvas);
-
- if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
- mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
- }
} finally {
+ mAttachInfo.mIgnoreDirtyState = false;
canvas.restoreToCount(saveCount);
}
+ if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
+ mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING);
+ }
+
if (Config.DEBUG && ViewDebug.showFps) {
int now = (int)SystemClock.elapsedRealtime();
if (sDrawTime != 0) {
@@ -1289,7 +1327,7 @@ public final class ViewRoot extends Handler implements ViewParent,
EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
}
}
-
+
} finally {
surface.unlockCanvasAndPost(canvas);
}
@@ -1297,7 +1335,7 @@ public final class ViewRoot extends Handler implements ViewParent,
if (LOCAL_LOGV) {
Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost");
}
-
+
if (scrolling) {
mFullRedrawNeeded = true;
scheduleTraversals();
@@ -1310,7 +1348,7 @@ public final class ViewRoot extends Handler implements ViewParent,
final Rect vi = attachInfo.mVisibleInsets;
int scrollY = 0;
boolean handled = false;
-
+
if (vi.left > ci.left || vi.top > ci.top
|| vi.right > ci.right || vi.bottom > ci.bottom) {
// We'll assume that we aren't going to change the scroll
@@ -1397,7 +1435,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
-
+
if (scrollY != mScrollY) {
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
+ mScrollY + " , new=" + scrollY);
@@ -1411,10 +1449,10 @@ public final class ViewRoot extends Handler implements ViewParent,
}
mScrollY = scrollY;
}
-
+
return handled;
}
-
+
public void requestChildFocus(View child, View focused) {
checkThread();
if (mFocusedView != focused) {
@@ -1494,7 +1532,7 @@ public final class ViewRoot extends Handler implements ViewParent,
} catch (RemoteException e) {
}
}
-
+
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
@@ -1568,10 +1606,9 @@ public final class ViewRoot extends Handler implements ViewParent,
} else {
didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE;
}
- if (event != null) {
- event.scale(mAppScaleInverted);
+ if (event != null && mTranslator != null) {
+ mTranslator.translateEventInScreenToAppWindow(event);
}
-
try {
boolean handled;
if (mView != null && mAdded && event != null) {
@@ -1657,6 +1694,7 @@ public final class ViewRoot extends Handler implements ViewParent,
case RESIZED:
Rect coveredInsets = ((Rect[])msg.obj)[0];
Rect visibleInsets = ((Rect[])msg.obj)[1];
+
if (mWinFrame.width() == msg.arg1 && mWinFrame.height() == msg.arg2
&& mPendingContentInsets.equals(coveredInsets)
&& mPendingVisibleInsets.equals(visibleInsets)) {
@@ -1691,16 +1729,17 @@ public final class ViewRoot extends Handler implements ViewParent,
if (mGlWanted && !mUseGL) {
initializeGL();
if (mGlCanvas != null) {
- mGlCanvas.setViewport((int) (mWidth * mAppScale),
- (int) (mHeight * mAppScale));
+ float appScale = mAttachInfo.mApplicationScale;
+ mGlCanvas.setViewport(
+ (int) (mWidth * appScale), (int) (mHeight * appScale));
}
}
}
}
-
+
mLastWasImTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
-
+
InputMethodManager imm = InputMethodManager.peekInstance();
if (mView != null) {
if (hasWindowFocus && imm != null && mLastWasImTarget) {
@@ -1708,7 +1747,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
mView.dispatchWindowFocusChanged(hasWindowFocus);
}
-
+
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
@@ -1726,6 +1765,10 @@ public final class ViewRoot extends Handler implements ViewParent,
~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
mHasHadWindowFocus = true;
}
+
+ if (hasWindowFocus && mView != null) {
+ sendAccessibilityEvents();
+ }
}
} break;
case DIE:
@@ -1892,9 +1935,6 @@ public final class ViewRoot extends Handler implements ViewParent,
} else {
didFinish = false;
}
- if (event != null) {
- event.scale(mAppScaleInverted);
- }
if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event);
@@ -2120,50 +2160,50 @@ public final class ViewRoot extends Handler implements ViewParent,
}
/**
- * log motion events
+ * log motion events
*/
private static void captureMotionLog(String subTag, MotionEvent ev) {
- //check dynamic switch
+ //check dynamic switch
if (ev == null ||
SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
return;
- }
-
- StringBuilder sb = new StringBuilder(subTag + ": ");
- sb.append(ev.getDownTime()).append(',');
- sb.append(ev.getEventTime()).append(',');
- sb.append(ev.getAction()).append(',');
- sb.append(ev.getX()).append(',');
- sb.append(ev.getY()).append(',');
- sb.append(ev.getPressure()).append(',');
- sb.append(ev.getSize()).append(',');
- sb.append(ev.getMetaState()).append(',');
- sb.append(ev.getXPrecision()).append(',');
- sb.append(ev.getYPrecision()).append(',');
- sb.append(ev.getDeviceId()).append(',');
+ }
+
+ StringBuilder sb = new StringBuilder(subTag + ": ");
+ sb.append(ev.getDownTime()).append(',');
+ sb.append(ev.getEventTime()).append(',');
+ sb.append(ev.getAction()).append(',');
+ sb.append(ev.getX()).append(',');
+ sb.append(ev.getY()).append(',');
+ sb.append(ev.getPressure()).append(',');
+ sb.append(ev.getSize()).append(',');
+ sb.append(ev.getMetaState()).append(',');
+ sb.append(ev.getXPrecision()).append(',');
+ sb.append(ev.getYPrecision()).append(',');
+ sb.append(ev.getDeviceId()).append(',');
sb.append(ev.getEdgeFlags());
- Log.d(TAG, sb.toString());
+ Log.d(TAG, sb.toString());
}
/**
- * log motion events
+ * log motion events
*/
private static void captureKeyLog(String subTag, KeyEvent ev) {
- //check dynamic switch
- if (ev == null ||
+ //check dynamic switch
+ if (ev == null ||
SystemProperties.getInt(ViewDebug.SYSTEM_PROPERTY_CAPTURE_EVENT, 0) == 0) {
return;
}
- StringBuilder sb = new StringBuilder(subTag + ": ");
+ StringBuilder sb = new StringBuilder(subTag + ": ");
sb.append(ev.getDownTime()).append(',');
sb.append(ev.getEventTime()).append(',');
sb.append(ev.getAction()).append(',');
- sb.append(ev.getKeyCode()).append(',');
+ sb.append(ev.getKeyCode()).append(',');
sb.append(ev.getRepeatCount()).append(',');
sb.append(ev.getMetaState()).append(',');
sb.append(ev.getDeviceId()).append(',');
sb.append(ev.getScanCode());
- Log.d(TAG, sb.toString());
- }
+ Log.d(TAG, sb.toString());
+ }
int enqueuePendingEvent(Object event, boolean sendDone) {
int seq = mPendingEventSeq+1;
@@ -2181,7 +2221,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
return event;
}
-
+
private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
// If mView is null, we just consume the key event because it doesn't
// make sense to do anything else with it.
@@ -2238,7 +2278,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
-
+
private void deliverKeyEventToViewHierarchy(KeyEvent event, boolean sendDone) {
try {
if (mView != null && mAdded) {
@@ -2247,8 +2287,8 @@ public final class ViewRoot extends Handler implements ViewParent,
if (checkForLeavingTouchModeAndConsume(event)) {
return;
- }
-
+ }
+
if (Config.LOGV) {
captureKeyLog("captureDispatchKeyEvent", event);
}
@@ -2324,24 +2364,31 @@ public final class ViewRoot extends Handler implements ViewParent,
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
+ float appScale = mAttachInfo.mApplicationScale;
boolean restore = false;
- if (params != null && mAppScale != 1.0f) {
+ if (params != null && mTranslator != null) {
restore = true;
- params.scale(mAppScale, mWindowLayoutParamsBackup);
+ params.backup();
+ mTranslator.translateWindowLayout(params);
+ }
+ if (params != null) {
+ if (DBG) Log.d(TAG, "WindowLayout in layoutWindow:" + params);
}
int relayoutResult = sWindowSession.relayout(
mWindow, params,
- (int) (mView.mMeasuredWidth * mAppScale),
- (int) (mView.mMeasuredHeight * mAppScale),
+ (int) (mView.mMeasuredWidth * appScale),
+ (int) (mView.mMeasuredHeight * appScale),
viewVisibility, insetsPending, mWinFrame,
mPendingContentInsets, mPendingVisibleInsets, mSurface);
if (restore) {
- params.restore(mWindowLayoutParamsBackup);
+ params.restore();
+ }
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
+ mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
+ mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
}
-
- mPendingContentInsets.scale(mAppScaleInverted);
- mPendingVisibleInsets.scale(mAppScaleInverted);
- mWinFrame.scale(mAppScaleInverted);
return relayoutResult;
}
@@ -2448,11 +2495,14 @@ public final class ViewRoot extends Handler implements ViewParent,
+ " visibleInsets=" + visibleInsets.toShortString()
+ " reportDraw=" + reportDraw);
Message msg = obtainMessage(reportDraw ? RESIZED_REPORT :RESIZED);
-
- coveredInsets.scale(mAppScaleInverted);
- visibleInsets.scale(mAppScaleInverted);
- msg.arg1 = (int) (w * mAppScaleInverted);
- msg.arg2 = (int) (h * mAppScaleInverted);
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(coveredInsets);
+ mTranslator.translateRectInScreenToAppWindow(visibleInsets);
+ w *= mTranslator.applicationInvertedScale;
+ h *= mTranslator.applicationInvertedScale;
+ }
+ msg.arg1 = w;
+ msg.arg2 = h;
msg.obj = new Rect[] { new Rect(coveredInsets), new Rect(visibleInsets) };
sendMessage(msg);
}
@@ -2511,6 +2561,21 @@ public final class ViewRoot extends Handler implements ViewParent,
sendMessage(msg);
}
+ /**
+ * The window is getting focus so if there is anything focused/selected
+ * send an {@link AccessibilityEvent} to announce that.
+ */
+ private void sendAccessibilityEvents() {
+ if (!AccessibilityManager.getInstance(mView.getContext()).isEnabled()) {
+ return;
+ }
+ mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ View focusedView = mView.findFocus();
+ if (focusedView != null && focusedView != mView) {
+ focusedView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
+ }
+
public boolean showContextMenuForChild(View originalView) {
return false;
}
@@ -2540,14 +2605,14 @@ public final class ViewRoot extends Handler implements ViewParent,
boolean immediate) {
return scrollToRectOrFocus(rectangle, immediate);
}
-
+
static class InputMethodCallback extends IInputMethodCallback.Stub {
private WeakReference<ViewRoot> mViewRoot;
public InputMethodCallback(ViewRoot viewRoot) {
mViewRoot = new WeakReference<ViewRoot>(viewRoot);
}
-
+
public void finishedEvent(int seq, boolean handled) {
final ViewRoot viewRoot = mViewRoot.get();
if (viewRoot != null) {
@@ -2559,13 +2624,13 @@ public final class ViewRoot extends Handler implements ViewParent,
// Stub -- not for use in the client.
}
}
-
+
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);
@@ -2575,7 +2640,7 @@ public final class ViewRoot extends Handler implements ViewParent,
mMotionEvent = motion;
sendEmptyMessage(0);
}
-
+
@Override
public void handleMessage(Message msg) {
if (mKeyEvent != null) {
@@ -2617,7 +2682,7 @@ public final class ViewRoot extends Handler implements ViewParent,
}
}
}
-
+
static class W extends IWindow.Stub {
private final WeakReference<ViewRoot> mViewRoot;
private final Looper mMainLooper;
@@ -2739,14 +2804,14 @@ public final class ViewRoot extends Handler implements ViewParent,
* The maximum amount of acceleration we will apply.
*/
static final float MAX_ACCELERATION = 20;
-
+
/**
* The maximum amount of time (in milliseconds) between events in order
* for us to consider the user to be doing fast trackball movements,
* and thus apply an acceleration.
*/
static final long FAST_MOVE_TIME = 150;
-
+
/**
* Scaling factor to the time (in milliseconds) between events to how
* much to multiple/divide the current acceleration. When movement
@@ -2754,7 +2819,7 @@ public final class ViewRoot extends Handler implements ViewParent,
* FAST_MOVE_TIME it divides it.
*/
static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
-
+
float position;
float absPosition;
float acceleration = 1;
@@ -2806,7 +2871,7 @@ public final class ViewRoot extends Handler implements ViewParent,
} else {
normTime = 0;
}
-
+
// The number of milliseconds between each movement that is
// considered "normal" and will not result in any acceleration
// or deceleration, scaled by the offset we have here.
@@ -2964,7 +3029,7 @@ public final class ViewRoot extends Handler implements ViewParent,
sRunQueues.set(rq);
return rq;
}
-
+
/**
* @hide
*/
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 428de67..d7457a0 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -24,7 +24,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
/**
* Abstract base class for a top-level window look and behavior policy. An
@@ -153,7 +153,16 @@ public abstract class Window {
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTrackballEvent(MotionEvent event);
-
+
+ /**
+ * Called to process population of {@link AccessibilityEvent}s.
+ *
+ * @param event The event.
+ *
+ * @return boolean Return true if event population was completed.
+ */
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
+
/**
* Instantiate the view to display in the panel for 'featureId'.
* You can return null, in which case the default content (typically
@@ -367,8 +376,14 @@ public abstract class Window {
String title;
if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
title="Media";
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
+ title="MediaOvr";
} else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
title="Panel";
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
+ title="SubPanel";
+ } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
+ title="AtchDlg";
} else {
title=Integer.toString(wp.type);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c69c281..bdb86d7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -18,7 +18,6 @@ package android.view;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -210,6 +209,15 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
/**
+ * Window type: window for showing overlays on top of media windows.
+ * These windows are displayed between TYPE_APPLICATION_MEDIA and the
+ * application window. They should be translucent to be useful. This
+ * is a big ugly hack so:
+ * @hide
+ */
+ public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;
+
+ /**
* End of types of sub-windows.
*/
public static final int LAST_SUB_WINDOW = 1999;
@@ -466,6 +474,21 @@ public interface WindowManager extends ViewManager {
*/
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
+ /** Window flag: special flag to let windows be shown when the screen
+ * is locked. This will let application windows take precedence over
+ * key guard or any other lock screens. Can be used with
+ * {@link #FLAG_KEEP_SCREEN_ON} to turn screen on and display windows
+ * directly before showing the key guard window
+ *
+ * {@hide} */
+ public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
+
+ /** Window flag: special flag to let a window ignore the compatibility scaling.
+ * This is used by SurfaceView to create a window that does not scale the content.
+ *
+ * {@hide} */
+ public static final int FLAG_NO_COMPATIBILITY_SCALING = 0x00100000;
+
/** Window flag: a special option intended for system dialogs. When
* this flag is set, the window will demand focus unconditionally when
* it is created.
@@ -787,6 +810,7 @@ public interface WindowManager extends ViewManager {
screenOrientation = in.readInt();
}
+ @SuppressWarnings({"PointlessBitwiseExpression"})
public static final int LAYOUT_CHANGED = 1<<0;
public static final int TYPE_CHANGED = 1<<1;
public static final int FLAGS_CHANGED = 1<<2;
@@ -800,6 +824,9 @@ public interface WindowManager extends ViewManager {
public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+ // internal buffer to backup/restore parameters under compatibility mode.
+ private int[] mCompatibilityParamsBackup = null;
+
public final int copyFrom(LayoutParams o) {
int changes = 0;
@@ -957,36 +984,45 @@ public interface WindowManager extends ViewManager {
/**
* Scale the layout params' coordinates and size.
- * Returns the original info as a backup so that the caller can
- * restore the layout params;
- */
- void scale(float scale, int[] backup) {
- if (scale != 1.0f) {
- backup[0] = x;
- backup[1] = y;
- x *= scale;
- y *= scale;
- if (width > 0) {
- backup[2] = width;
- width *= scale;
- }
- if (height > 0) {
- backup[3] = height;
- height *= scale;
- }
+ * @hide
+ */
+ public void scale(float scale) {
+ x *= scale;
+ y *= scale;
+ if (width > 0) {
+ width *= scale;
+ }
+ if (height > 0) {
+ height *= scale;
}
}
/**
- * Restore the layout params' coordinates and size.
- */
- void restore(int[] backup) {
- x = backup[0];
- y = backup[1];
- if (width > 0) {
- width = backup[2];
+ * Backup the layout parameters used in compatibility mode.
+ * @see LayoutParams#restore()
+ */
+ void backup() {
+ int[] backup = mCompatibilityParamsBackup;
+ if (backup == null) {
+ // we backup 4 elements, x, y, width, height
+ backup = mCompatibilityParamsBackup = new int[4];
}
- if (height > 0) {
+ backup[0] = x;
+ backup[1] = y;
+ backup[2] = width;
+ backup[3] = height;
+ }
+
+ /**
+ * Restore the layout params' coordinates, size and gravity
+ * @see LayoutParams#backup()
+ */
+ void restore() {
+ int[] backup = mCompatibilityParamsBackup;
+ if (backup != null) {
+ x = backup[0];
+ y = backup[1];
+ width = backup[2];
height = backup[3];
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 755d7b8..0973599 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -173,7 +173,6 @@ public class WindowManagerImpl implements WindowManager {
mRoots[index] = root;
mParams[index] = wparams;
}
-
// do this last because it fires off messages to start doing things
root.setView(view, wparams, panelParentView);
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.aidl b/core/java/android/view/accessibility/AccessibilityEvent.aidl
new file mode 100644
index 0000000..cee3604
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityEvent.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+parcelable AccessibilityEvent;
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
new file mode 100644
index 0000000..c22f991
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -0,0 +1,734 @@
+/*
+ * 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.view.accessibility;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents accessibility events that are sent by the system when
+ * something notable happens in the user interface. For example, when a
+ * {@link android.widget.Button} is clicked, a {@link android.view.View} is focused, etc.
+ * <p>
+ * This class represents various semantically different accessibility event
+ * types. Each event type has associated a set of related properties. In other
+ * words, each event type is characterized via a subset of the properties exposed
+ * by this class. For each event type there is a corresponding constant defined
+ * in this class. Since some event types are semantically close there are mask
+ * constants that group them together. Follows a specification of the event
+ * types and their associated properties:
+ * <p>
+ * <b>VIEW TYPES</b> <br>
+ * <p>
+ * <b>View clicked</b> - represents the event of clicking on a {@link android.view.View}
+ * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br>
+ * Type:{@link #TYPE_VIEW_CLICKED} <br>
+ * Properties:
+ * {@link #getClassName()},
+ * {@link #getPackageName()},
+ * {@link #getEventTime()},
+ * {@link #getText()},
+ * {@link #isChecked()},
+ * {@link #isEnabled()},
+ * {@link #isPassword()},
+ * {@link #getItemCount()},
+ * {@link #getCurrentItemIndex()}
+ * <p>
+ * <b>View long clicked</b> - represents the event of long clicking on a {@link android.view.View}
+ * like {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc. <br>
+ * Type:{@link #TYPE_VIEW_LONG_CLICKED} <br>
+ * Properties:
+ * {@link #getClassName()},
+ * {@link #getPackageName()},
+ * {@link #getEventTime()},
+ * {@link #getText()},
+ * {@link #isChecked()},
+ * {@link #isEnabled()},
+ * {@link #isPassword()},
+ * {@link #getItemCount()},
+ * {@link #getCurrentItemIndex()}
+ * <p>
+ * <b>View selected</b> - represents the event of selecting an item usually in
+ * the context of an {@link android.widget.AdapterView}. <br>
+ * Type: {@link #TYPE_VIEW_SELECTED} <br>
+ * Properties:
+ * {@link #getClassName()},
+ * {@link #getPackageName()},
+ * {@link #getEventTime()},
+ * {@link #getText()},
+ * {@link #isChecked()},
+ * {@link #isEnabled()},
+ * {@link #isPassword()},
+ * {@link #getItemCount()},
+ * {@link #getCurrentItemIndex()}
+ * <p>
+ * <b>View focused</b> - represents the event of focusing a
+ * {@link android.view.View}. <br>
+ * Type: {@link #TYPE_VIEW_FOCUSED} <br>
+ * Properties:
+ * {@link #getClassName()},
+ * {@link #getPackageName()},
+ * {@link #getEventTime()},
+ * {@link #getText()},
+ * {@link #isChecked()},
+ * {@link #isEnabled()},
+ * {@link #isPassword()},
+ * {@link #getItemCount()},
+ * {@link #getCurrentItemIndex()}
+ * <p>
+ * <b>View text changed</b> - represents the event of changing the text of an
+ * {@link android.widget.EditText}. <br>
+ * Type: {@link #TYPE_VIEW_TEXT_CHANGED} <br>
+ * Properties:
+ * {@link #getClassName()},
+ * {@link #getPackageName()},
+ * {@link #getEventTime()},
+ * {@link #getText()},
+ * {@link #isChecked()},
+ * {@link #isEnabled()},
+ * {@link #isPassword()},
+ * {@link #getItemCount()},
+ * {@link #getCurrentItemIndex()},
+ * {@link #getFromIndex()},
+ * {@link #getAddedCount()},
+ * {@link #getRemovedCount()},
+ * {@link #getBeforeText()}
+ * <p>
+ * <b>TRANSITION TYPES</b> <br>
+ * <p>
+ * <b>Window state changed</b> - represents the event of opening/closing a
+ * {@link android.widget.PopupWindow}, {@link android.view.Menu},
+ * {@link android.app.Dialog}, etc. <br>
+ * Type: {@link #TYPE_WINDOW_STATE_CHANGED} <br>
+ * Properties:
+ * {@link #getClassName()},
+ * {@link #getPackageName()},
+ * {@link #getEventTime()},
+ * {@link #getText()}
+ * <p>
+ * <b>NOTIFICATION TYPES</b> <br>
+ * <p>
+ * <b>Notification state changed</b> - represents the event showing/hiding
+ * {@link android.app.Notification}.
+ * Type: {@link #TYPE_NOTIFICATION_STATE_CHANGED} <br>
+ * Properties:
+ * {@link #getClassName()},
+ * {@link #getPackageName()},
+ * {@link #getEventTime()},
+ * {@link #getText()}
+ * {@link #getParcelableData()}
+ * <p>
+ * <b>Security note</b>
+ * <p>
+ * Since an event contains the text of its source privacy can be compromised by leaking of
+ * sensitive information such as passwords. To address this issue any event fired in response
+ * to manipulation of a PASSWORD field does NOT CONTAIN the text of the password.
+ *
+ * @see android.view.accessibility.AccessibilityManager
+ * @see android.accessibilityservice.AccessibilityService
+ */
+public final class AccessibilityEvent implements Parcelable {
+
+ /**
+ * Invalid selection/focus position.
+ *
+ * @see #getCurrentItemIndex()
+ */
+ public static final int INVALID_POSITION = -1;
+
+ /**
+ * Maximum length of the text fields.
+ *
+ * @see #getBeforeText()
+ * @see #getText()
+ */
+ public static final int MAX_TEXT_LENGTH = 500;
+
+ /**
+ * Represents the event of clicking on a {@link android.view.View} like
+ * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+ */
+ public static final int TYPE_VIEW_CLICKED = 0x00000001;
+
+ /**
+ * Represents the event of long clicking on a {@link android.view.View} like
+ * {@link android.widget.Button}, {@link android.widget.CompoundButton}, etc.
+ */
+ public static final int TYPE_VIEW_LONG_CLICKED = 0x00000002;
+
+ /**
+ * Represents the event of selecting an item usually in the context of an
+ * {@link android.widget.AdapterView}.
+ */
+ public static final int TYPE_VIEW_SELECTED = 0x00000004;
+
+ /**
+ * Represents the event of focusing a {@link android.view.View}.
+ */
+ public static final int TYPE_VIEW_FOCUSED = 0x00000008;
+
+ /**
+ * Represents the event of changing the text of an {@link android.widget.EditText}.
+ */
+ public static final int TYPE_VIEW_TEXT_CHANGED = 0x00000010;
+
+ /**
+ * Represents the event of opening/closing a {@link android.widget.PopupWindow},
+ * {@link android.view.Menu}, {@link android.app.Dialog}, etc.
+ */
+ public static final int TYPE_WINDOW_STATE_CHANGED = 0x00000020;
+
+ /**
+ * Represents the event showing/hiding a {@link android.app.Notification}.
+ */
+ public static final int TYPE_NOTIFICATION_STATE_CHANGED = 0x00000040;
+
+ /**
+ * Mask for {@link AccessibilityEvent} all types.
+ *
+ * @see #TYPE_VIEW_CLICKED
+ * @see #TYPE_VIEW_LONG_CLICKED
+ * @see #TYPE_VIEW_SELECTED
+ * @see #TYPE_VIEW_FOCUSED
+ * @see #TYPE_VIEW_TEXT_CHANGED
+ * @see #TYPE_WINDOW_STATE_CHANGED
+ * @see #TYPE_NOTIFICATION_STATE_CHANGED
+ */
+ public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
+
+ private static final int MAX_POOL_SIZE = 2;
+ private static final Object mPoolLock = new Object();
+ private static AccessibilityEvent sPool;
+ private static int sPoolSize;
+
+ private static final int CHECKED = 0x00000001;
+ private static final int ENABLED = 0x00000002;
+ private static final int PASSWORD = 0x00000004;
+ private static final int FULL_SCREEN = 0x00000080;
+
+ private AccessibilityEvent mNext;
+
+ private int mEventType;
+ private int mBooleanProperties;
+ private int mCurrentItemIndex;
+ private int mItemCount;
+ private int mFromIndex;
+ private int mAddedCount;
+ private int mRemovedCount;
+
+ private long mEventTime;
+
+ private CharSequence mClassName;
+ private CharSequence mPackageName;
+ private CharSequence mContentDescription;
+ private CharSequence mBeforeText;
+
+ private Parcelable mParcelableData;
+
+ private final List<CharSequence> mText = new ArrayList<CharSequence>();
+
+ private boolean mIsInPool;
+
+ /*
+ * Hide constructor from clients.
+ */
+ private AccessibilityEvent() {
+ mCurrentItemIndex = INVALID_POSITION;
+ }
+
+ /**
+ * Gets if the source is checked.
+ *
+ * @return True if the view is checked, false otherwise.
+ */
+ public boolean isChecked() {
+ return getBooleanProperty(CHECKED);
+ }
+
+ /**
+ * Sets if the source is checked.
+ *
+ * @param isChecked True if the view is checked, false otherwise.
+ */
+ public void setChecked(boolean isChecked) {
+ setBooleanProperty(CHECKED, isChecked);
+ }
+
+ /**
+ * Gets if the source is enabled.
+ *
+ * @return True if the view is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ return getBooleanProperty(ENABLED);
+ }
+
+ /**
+ * Sets if the source is enabled.
+ *
+ * @param isEnabled True if the view is enabled, false otherwise.
+ */
+ public void setEnabled(boolean isEnabled) {
+ setBooleanProperty(ENABLED, isEnabled);
+ }
+
+ /**
+ * Gets if the source is a password field.
+ *
+ * @return True if the view is a password field, false otherwise.
+ */
+ public boolean isPassword() {
+ return getBooleanProperty(PASSWORD);
+ }
+
+ /**
+ * Sets if the source is a password field.
+ *
+ * @param isPassword True if the view is a password field, false otherwise.
+ */
+ public void setPassword(boolean isPassword) {
+ setBooleanProperty(PASSWORD, isPassword);
+ }
+
+ /**
+ * Sets if the source is taking the entire screen.
+ *
+ * @param isFullScreen True if the source is full screen, false otherwise.
+ */
+ public void setFullScreen(boolean isFullScreen) {
+ setBooleanProperty(FULL_SCREEN, isFullScreen);
+ }
+
+ /**
+ * Gets if the source is taking the entire screen.
+ *
+ * @return True if the source is full screen, false otherwise.
+ */
+ public boolean isFullScreen() {
+ return getBooleanProperty(FULL_SCREEN);
+ }
+
+ /**
+ * Gets the event type.
+ *
+ * @return The event type.
+ */
+ public int getEventType() {
+ return mEventType;
+ }
+
+ /**
+ * Sets the event type.
+ *
+ * @param eventType The event type.
+ */
+ public void setEventType(int eventType) {
+ mEventType = eventType;
+ }
+
+ /**
+ * Gets the number of items that can be visited.
+ *
+ * @return The number of items.
+ */
+ public int getItemCount() {
+ return mItemCount;
+ }
+
+ /**
+ * Sets the number of items that can be visited.
+ *
+ * @param itemCount The number of items.
+ */
+ public void setItemCount(int itemCount) {
+ mItemCount = itemCount;
+ }
+
+ /**
+ * Gets the index of the source in the list of items the can be visited.
+ *
+ * @return The current item index.
+ */
+ public int getCurrentItemIndex() {
+ return mCurrentItemIndex;
+ }
+
+ /**
+ * Sets the index of the source in the list of items that can be visited.
+ *
+ * @param currentItemIndex The current item index.
+ */
+ public void setCurrentItemIndex(int currentItemIndex) {
+ mCurrentItemIndex = currentItemIndex;
+ }
+
+ /**
+ * Gets the index of the first character of the changed sequence.
+ *
+ * @return The index of the first character.
+ */
+ public int getFromIndex() {
+ return mFromIndex;
+ }
+
+ /**
+ * Sets the index of the first character of the changed sequence.
+ *
+ * @param fromIndex The index of the first character.
+ */
+ public void setFromIndex(int fromIndex) {
+ mFromIndex = fromIndex;
+ }
+
+ /**
+ * Gets the number of added characters.
+ *
+ * @return The number of added characters.
+ */
+ public int getAddedCount() {
+ return mAddedCount;
+ }
+
+ /**
+ * Sets the number of added characters.
+ *
+ * @param addedCount The number of added characters.
+ */
+ public void setAddedCount(int addedCount) {
+ mAddedCount = addedCount;
+ }
+
+ /**
+ * Gets the number of removed characters.
+ *
+ * @return The number of removed characters.
+ */
+ public int getRemovedCount() {
+ return mRemovedCount;
+ }
+
+ /**
+ * Sets the number of removed characters.
+ *
+ * @param removedCount The number of removed characters.
+ */
+ public void setRemovedCount(int removedCount) {
+ mRemovedCount = removedCount;
+ }
+
+ /**
+ * Gets the time in which this event was sent.
+ *
+ * @return The event time.
+ */
+ public long getEventTime() {
+ return mEventTime;
+ }
+
+ /**
+ * Sets the time in which this event was sent.
+ *
+ * @param eventTime The event time.
+ */
+ public void setEventTime(long eventTime) {
+ mEventTime = eventTime;
+ }
+
+ /**
+ * Gets the class name of the source.
+ *
+ * @return The class name.
+ */
+ public CharSequence getClassName() {
+ return mClassName;
+ }
+
+ /**
+ * Sets the class name of the source.
+ *
+ * @param className The lass name.
+ */
+ public void setClassName(CharSequence className) {
+ mClassName = className;
+ }
+
+ /**
+ * Gets the package name of the source.
+ *
+ * @return The package name.
+ */
+ public CharSequence getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Sets the package name of the source.
+ *
+ * @param packageName The package name.
+ */
+ public void setPackageName(CharSequence packageName) {
+ mPackageName = packageName;
+ }
+
+ /**
+ * Gets the text of the event. The index in the list represents the priority
+ * of the text. Specifically, the lower the index the higher the priority.
+ *
+ * @return The text.
+ */
+ public List<CharSequence> getText() {
+ return mText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @return The text before the change.
+ */
+ public CharSequence getBeforeText() {
+ return mBeforeText;
+ }
+
+ /**
+ * Sets the text before a change.
+ *
+ * @param beforeText The text before the change.
+ */
+ public void setBeforeText(CharSequence beforeText) {
+ mBeforeText = beforeText;
+ }
+
+ /**
+ * Gets the description of the source.
+ *
+ * @return The description.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the description of the source.
+ *
+ * @param contentDescription The description.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ }
+
+ /**
+ * Gets the {@link Parcelable} data.
+ *
+ * @return The parcelable data.
+ */
+ public Parcelable getParcelableData() {
+ return mParcelableData;
+ }
+
+ /**
+ * Sets the {@link Parcelable} data of the event.
+ *
+ * @param parcelableData The parcelable data.
+ */
+ public void setParcelableData(Parcelable parcelableData) {
+ mParcelableData = parcelableData;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated with type property set.
+ *
+ * @param eventType The event type.
+ * @return An instance.
+ */
+ public static AccessibilityEvent obtain(int eventType) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.setEventType(eventType);
+ return event;
+ }
+
+ /**
+ * Returns a cached instance if such is available or a new one is
+ * instantiated.
+ *
+ * @return An instance.
+ */
+ public static AccessibilityEvent obtain() {
+ synchronized (mPoolLock) {
+ if (sPool != null) {
+ AccessibilityEvent event = sPool;
+ sPool = sPool.mNext;
+ sPoolSize--;
+ event.mNext = null;
+ event.mIsInPool = false;
+ return event;
+ }
+ return new AccessibilityEvent();
+ }
+ }
+
+ /**
+ * Return an instance back to be reused.
+ * <p>
+ * <b>Note: You must not touch the object after calling this function.</b>
+ */
+ public void recycle() {
+ if (mIsInPool) {
+ return;
+ }
+
+ clear();
+ synchronized (mPoolLock) {
+ if (sPoolSize <= MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ mIsInPool = true;
+ sPoolSize++;
+ }
+ }
+ }
+
+ /**
+ * Clears the state of this instance.
+ */
+ private void clear() {
+ mEventType = 0;
+ mBooleanProperties = 0;
+ mCurrentItemIndex = INVALID_POSITION;
+ mItemCount = 0;
+ mFromIndex = 0;
+ mAddedCount = 0;
+ mRemovedCount = 0;
+ mEventTime = 0;
+ mClassName = null;
+ mPackageName = null;
+ mContentDescription = null;
+ mBeforeText = null;
+ mText.clear();
+ }
+
+ /**
+ * Gets the value of a boolean property.
+ *
+ * @param property The property.
+ * @return The value.
+ */
+ private boolean getBooleanProperty(int property) {
+ return (mBooleanProperties & property) == property;
+ }
+
+ /**
+ * Sets a boolean property.
+ *
+ * @param property The property.
+ * @param value The value.
+ */
+ private void setBooleanProperty(int property, boolean value) {
+ if (value) {
+ mBooleanProperties |= property;
+ } else {
+ mBooleanProperties &= ~property;
+ }
+ }
+
+ /**
+ * Creates a new instance from a {@link Parcel}.
+ *
+ * @param parcel A parcel containing the state of a {@link AccessibilityEvent}.
+ */
+ public void initFromParcel(Parcel parcel) {
+ mEventType = parcel.readInt();
+ mBooleanProperties = parcel.readInt();
+ mCurrentItemIndex = parcel.readInt();
+ mItemCount = parcel.readInt();
+ mFromIndex = parcel.readInt();
+ mAddedCount = parcel.readInt();
+ mRemovedCount = parcel.readInt();
+ mEventTime = parcel.readLong();
+ mClassName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mPackageName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mBeforeText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
+ mParcelableData = parcel.readParcelable(null);
+ parcel.readList(mText, null);
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mEventType);
+ parcel.writeInt(mBooleanProperties);
+ parcel.writeInt(mCurrentItemIndex);
+ parcel.writeInt(mItemCount);
+ parcel.writeInt(mFromIndex);
+ parcel.writeInt(mAddedCount);
+ parcel.writeInt(mRemovedCount);
+ parcel.writeLong(mEventTime);
+ TextUtils.writeToParcel(mClassName, parcel, 0);
+ TextUtils.writeToParcel(mPackageName, parcel, 0);
+ TextUtils.writeToParcel(mContentDescription, parcel, 0);
+ TextUtils.writeToParcel(mBeforeText, parcel, 0);
+ parcel.writeParcelable(mParcelableData, flags);
+ parcel.writeList(mText);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(super.toString());
+ builder.append("; EventType: " + mEventType);
+ builder.append("; EventTime: " + mEventTime);
+ builder.append("; ClassName: " + mClassName);
+ builder.append("; PackageName: " + mPackageName);
+ builder.append("; Text: " + mText);
+ builder.append("; ContentDescription: " + mContentDescription);
+ builder.append("; ItemCount: " + mItemCount);
+ builder.append("; CurrentItemIndex: " + mCurrentItemIndex);
+ builder.append("; IsEnabled: " + isEnabled());
+ builder.append("; IsPassword: " + isPassword());
+ builder.append("; IsChecked: " + isChecked());
+ builder.append("; IsFullScreen: " + isFullScreen());
+ builder.append("; BeforeText: " + mBeforeText);
+ builder.append("; FromIndex: " + mFromIndex);
+ builder.append("; AddedCount: " + mAddedCount);
+ builder.append("; RemovedCount: " + mRemovedCount);
+ builder.append("; ParcelableData: " + mParcelableData);
+ return builder.toString();
+ }
+
+ /**
+ * @see Parcelable.Creator
+ */
+ public static final Parcelable.Creator<AccessibilityEvent> CREATOR =
+ new Parcelable.Creator<AccessibilityEvent>() {
+ public AccessibilityEvent createFromParcel(Parcel parcel) {
+ AccessibilityEvent event = AccessibilityEvent.obtain();
+ event.initFromParcel(parcel);
+ return event;
+ }
+
+ public AccessibilityEvent[] newArray(int size) {
+ return new AccessibilityEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/view/accessibility/AccessibilityEventSource.java b/core/java/android/view/accessibility/AccessibilityEventSource.java
new file mode 100644
index 0000000..3d70959
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityEventSource.java
@@ -0,0 +1,52 @@
+/*
+ * 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.view.accessibility;
+
+/**
+ * This interface is implemented by classes source of {@link AccessibilityEvent}s.
+ */
+public interface AccessibilityEventSource {
+
+ /**
+ * Handles the request for sending an {@link AccessibilityEvent} given
+ * the event type. The method must first check if accessibility is on
+ * via calling {@link AccessibilityManager#isEnabled()}, obtain
+ * an {@link AccessibilityEvent} from the event pool through calling
+ * {@link AccessibilityEvent#obtain(int)}, populate the event, and
+ * send it for dispatch via calling
+ * {@link AccessibilityManager#sendAccessibilityEvent(AccessibilityEvent)}.
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ *
+ * @param eventType The event type.
+ */
+ public void sendAccessibilityEvent(int eventType);
+
+ /**
+ * Handles the request for sending an {@link AccessibilityEvent}. The
+ * method does not guarantee to check if accessibility is on before
+ * sending the event for dispatch. It is responsibility of the caller
+ * to do the check via calling {@link AccessibilityManager#isEnabled()}.
+ *
+ * @see AccessibilityEvent
+ * @see AccessibilityManager
+ *
+ * @param event The event.
+ */
+ public void sendAccessibilityEventUnchecked(AccessibilityEvent event);
+}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
new file mode 100644
index 0000000..0186270
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -0,0 +1,198 @@
+/*
+ * 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.view.accessibility;
+
+import static android.util.Config.LOGV;
+
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * System level service that serves as an event dispatch for {@link AccessibilityEvent}s.
+ * Such events are generated when something notable happens in the user interface,
+ * for example an {@link android.app.Activity} starts, the focus or selection of a
+ * {@link android.view.View} changes etc. Parties interested in handling accessibility
+ * events implement and register an accessibility service which extends
+ * {@link android.accessibilityservice.AccessibilityService}.
+ *
+ * @see AccessibilityEvent
+ * @see android.accessibilityservice.AccessibilityService
+ * @see android.content.Context#getSystemService
+ */
+public final class AccessibilityManager {
+ private static final String LOG_TAG = "AccessibilityManager";
+
+ static final Object sInstanceSync = new Object();
+
+ private static AccessibilityManager sInstance;
+
+ private static final int DO_SET_ENABLED = 10;
+
+ final IAccessibilityManager mService;
+
+ final Handler mHandler;
+
+ boolean mIsEnabled;
+
+ final IAccessibilityManagerClient.Stub mClient = new IAccessibilityManagerClient.Stub() {
+ public void setEnabled(boolean enabled) {
+ mHandler.obtainMessage(DO_SET_ENABLED, enabled ? 1 : 0, 0).sendToTarget();
+ }
+ };
+
+ class MyHandler extends Handler {
+
+ MyHandler(Looper mainLooper) {
+ super(mainLooper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case DO_SET_ENABLED :
+ synchronized (mHandler) {
+ mIsEnabled = (message.arg1 == 1);
+ }
+ return;
+ default :
+ Log.w(LOG_TAG, "Unknown message type: " + message.what);
+ }
+ }
+ }
+
+ /**
+ * Get an AccessibilityManager instance (create one if necessary).
+ *
+ * @hide
+ */
+ public static AccessibilityManager getInstance(Context context) {
+ synchronized (sInstanceSync) {
+ if (sInstance == null) {
+ sInstance = new AccessibilityManager(context);
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Create an instance.
+ *
+ * @param context A {@link Context}.
+ */
+ private AccessibilityManager(Context context) {
+ mHandler = new MyHandler(context.getMainLooper());
+ IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
+ mService = IAccessibilityManager.Stub.asInterface(iBinder);
+ try {
+ mService.addClient(mClient);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
+ }
+ }
+
+ /**
+ * Returns if the {@link AccessibilityManager} is enabled.
+ *
+ * @return True if this {@link AccessibilityManager} is enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ synchronized (mHandler) {
+ return mIsEnabled;
+ }
+ }
+
+ /**
+ * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not
+ * enabled the call is a NOOP.
+ *
+ * @param event The {@link AccessibilityEvent}.
+ *
+ * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent}
+ * while accessibility is not enabled.
+ */
+ public void sendAccessibilityEvent(AccessibilityEvent event) {
+ if (!mIsEnabled) {
+ throw new IllegalStateException("Accessibility off. Did you forget to check that?");
+ }
+ boolean doRecycle = false;
+ try {
+ event.setEventTime(SystemClock.uptimeMillis());
+ // it is possible that this manager is in the same process as the service but
+ // client using it is called through Binder from another process. Example: MMS
+ // app adds a SMS notification and the NotificationManagerService calls this method
+ long identityToken = Binder.clearCallingIdentity();
+ doRecycle = mService.sendAccessibilityEvent(event);
+ Binder.restoreCallingIdentity(identityToken);
+ if (LOGV) {
+ Log.i(LOG_TAG, event + " sent");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error during sending " + event + " ", re);
+ } finally {
+ if (doRecycle) {
+ event.recycle();
+ }
+ }
+ }
+
+ /**
+ * Requests interruption of the accessibility feedback from all accessibility services.
+ */
+ public void interrupt() {
+ if (!mIsEnabled) {
+ throw new IllegalStateException("Accessibility off. Did you forget to check that?");
+ }
+ try {
+ mService.interrupt();
+ if (LOGV) {
+ Log.i(LOG_TAG, "Requested interrupt from all services");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while requesting interrupt from all services. ", re);
+ }
+ }
+
+ /**
+ * Returns the {@link ServiceInfo}s of the installed accessibility services.
+ *
+ * @return An unmodifiable list with {@link ServiceInfo}s.
+ */
+ public List<ServiceInfo> getAccessibilityServiceList() {
+ List<ServiceInfo> services = null;
+ try {
+ services = mService.getAccessibilityServiceList();
+ if (LOGV) {
+ Log.i(LOG_TAG, "Installed AccessibilityServices " + services);
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re);
+ }
+ return Collections.unmodifiableList(services);
+ }
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
new file mode 100644
index 0000000..32788be
--- /dev/null
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -0,0 +1,39 @@
+/* //device/java/android/android/app/INotificationManager.aidl
+**
+** Copyright 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.view.accessibility;
+
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.IAccessibilityManagerClient;
+import android.content.pm.ServiceInfo;
+
+/**
+ * Interface implemented by the AccessibilityManagerService called by
+ * the AccessibilityMasngers.
+ *
+ * @hide
+ */
+interface IAccessibilityManager {
+
+ void addClient(IAccessibilityManagerClient client);
+
+ boolean sendAccessibilityEvent(in AccessibilityEvent uiEvent);
+
+ List<ServiceInfo> getAccessibilityServiceList();
+
+ void interrupt();
+}
diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
new file mode 100644
index 0000000..1eb60fc
--- /dev/null
+++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.view.accessibility;
+
+/**
+ * Interface a client of the IAccessibilityManager implements to
+ * receive information about changes in the manager state.
+ *
+ * @hide
+ */
+oneway interface IAccessibilityManagerClient {
+
+ void setEnabled(boolean enabled);
+
+}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index 11de3e2..7393737 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -297,6 +297,10 @@ public class BaseInputConnection implements InputConnection {
b = tmp;
}
+ if (a <= 0) {
+ return "";
+ }
+
if (length > a) {
length = a;
}
@@ -336,10 +340,19 @@ public class BaseInputConnection implements InputConnection {
}
/**
- * The default implementation does nothing.
+ * The default implementation turns this into the enter key.
*/
public boolean performEditorAction(int actionCode) {
- return false;
+ long eventTime = SystemClock.uptimeMillis();
+ sendKeyEvent(new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+ | KeyEvent.FLAG_EDITOR_ACTION));
+ sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
+ | KeyEvent.FLAG_EDITOR_ACTION));
+ return true;
}
/**
@@ -488,12 +501,12 @@ public class BaseInputConnection implements InputConnection {
} else {
a = Selection.getSelectionStart(content);
b = Selection.getSelectionEnd(content);
- if (a >=0 && b>= 0 && a != b) {
- if (b < a) {
- int tmp = a;
- a = b;
- b = tmp;
- }
+ if (a < 0) a = 0;
+ if (b < 0) b = 0;
+ if (b < a) {
+ int tmp = a;
+ a = b;
+ b = tmp;
}
}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index ba3f78c..dbd2682 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -143,6 +143,17 @@ class BrowserFrame extends Handler {
}
/**
+ * Load a url with "POST" method from the network into the main frame.
+ * @param url The url to load.
+ * @param data The data for POST request.
+ */
+ public void postUrl(String url, byte[] data) {
+ mLoadInitFromJava = true;
+ nativePostUrl(url, data);
+ mLoadInitFromJava = false;
+ }
+
+ /**
* Load the content as if it was loaded by the provided base URL. The
* failUrl is used as the history entry for the load data. If null or
* an empty string is passed for the failUrl, then no history entry is
@@ -752,6 +763,8 @@ class BrowserFrame extends Handler {
*/
private native void nativeLoadUrl(String url);
+ private native void nativePostUrl(String url, byte[] postData);
+
private native void nativeLoadData(String baseUrl, String data,
String mimeType, String encoding, String failUrl);
diff --git a/core/java/android/webkit/ByteArrayBuilder.java b/core/java/android/webkit/ByteArrayBuilder.java
index 806b458..145411c 100644
--- a/core/java/android/webkit/ByteArrayBuilder.java
+++ b/core/java/android/webkit/ByteArrayBuilder.java
@@ -17,6 +17,7 @@
package android.webkit;
import java.util.LinkedList;
+import java.util.ListIterator;
/** Utility class optimized for accumulating bytes, and then spitting
them back out. It does not optimize for returning the result in a
@@ -94,6 +95,20 @@ class ByteArrayBuilder {
return mChunks.isEmpty();
}
+ public int size() {
+ return mChunks.size();
+ }
+
+ public int getByteSize() {
+ int total = 0;
+ ListIterator<Chunk> it = mChunks.listIterator(0);
+ while (it.hasNext()) {
+ Chunk c = it.next();
+ total += c.mLength;
+ }
+ return total;
+ }
+
public synchronized void clear() {
Chunk c = getFirstChunk();
while (c != null) {
diff --git a/core/java/android/webkit/FrameLoader.java b/core/java/android/webkit/FrameLoader.java
index 6f1b160..66ab021 100644
--- a/core/java/android/webkit/FrameLoader.java
+++ b/core/java/android/webkit/FrameLoader.java
@@ -364,7 +364,7 @@ class FrameLoader {
String cookie = CookieManager.getInstance().getCookie(
mListener.getWebAddress());
if (cookie != null && cookie.length() > 0) {
- mHeaders.put("cookie", cookie);
+ mHeaders.put("Cookie", cookie);
}
}
}
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 2a84683..1dbd007 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -18,6 +18,7 @@ package android.webkit;
import android.os.Handler;
import android.os.Message;
+import android.security.CertTool;
import android.util.Log;
final class JWebCoreJavaBridge extends Handler {
@@ -186,6 +187,15 @@ final class JWebCoreJavaBridge extends Handler {
mHasInstantTimer = false;
}
+ private String[] getKeyStrengthList() {
+ return CertTool.getInstance().getSupportedKeyStrenghs();
+ }
+
+ private String getSignedPublicKey(int index, String challenge, String url) {
+ // generateKeyPair expects organizations which we don't have. Ignore url.
+ return CertTool.getInstance().generateKeyPair(index, challenge, null);
+ }
+
private native void nativeConstructor();
private native void nativeFinalize();
private native void sharedTimerFired();
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index d583eb1..39360cd 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -25,16 +25,16 @@ import android.net.http.HttpAuthHeader;
import android.net.http.RequestHandle;
import android.net.http.SslCertificate;
import android.net.http.SslError;
-import android.net.http.SslCertificate;
import android.os.Handler;
import android.os.Message;
+import android.security.CertTool;
import android.util.Log;
import android.webkit.CacheManager.CacheResult;
+import android.widget.Toast;
import com.android.internal.R;
-import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -72,6 +72,8 @@ class LoadListener extends Handler implements EventHandler {
private static final int HTTP_NOT_FOUND = 404;
private static final int HTTP_PROXY_AUTH = 407;
+ private static final String CERT_MIMETYPE = "application/x-x509-ca-cert";
+
private static int sNativeLoaderCount;
private final ByteArrayBuilder mDataBuilder = new ByteArrayBuilder(8192);
@@ -934,6 +936,12 @@ class LoadListener extends Handler implements EventHandler {
// This commits the headers without checking the response status code.
private void commitHeaders() {
+ if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) {
+ // In the case of downloading certificate, we will save it to the
+ // Keystore in commitLoad. Do not call webcore.
+ return;
+ }
+
// Commit the headers to WebCore
int nativeResponse = createNativeResponse();
// The native code deletes the native response object.
@@ -974,6 +982,30 @@ class LoadListener extends Handler implements EventHandler {
private void commitLoad() {
if (mCancelled) return;
+ if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) {
+ // In the case of downloading certificate, we will save it to the
+ // Keystore and stop the current loading so that it will not
+ // generate a new history page
+ byte[] cert = new byte[mDataBuilder.getByteSize()];
+ int position = 0;
+ ByteArrayBuilder.Chunk c;
+ while (true) {
+ c = mDataBuilder.getFirstChunk();
+ if (c == null) break;
+
+ if (c.mLength != 0) {
+ System.arraycopy(c.mArray, 0, cert, position, c.mLength);
+ position += c.mLength;
+ }
+ mDataBuilder.releaseChunk(c);
+ }
+ CertTool.getInstance().addCertificate(cert, mContext);
+ Toast.makeText(mContext, R.string.certificateSaved,
+ Toast.LENGTH_SHORT).show();
+ mBrowserFrame.stopLoading();
+ return;
+ }
+
// Give the data to WebKit now
PerfChecker checker = new PerfChecker();
ByteArrayBuilder.Chunk c;
diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java
index 9de97c9..99de56d 100644
--- a/core/java/android/webkit/TextDialog.java
+++ b/core/java/android/webkit/TextDialog.java
@@ -538,7 +538,8 @@ import java.util.ArrayList;
* removing the password input type.
*/
public void setSingleLine(boolean single) {
- int inputType = EditorInfo.TYPE_CLASS_TEXT;
+ int inputType = EditorInfo.TYPE_CLASS_TEXT
+ | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
if (!single) {
inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
| EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 105eacd..ec671d5 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -69,7 +69,24 @@ public class WebSettings {
}
int value;
}
-
+
+ /**
+ * Enum for specifying the WebView's desired density.
+ * FAR makes 100% looking like in 240dpi
+ * MEDIUM makes 100% looking like in 160dpi
+ * CLOSE makes 100% looking like in 120dpi
+ * @hide Pending API council approval
+ */
+ public enum ZoomDensity {
+ FAR(150), // 240dpi
+ MEDIUM(100), // 160dpi
+ CLOSE(75); // 120dpi
+ ZoomDensity(int size) {
+ value = size;
+ }
+ int value;
+ }
+
/**
* Default cache usage pattern Use with {@link #setCacheMode}.
*/
@@ -105,6 +122,8 @@ public class WebSettings {
LOW
}
+ // WebView associated with this WebSettings.
+ private WebView mWebView;
// BrowserFrame used to access the native frame pointer.
private BrowserFrame mBrowserFrame;
// Flag to prevent multiple SYNC messages at one time.
@@ -123,7 +142,7 @@ public class WebSettings {
private String mSerifFontFamily = "serif";
private String mCursiveFontFamily = "cursive";
private String mFantasyFontFamily = "fantasy";
- private String mDefaultTextEncoding = "Latin-1";
+ private String mDefaultTextEncoding;
private String mUserAgent;
private boolean mUseDefaultUserAgent;
private String mAcceptLanguage;
@@ -145,6 +164,7 @@ public class WebSettings {
// Don't need to synchronize the get/set methods as they
// are basic types, also none of these values are used in
// native WebCore code.
+ private ZoomDensity mDefaultZoom = ZoomDensity.MEDIUM;
private RenderPriority mRenderPriority = RenderPriority.NORMAL;
private int mOverrideCacheMode = LOAD_DEFAULT;
private boolean mSaveFormData = true;
@@ -237,9 +257,12 @@ public class WebSettings {
* Package constructor to prevent clients from creating a new settings
* instance.
*/
- WebSettings(Context context) {
+ WebSettings(Context context, WebView webview) {
mEventHandler = new EventHandler();
mContext = context;
+ mWebView = webview;
+ mDefaultTextEncoding = context.getString(com.android.internal.
+ R.string.default_text_encoding);
if (sLockForLocaleSettings == null) {
sLockForLocaleSettings = new Object();
@@ -445,6 +468,31 @@ public class WebSettings {
}
/**
+ * Set the default zoom density of the page. This should be called from UI
+ * thread.
+ * @param zoom A ZoomDensity value
+ * @see WebSettings.ZoomDensity
+ * @hide Pending API council approval
+ */
+ public void setDefaultZoom(ZoomDensity zoom) {
+ if (mDefaultZoom != zoom) {
+ mDefaultZoom = zoom;
+ mWebView.updateDefaultZoomDensity(zoom.value);
+ }
+ }
+
+ /**
+ * Get the default zoom density of the page. This should be called from UI
+ * thread.
+ * @return A ZoomDensity value
+ * @see WebSettings.ZoomDensity
+ * @hide Pending API council approval
+ */
+ public ZoomDensity getDefaultZoom() {
+ return mDefaultZoom;
+ }
+
+ /**
* Enables using light touches to make a selection and activate mouseovers.
*/
public void setLightTouchEnabled(boolean enabled) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 563d819..fcf946f 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -237,6 +237,7 @@ public class WebView extends AbsoluteLayout
* Helper class to get velocity for fling
*/
VelocityTracker mVelocityTracker;
+ private int mMaximumFling;
/**
* Touch mode
@@ -395,22 +396,27 @@ public class WebView extends AbsoluteLayout
// width which view is considered to be fully zoomed out
static final int ZOOM_OUT_WIDTH = 1008;
- private static final float DEFAULT_MAX_ZOOM_SCALE = 4.0f;
- private static final float DEFAULT_MIN_ZOOM_SCALE = 0.25f;
+ // default scale limit. Depending on the display density
+ private static float DEFAULT_MAX_ZOOM_SCALE;
+ private static float DEFAULT_MIN_ZOOM_SCALE;
// scale limit, which can be set through viewport meta tag in the web page
- private float mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
- private float mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+ private float mMaxZoomScale;
+ private float mMinZoomScale;
private boolean mMinZoomScaleFixed = false;
// initial scale in percent. 0 means using default.
private int mInitialScale = 0;
+ // default scale. Depending on the display density.
+ static int DEFAULT_SCALE_PERCENT;
+ private float mDefaultScale;
+
// set to true temporarily while the zoom control is being dragged
private boolean mPreviewZoomOnly = false;
// computed scale and inverse, from mZoomWidth.
- private float mActualScale = 1;
- private float mInvActualScale = 1;
+ private float mActualScale;
+ private float mInvActualScale;
// if this is non-zero, it is used on drawing rather than mActualScale
private float mZoomScale;
private float mInvInitialZoomScale;
@@ -635,7 +641,7 @@ public class WebView extends AbsoluteLayout
mZoomFitPageButton.setOnClickListener(
new View.OnClickListener() {
public void onClick(View v) {
- zoomWithPreview(1f);
+ zoomWithPreview(mDefaultScale);
updateZoomButtonsEnabled();
}
});
@@ -658,7 +664,7 @@ public class WebView extends AbsoluteLayout
// or out.
mZoomButtonsController.setZoomInEnabled(canZoomIn);
mZoomButtonsController.setZoomOutEnabled(canZoomOut);
- mZoomFitPageButton.setEnabled(mActualScale != 1);
+ mZoomFitPageButton.setEnabled(mActualScale != mDefaultScale);
}
mZoomOverviewButton.setVisibility(canZoomScrollOut() ? View.VISIBLE:
View.GONE);
@@ -671,13 +677,41 @@ public class WebView extends AbsoluteLayout
setClickable(true);
setLongClickable(true);
- final int slop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+ final int slop = configuration.getScaledTouchSlop();
mTouchSlopSquare = slop * slop;
mMinLockSnapReverseDistance = slop;
+ final float density = getContext().getResources().getDisplayMetrics().density;
// use one line height, 16 based on our current default font, for how
// far we allow a touch be away from the edge of a link
- mNavSlop = (int) (16 * getContext().getResources()
- .getDisplayMetrics().density);
+ mNavSlop = (int) (16 * density);
+ // density adjusted scale factors
+ DEFAULT_SCALE_PERCENT = (int) (100 * density);
+ mDefaultScale = density;
+ mActualScale = density;
+ mInvActualScale = 1 / density;
+ DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
+ DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
+ mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
+ mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+ mMaximumFling = configuration.getScaledMaximumFlingVelocity();
+ }
+
+ /* package */void updateDefaultZoomDensity(int zoomDensity) {
+ final float density = getContext().getResources().getDisplayMetrics().density
+ * 100 / zoomDensity;
+ if (Math.abs(density - mDefaultScale) > 0.01) {
+ float scaleFactor = density / mDefaultScale;
+ // adjust the limits
+ mNavSlop = (int) (16 * density);
+ DEFAULT_SCALE_PERCENT = (int) (100 * density);
+ DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
+ DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
+ mDefaultScale = density;
+ mMaxZoomScale *= scaleFactor;
+ mMinZoomScale *= scaleFactor;
+ setNewZoomScale(mActualScale * scaleFactor, false);
+ }
}
/* package */ boolean onSavePassword(String schemePlusHost, String username,
@@ -1118,6 +1152,29 @@ public class WebView extends AbsoluteLayout
}
/**
+ * Load the url with postData using "POST" method into the WebView. If url
+ * is not a network url, it will be loaded with {link
+ * {@link #loadUrl(String)} instead.
+ *
+ * @param url The url of the resource to load.
+ * @param postData The data will be passed to "POST" request.
+ *
+ * @hide pending API solidification
+ */
+ public void postUrl(String url, byte[] postData) {
+ if (URLUtil.isNetworkUrl(url)) {
+ switchOutDrawHistory();
+ HashMap arg = new HashMap();
+ arg.put("url", url);
+ arg.put("data", postData);
+ mWebViewCore.sendMessage(EventHub.POST_URL, arg);
+ clearTextEntry();
+ } else {
+ loadUrl(url);
+ }
+ }
+
+ /**
* Load the given data into the WebView. This will load the data into
* WebView using the data: scheme. Content loaded through this mechanism
* does not have the ability to load content from the network.
@@ -4103,7 +4160,7 @@ public class WebView extends AbsoluteLayout
int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
int maxY = Math.max(computeVerticalScrollRange() - getViewHeight(), 0);
- mVelocityTracker.computeCurrentVelocity(1000);
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling);
int vx = (int) mVelocityTracker.getXVelocity();
int vy = (int) mVelocityTracker.getYVelocity();
@@ -4134,9 +4191,9 @@ public class WebView extends AbsoluteLayout
private boolean zoomWithPreview(float scale) {
float oldScale = mActualScale;
- // snap to 100% if it is close
- if (scale > 0.95f && scale < 1.05f) {
- scale = 1.0f;
+ // snap to DEFAULT_SCALE if it is close
+ if (scale > (mDefaultScale - 0.05) && scale < (mDefaultScale + 0.05)) {
+ scale = mDefaultScale;
}
setNewZoomScale(scale, false);
@@ -4517,9 +4574,11 @@ public class WebView extends AbsoluteLayout
break;
}
case SWITCH_TO_LONGPRESS: {
- mTouchMode = TOUCH_DONE_MODE;
- performLongClick();
- updateTextEntry();
+ if (!mPreventDrag) {
+ mTouchMode = TOUCH_DONE_MODE;
+ performLongClick();
+ updateTextEntry();
+ }
break;
}
case SWITCH_TO_ENTER:
@@ -4651,8 +4710,8 @@ public class WebView extends AbsoluteLayout
}
int initialScale = msg.arg1;
int viewportWidth = msg.arg2;
- // by default starting a new page with 100% zoom scale.
- float scale = 1.0f;
+ // start a new page with DEFAULT_SCALE zoom scale.
+ float scale = mDefaultScale;
if (mInitialScale > 0) {
scale = mInitialScale / 100.0f;
} else {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index e9df453..a5fa41e 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -97,7 +97,7 @@ final class WebViewCore {
private boolean mViewportUserScalable = true;
- private int mRestoredScale = 100;
+ private int mRestoredScale = WebView.DEFAULT_SCALE_PERCENT;
private int mRestoredX = 0;
private int mRestoredY = 0;
@@ -139,7 +139,7 @@ final class WebViewCore {
// ready.
mEventHub = new EventHub();
// Create a WebSettings object for maintaining all settings
- mSettings = new WebSettings(mContext);
+ mSettings = new WebSettings(mContext, mWebView);
// The WebIconDatabase needs to be initialized within the UI thread so
// just request the instance here.
WebIconDatabase.getInstance();
@@ -544,6 +544,8 @@ final class WebViewCore {
"WEBKIT_DRAW", // = 130;
"SYNC_SCROLL", // = 131;
"REFRESH_PLUGINS", // = 132;
+ // this will replace REFRESH_PLUGINS in the next release
+ "POST_URL", // = 142;
"SPLIT_PICTURE_SET", // = 133;
"CLEAR_CONTENT", // = 134;
"SET_FINAL_FOCUS", // = 135;
@@ -589,6 +591,8 @@ final class WebViewCore {
static final int WEBKIT_DRAW = 130;
static final int SYNC_SCROLL = 131;
static final int REFRESH_PLUGINS = 132;
+ // this will replace REFRESH_PLUGINS in the next release
+ static final int POST_URL = 142;
static final int SPLIT_PICTURE_SET = 133;
static final int CLEAR_CONTENT = 134;
@@ -672,6 +676,13 @@ final class WebViewCore {
loadUrl((String) msg.obj);
break;
+ case POST_URL: {
+ HashMap param = (HashMap) msg.obj;
+ String url = (String) param.get("url");
+ byte[] data = (byte[]) param.get("data");
+ mBrowserFrame.postUrl(url, data);
+ break;
+ }
case LOAD_DATA:
HashMap loadParams = (HashMap) msg.obj;
String baseUrl = (String) loadParams.get("baseUrl");
@@ -1549,19 +1560,33 @@ final class WebViewCore {
// set the viewport settings from WebKit
setViewportSettingsFromNative();
+ // adjust the default scale to match the density
+ if (WebView.DEFAULT_SCALE_PERCENT != 100) {
+ float adjust = (float) WebView.DEFAULT_SCALE_PERCENT / 100.0f;
+ if (mViewportInitialScale > 0) {
+ mViewportInitialScale *= adjust;
+ }
+ if (mViewportMinimumScale > 0) {
+ mViewportMinimumScale *= adjust;
+ }
+ if (mViewportMaximumScale > 0) {
+ mViewportMaximumScale *= adjust;
+ }
+ }
+
// infer the values if they are not defined.
if (mViewportWidth == 0) {
if (mViewportInitialScale == 0) {
- mViewportInitialScale = 100;
+ mViewportInitialScale = WebView.DEFAULT_SCALE_PERCENT;
}
if (mViewportMinimumScale == 0) {
- mViewportMinimumScale = 100;
+ mViewportMinimumScale = WebView.DEFAULT_SCALE_PERCENT;
}
}
if (mViewportUserScalable == false) {
- mViewportInitialScale = 100;
- mViewportMinimumScale = 100;
- mViewportMaximumScale = 100;
+ mViewportInitialScale = WebView.DEFAULT_SCALE_PERCENT;
+ mViewportMinimumScale = WebView.DEFAULT_SCALE_PERCENT;
+ mViewportMaximumScale = WebView.DEFAULT_SCALE_PERCENT;
}
if (mViewportMinimumScale > mViewportInitialScale) {
if (mViewportInitialScale == 0) {
@@ -1575,9 +1600,10 @@ final class WebViewCore {
mViewportMaximumScale = mViewportInitialScale;
} else if (mViewportInitialScale == 0) {
mViewportInitialScale = mViewportMaximumScale;
- }
+ }
}
- if (mViewportWidth < 0 && mViewportInitialScale == 100) {
+ if (mViewportWidth < 0
+ && mViewportInitialScale == WebView.DEFAULT_SCALE_PERCENT) {
mViewportWidth = 0;
}
diff --git a/core/java/android/webkit/gears/AndroidRadioDataProvider.java b/core/java/android/webkit/gears/AndroidRadioDataProvider.java
index 2d431a8..1384042 100644
--- a/core/java/android/webkit/gears/AndroidRadioDataProvider.java
+++ b/core/java/android/webkit/gears/AndroidRadioDataProvider.java
@@ -28,6 +28,7 @@ package android.webkit.gears;
import android.content.Context;
import android.telephony.CellLocation;
import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
import android.telephony.gsm.GsmCellLocation;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
@@ -54,6 +55,7 @@ public final class AndroidRadioDataProvider extends PhoneStateListener {
public static final class RadioData {
public int cellId = -1;
public int locationAreaCode = -1;
+ // TODO: use new SignalStrength instead of asu
public int signalStrength = -1;
public int mobileCountryCode = -1;
public int mobileNetworkCode = -1;
@@ -179,6 +181,7 @@ public final class AndroidRadioDataProvider extends PhoneStateListener {
private CellLocation cellLocation = null;
/** The last known signal strength */
+ // TODO: use new SignalStrength instead of asu
private int signalStrength = -1;
/** The last known serviceState */
@@ -207,7 +210,7 @@ public final class AndroidRadioDataProvider extends PhoneStateListener {
// Register for cell id, signal strength and service state changed
// notifications.
telephonyManager.listen(this, PhoneStateListener.LISTEN_CELL_LOCATION
- | PhoneStateListener.LISTEN_SIGNAL_STRENGTH
+ | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
| PhoneStateListener.LISTEN_SERVICE_STATE);
}
@@ -226,8 +229,9 @@ public final class AndroidRadioDataProvider extends PhoneStateListener {
}
@Override
- public void onSignalStrengthChanged(int asu) {
- signalStrength = asu;
+ public void onSignalStrengthsChanged(SignalStrength ss) {
+ int gsmSignalStrength = ss.getGsmSignalStrength();
+ signalStrength = (gsmSignalStrength == 99 ? -1 : gsmSignalStrength);
notifyListeners();
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1ca59b2..f9ca8cb 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -54,7 +54,9 @@ import java.util.ArrayList;
import java.util.List;
/**
- * Common code shared between ListView and GridView
+ * Base class that can be used to implement virtualized lists of items. A list does
+ * not have a spatial definition here. For instance, subclases of this class can
+ * display the content of the list in a grid, in a carousel, as stack, etc.
*
* @attr ref android.R.styleable#AbsListView_listSelector
* @attr ref android.R.styleable#AbsListView_drawSelectorOnTop
@@ -86,7 +88,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public static final int TRANSCRIPT_MODE_NORMAL = 1;
/**
* The list will automatically scroll to the bottom, no matter what items
- * are currently visible.
+ * are currently visible.
*
* @see #setTranscriptMode(int)
*/
@@ -123,7 +125,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Indicates the view is in the process of being flung
*/
static final int TOUCH_MODE_FLING = 4;
-
+
/**
* Indicates that the user is currently dragging the fast scroll thumb
*/
@@ -316,7 +318,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* bitmap cache after scrolling.
*/
boolean mScrollingCacheEnabled;
-
+
/**
* Whether or not to enable the fast scroll feature on this list
*/
@@ -389,7 +391,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* The last CheckForTap runnable we posted, if any
*/
private Runnable mPendingCheckForTap;
-
+
/**
* The last CheckForKeyLongPress runnable we posted, if any
*/
@@ -427,14 +429,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
private FastScroller mFastScroller;
- private int mTouchSlop;
+ private boolean mGlobalLayoutListenerAddedFilter;
+ private int mTouchSlop;
private float mDensityScale;
private InputConnection mDefInputConnection;
private InputConnectionWrapper mPublicInputConnection;
private Runnable mClearScrollingCache;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
/**
* Interface definition for a callback to be invoked when the list or grid
@@ -529,21 +534,35 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int color = a.getColor(R.styleable.AbsListView_cacheColorHint, 0);
setCacheColorHint(color);
-
+
boolean enableFastScroll = a.getBoolean(R.styleable.AbsListView_fastScrollEnabled, false);
setFastScrollEnabled(enableFastScroll);
boolean smoothScrollbar = a.getBoolean(R.styleable.AbsListView_smoothScrollbar, true);
setSmoothScrollbarEnabled(smoothScrollbar);
-
+
a.recycle();
}
+ private void initAbsListView() {
+ // Setting focusable in touch mode will set the focusable property to true
+ setFocusableInTouchMode(true);
+ setWillNotDraw(false);
+ setAlwaysDrawnWithCacheEnabled(false);
+ setScrollingCacheEnabled(true);
+
+ final ViewConfiguration configuration = ViewConfiguration.get(mContext);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+ mDensityScale = getContext().getResources().getDisplayMetrics().density;
+ }
+
/**
- * 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
+ * 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
* to implement {@link SectionIndexer} if it wishes to display alphabet preview and
- * jump between sections of the list.
+ * jump between sections of the list.
* @see SectionIndexer
* @see #isFastScrollEnabled()
* @param enabled whether or not to enable fast scrolling
@@ -561,7 +580,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
}
-
+
/**
* Returns the current state of the fast scroll feature.
* @see #setFastScrollEnabled(boolean)
@@ -571,10 +590,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public boolean isFastScrollEnabled() {
return mFastScrollEnabled;
}
-
+
/**
* If fast scroll is visible, then don't draw the vertical scrollbar.
- * @hide
+ * @hide
*/
@Override
protected boolean isVerticalScrollBarHidden() {
@@ -592,11 +611,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* When smooth scrollbar is disabled, the position and size of the scrollbar thumb
* is based solely on the number of items in the adapter and the position of the
* visible items inside the adapter. This provides a stable scrollbar as the user
- * navigates through a list of items with varying heights.
+ * navigates through a list of items with varying heights.
*
* @param enabled Whether or not to enable smooth scrollbar.
*
- * @see #setSmoothScrollbarEnabled(boolean)
+ * @see #setSmoothScrollbarEnabled(boolean)
* @attr ref android.R.styleable#AbsListView_smoothScrollbar
*/
public void setSmoothScrollbarEnabled(boolean enabled) {
@@ -712,17 +731,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- private void initAbsListView() {
- // Setting focusable in touch mode will set the focusable property to true
- setFocusableInTouchMode(true);
- setWillNotDraw(false);
- setAlwaysDrawnWithCacheEnabled(false);
- setScrollingCacheEnabled(true);
-
- mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- mDensityScale = getContext().getResources().getDisplayMetrics().density;
- }
-
private void useDefaultSelector() {
setSelector(getResources().getDrawable(
com.android.internal.R.drawable.list_selector_background));
@@ -828,7 +836,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public Parcelable onSaveInstanceState() {
/*
* This doesn't really make sense as the place to dismiss the
- * popup, but there don't seem to be any other useful hooks
+ * popups, but there don't seem to be any other useful hooks
* that happen early enough to keep from getting complaints
* about having leaked the window.
*/
@@ -908,17 +916,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private boolean acceptFilter() {
- if (!mTextFilterEnabled || !(getAdapter() instanceof Filterable) ||
- ((Filterable) getAdapter()).getFilter() == null) {
- return false;
- }
- return true;
+ return mTextFilterEnabled && getAdapter() instanceof Filterable &&
+ ((Filterable) getAdapter()).getFilter() != null;
}
/**
* Sets the initial value for the text filter.
* @param filterText The text to use for the filter.
- *
+ *
* @see #setTextFilterEnabled
*/
public void setFilterText(String filterText) {
@@ -944,7 +949,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
- * Returns the list's text filter, if available.
+ * Returns the list's text filter, if available.
* @return the list's text filter or null if filtering isn't enabled
*/
public CharSequence getTextFilter() {
@@ -953,7 +958,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
return null;
}
-
+
@Override
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
@@ -1096,6 +1101,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
listPadding.bottom = mSelectionBottomPadding + mPaddingBottom;
}
+ /**
+ * Subclasses should NOT override this method but
+ * {@link #layoutChildren()} instead.
+ */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
@@ -1111,17 +1120,22 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
protected boolean setFrame(int left, int top, int right, int bottom) {
final boolean changed = super.setFrame(left, top, right, bottom);
- // Reposition the popup when the frame has changed. This includes
- // translating the widget, not just changing its dimension. The
- // filter popup needs to follow the widget.
- if (mFiltered && changed && getWindowVisibility() == View.VISIBLE && mPopup != null &&
- mPopup.isShowing()) {
- positionPopup();
+ if (changed) {
+ // Reposition the popup when the frame has changed. This includes
+ // translating the widget, not just changing its dimension. The
+ // filter popup needs to follow the widget.
+ final boolean visible = getWindowVisibility() == View.VISIBLE;
+ if (mFiltered && visible && mPopup != null && mPopup.isShowing()) {
+ positionPopup();
+ }
}
return changed;
}
+ /**
+ * Subclasses must override this method to layout their children.
+ */
protected void layoutChildren() {
}
@@ -1324,6 +1338,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mDataChanged = true;
rememberSyncState();
}
+
if (mFastScroller != null) {
mFastScroller.onSizeChanged(w, h, oldw, oldh);
}
@@ -1494,7 +1509,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
System.arraycopy(state, enabledPos + 1, state, enabledPos,
state.length - enabledPos - 1);
}
-
+
return state;
}
@@ -1510,6 +1525,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final ViewTreeObserver treeObserver = getViewTreeObserver();
if (treeObserver != null) {
treeObserver.addOnTouchModeChangeListener(this);
+ if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
+ treeObserver.addOnGlobalLayoutListener(this);
+ }
}
}
@@ -1520,6 +1538,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final ViewTreeObserver treeObserver = getViewTreeObserver();
if (treeObserver != null) {
treeObserver.removeOnTouchModeChangeListener(this);
+ if (mTextFilterEnabled && mPopup != null) {
+ treeObserver.removeGlobalOnLayoutListener(this);
+ mGlobalLayoutListenerAddedFilter = false;
+ }
}
}
@@ -1586,16 +1608,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
*/
private class WindowRunnnable {
private int mOriginalAttachCount;
-
+
public void rememberWindowAttachCount() {
mOriginalAttachCount = getWindowAttachCount();
}
-
+
public boolean sameWindow() {
return hasWindowFocus() && getWindowAttachCount() == mOriginalAttachCount;
}
}
-
+
private class PerformClick extends WindowRunnnable implements Runnable {
View mChild;
int mClickMotionPosition;
@@ -1622,7 +1644,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
final long longPressId = mAdapter.getItemId(mMotionPosition);
boolean handled = false;
- if (sameWindow() && !mDataChanged) {
+ if (sameWindow() && !mDataChanged) {
handled = performLongPress(child, longPressPosition, longPressId);
}
if (handled) {
@@ -1636,7 +1658,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
}
-
+
private class CheckForKeyLongPress extends WindowRunnnable implements Runnable {
public void run() {
if (isPressed() && mSelectedPosition >= 0) {
@@ -1812,7 +1834,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mTouchMode = TOUCH_MODE_DONE_WAITING;
}
} else {
- mTouchMode = TOUCH_MODE_DONE_WAITING;
+ mTouchMode = TOUCH_MODE_DONE_WAITING;
}
}
}
@@ -1867,13 +1889,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean onTouchEvent(MotionEvent ev) {
-
if (mFastScroller != null) {
boolean intercepted = mFastScroller.onTouchEvent(ev);
if (intercepted) {
return true;
- }
+ }
}
+
final int action = ev.getAction();
final int x = (int) ev.getX();
final int y = (int) ev.getY();
@@ -2041,12 +2063,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
break;
case TOUCH_MODE_SCROLL:
final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000);
- int initialVelocity = (int)velocityTracker.getYVelocity();
-
- if ((Math.abs(initialVelocity) >
- ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
- (getChildCount() > 0)) {
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ final int initialVelocity = (int) velocityTracker.getYVelocity();
+ if (Math.abs(initialVelocity) > mMinimumVelocity && (getChildCount() > 0)) {
if (mFlingRunnable == null) {
mFlingRunnable = new FlingRunnable();
}
@@ -2059,10 +2078,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
setPressed(false);
-
+
// Need to redraw since we probably aren't drawing the selector anymore
invalidate();
-
+
final Handler handler = getHandler();
if (handler != null) {
handler.removeCallbacks(mPendingCheckForLongPress);
@@ -2106,7 +2125,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
return true;
}
-
+
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@@ -2121,14 +2140,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
int x = (int) ev.getX();
int y = (int) ev.getY();
View v;
-
+
if (mFastScroller != null) {
boolean intercepted = mFastScroller.onInterceptTouchEvent(ev);
if (intercepted) {
return true;
}
}
-
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
int motionPosition = findMotionRow(y);
@@ -2775,7 +2794,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
/**
* Removes the filter window
*/
- void dismissPopup() {
+ private void dismissPopup() {
if (mPopup != null) {
mPopup.dismiss();
}
@@ -2978,7 +2997,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
return null;
}
-
+
/**
* For filtering we proxy an input connection to an internal text editor,
* and this allows the proxying to happen.
@@ -2987,7 +3006,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public boolean checkInputConnectionProxy(View view) {
return view == mTextFilter;
}
-
+
/**
* Creates the window for the text filter and populates it with an EditText field;
*
@@ -3017,6 +3036,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
p.setBackgroundDrawable(null);
mPopup = p;
getViewTreeObserver().addOnGlobalLayoutListener(this);
+ mGlobalLayoutListenerAddedFilter = true;
}
if (animateEntrance) {
mPopup.setAnimationStyle(com.android.internal.R.style.Animation_TypingFilter);
@@ -3379,7 +3399,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mCurrentScrap = scrapViews[0];
mScrapViews = scrapViews;
}
-
+
public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 173e80f..7d2fcbc 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -24,11 +24,12 @@ import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.ContextMenu;
+import android.view.SoundEffectConstants;
import android.view.View;
-import android.view.ViewGroup;
import android.view.ViewDebug;
-import android.view.SoundEffectConstants;
+import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.accessibility.AccessibilityEvent;
/**
@@ -618,7 +619,9 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
/**
- * Sets the currently selected item
+ * Sets the currently selected item. To support accessibility subclasses that
+ * override this method must invoke the overriden super method first.
+ *
* @param position Index (starting at 0) of the data item to be selected.
*/
public abstract void setSelection(int position);
@@ -844,6 +847,11 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
fireOnSelected();
}
}
+
+ // we fire selection events here not in View
+ if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
}
private void fireOnSelected() {
@@ -861,6 +869,35 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
@Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ boolean populated = false;
+ // This is an exceptional case which occurs when a window gets the
+ // focus and sends a focus event via its focused child to announce
+ // current focus/selection. AdapterView fires selection but not focus
+ // events so we change the event type here.
+ if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
+ event.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+
+ // we send selection events only from AdapterView to avoid
+ // generation of such event for each child
+ View selectedView = getSelectedView();
+ if (selectedView != null) {
+ populated = selectedView.dispatchPopulateAccessibilityEvent(event);
+ }
+
+ if (!populated) {
+ if (selectedView != null) {
+ event.setEnabled(selectedView.isEnabled());
+ }
+ event.setItemCount(getCount());
+ event.setCurrentItemIndex(getSelectedItemPosition());
+ }
+
+ return populated;
+ }
+
+ @Override
protected boolean canAnimate() {
return super.canAnimate() && mItemCount > 0;
}
diff --git a/core/java/android/widget/AlphabetIndexer.java b/core/java/android/widget/AlphabetIndexer.java
index 4e466a0..f50676a 100644
--- a/core/java/android/widget/AlphabetIndexer.java
+++ b/core/java/android/widget/AlphabetIndexer.java
@@ -248,8 +248,8 @@ public class AlphabetIndexer extends DataSetObserver implements SectionIndexer {
public int getSectionForPosition(int position) {
int savedCursorPos = mDataCursor.getPosition();
mDataCursor.moveToPosition(position);
- mDataCursor.moveToPosition(savedCursorPos);
String curName = mDataCursor.getString(mColumnIndex);
+ mDataCursor.moveToPosition(savedCursorPos);
// Linear search, as there are only a few items in the section index
// Could speed this up later if it actually gets used.
for (int i = 0; i < mAlphabetLength; i++) {
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index 5fa00e7..c4b5ef8 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -124,25 +124,25 @@ public class AppSecurityPermissions implements View.OnClickListener {
if(pkg == null) {
return;
}
- // Extract shared user permissions if any
+ // Get requested permissions
+ if (pkg.requestedPermissions != null) {
+ ArrayList<String> strList = pkg.requestedPermissions;
+ int size = strList.size();
+ if (size > 0) {
+ extractPerms(strList.toArray(new String[size]), permSet);
+ }
+ }
+ // Get permissions related to shared user if any
if(pkg.mSharedUserId != null) {
int sharedUid;
try {
sharedUid = mPm.getUidForSharedUser(pkg.mSharedUserId);
+ getAllUsedPermissions(sharedUid, permSet);
} catch (NameNotFoundException e) {
Log.w(TAG, "Could'nt retrieve shared user id for:"+pkg.packageName);
- return;
}
- getAllUsedPermissions(sharedUid, permSet);
- } else {
- ArrayList<String> strList = pkg.requestedPermissions;
- int size;
- if((strList == null) || ((size = strList.size()) == 0)) {
- return;
- }
- // Extract permissions defined in current package
- extractPerms(strList.toArray(new String[size]), permSet);
}
+ // Retrieve list of permissions
for(PermissionInfo tmpInfo : permSet) {
mPermsList.add(tmpInfo);
}
@@ -176,14 +176,9 @@ public class AppSecurityPermissions implements View.OnClickListener {
Log.w(TAG, "Could'nt retrieve permissions for package:"+packageName);
return;
}
- if(pkgInfo == null) {
- return;
- }
- String strList[] = pkgInfo.requestedPermissions;
- if(strList == null) {
- return;
+ if ((pkgInfo != null) && (pkgInfo.requestedPermissions != null)) {
+ extractPerms(pkgInfo.requestedPermissions, permSet);
}
- extractPerms(strList, permSet);
}
private void extractPerms(String strList[], Set<PermissionInfo> permSet) {
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index c28210d..32e5504 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -348,7 +348,12 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
"ArrayAdapter requires the resource ID to be a TextView", e);
}
- text.setText(getItem(position).toString());
+ T item = getItem(position);
+ if (item instanceof CharSequence) {
+ text.setText((CharSequence)item);
+ } else {
+ text.setText(item.toString());
+ }
return view;
}
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index a1d16ea..675aba2 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -80,6 +80,7 @@ import com.android.internal.R;
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
* @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
*/
public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
static final boolean DEBUG = false;
@@ -101,6 +102,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private int mDropDownAnchorId;
private View mDropDownAnchorView; // view is retrieved lazily from id once needed
private int mDropDownWidth;
+ private int mDropDownHeight;
private Drawable mDropDownListHighlight;
@@ -122,10 +124,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private boolean mBlockCompletion;
private AutoCompleteTextView.ListSelectorHider mHideSelector;
-
- // Indicates whether this AutoCompleteTextView is attached to a window or not
- // The widget is attached to a window when mAttachCount > 0
- private int mAttachCount;
+ private Runnable mShowDropDownRunnable;
private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener;
@@ -170,6 +169,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
// (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
ViewGroup.LayoutParams.WRAP_CONTENT);
+ mDropDownHeight = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownHeight,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
R.layout.simple_dropdown_hint);
@@ -258,6 +259,34 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void setDropDownWidth(int width) {
mDropDownWidth = width;
}
+
+ /**
+ * <p>Returns the current height for the auto-complete drop down list. This can
+ * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill
+ * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
+ * of the drop down's content.</p>
+ *
+ * @return the height for the drop down list
+ *
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
+ */
+ public int getDropDownHeight() {
+ return mDropDownHeight;
+ }
+
+ /**
+ * <p>Sets the current height for the auto-complete drop down list. This can
+ * be a fixed height, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill
+ * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the height
+ * of the drop down's content.</p>
+ *
+ * @param height the height to use
+ *
+ * @attr ref android.R.styleable#AutoCompleteTextView_dropDownHeight
+ */
+ public void setDropDownHeight(int height) {
+ mDropDownHeight = height;
+ }
/**
* <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
@@ -589,7 +618,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
if (isPopupShowing()) {
// special case for the back key, we do not even try to send it
// to the drop down list but instead, consume it immediately
- if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && !mDropDownAlwaysVisible) {
dismissDropDown();
return true;
}
@@ -637,15 +666,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownList.getAdapter().getCount() - 1)) {
// When the selection is at the top, we block the key
// event to prevent focus from moving.
- mDropDownList.hideSelector();
- mDropDownList.requestLayout();
+ clearListSelection();
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
- mPopup.update();
+ showDropDown();
return true;
+ } else {
+ // WARNING: Please read the comment where mListSelectionHidden
+ // is declared
+ mDropDownList.mListSelectionHidden = false;
}
+
consumed = mDropDownList.onKeyDown(keyCode, event);
- if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed="
- + consumed);
+ if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed=" + consumed);
+
if (consumed) {
// If it handled the key event, then the user is
// navigating in the list, so we should put it in front.
@@ -655,7 +688,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
// by ensuring it has focus and getting its window out
// of touch mode.
mDropDownList.requestFocusFromTouch();
- mPopup.update();
+ showDropDown();
switch (keyCode) {
// avoid passing the focus from the text view to the
@@ -755,7 +788,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
} else {
// drop down is automatically dismissed when enough characters
// are deleted from the text view
- dismissDropDown();
+ if (!mDropDownAlwaysVisible) dismissDropDown();
if (mFilter != null) {
mFilter.filter(null);
}
@@ -788,9 +821,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* it back.
*/
public void clearListSelection() {
- if (mDropDownList != null) {
- mDropDownList.hideSelector();
- mDropDownList.requestLayout();
+ final DropDownListView list = mDropDownList;
+ if (list != null) {
+ // WARNING: Please read the comment where mListSelectionHidden is declared
+ list.mListSelectionHidden = true;
+ list.hideSelector();
+ list.requestLayout();
}
}
@@ -801,6 +837,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
*/
public void setListSelection(int position) {
if (mPopup.isShowing() && (mDropDownList != null)) {
+ mDropDownList.mListSelectionHidden = false;
mDropDownList.setSelection(position);
// ListView.setSelection() will call requestLayout()
}
@@ -893,7 +930,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
}
- if (mDropDownDismissedOnCompletion) {
+ if (mDropDownDismissedOnCompletion && !mDropDownAlwaysVisible) {
dismissDropDown();
}
}
@@ -950,6 +987,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* @param text the selected suggestion in the drop down list
*/
protected void replaceText(CharSequence text) {
+ clearComposingText();
+
setText(text);
// make sure we keep the caret at the end of the text view
Editable spannable = getText();
@@ -958,7 +997,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
/** {@inheritDoc} */
public void onFilterComplete(int count) {
- if (mAttachCount <= 0) return;
+ // Not attached to window, don't update drop-down
+ if (getWindowVisibility() == View.GONE) return;
/*
* This checks enoughToFilter() again because filtering requests
@@ -971,7 +1011,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
if (hasFocus() && hasWindowFocus()) {
showDropDown();
}
- } else {
+ } else if (!mDropDownAlwaysVisible) {
dismissDropDown();
}
}
@@ -980,7 +1020,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
performValidation();
- if (!hasWindowFocus) {
+ if (!hasWindowFocus && !mDropDownAlwaysVisible) {
dismissDropDown();
}
}
@@ -989,7 +1029,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
performValidation();
- if (!focused) {
+ if (!focused && !mDropDownAlwaysVisible) {
dismissDropDown();
}
}
@@ -997,13 +1037,11 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mAttachCount++;
}
@Override
protected void onDetachedFromWindow() {
dismissDropDown();
- mAttachCount--;
super.onDetachedFromWindow();
}
@@ -1044,12 +1082,26 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
}
/**
+ * Issues a runnable to show the dropdown as soon as possible.
+ *
+ * @hide internal used only by Search Dialog
+ */
+ public void showDropDownAfterLayout() {
+ post(mShowDropDownRunnable);
+ }
+
+ /**
* <p>Displays the drop down on screen.</p>
*/
public void showDropDown() {
int height = buildDropDown();
+
+ int widthSpec = 0;
+ int heightSpec = 0;
+
+ boolean noInputMethod = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED;
+
if (mPopup.isShowing()) {
- int widthSpec;
if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
// The call to PopupWindow's update method below can accept -1 for any
// value you do not want to update.
@@ -1059,20 +1111,51 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
} else {
widthSpec = mDropDownWidth;
}
+
+ if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
+ // The call to PopupWindow's update method below can accept -1 for any
+ // value you do not want to update.
+ heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.FILL_PARENT;
+ if (noInputMethod) {
+ mPopup.setWindowLayoutMode(
+ mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
+ ViewGroup.LayoutParams.FILL_PARENT : 0, 0);
+ } else {
+ mPopup.setWindowLayoutMode(
+ mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT ?
+ ViewGroup.LayoutParams.FILL_PARENT : 0,
+ ViewGroup.LayoutParams.FILL_PARENT);
+ }
+ } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ heightSpec = height;
+ } else {
+ heightSpec = mDropDownHeight;
+ }
+
mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
- mDropDownVerticalOffset, widthSpec, height);
+ mDropDownVerticalOffset, widthSpec, heightSpec);
} else {
if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
- mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT, 0);
+ widthSpec = ViewGroup.LayoutParams.FILL_PARENT;
} else {
- mPopup.setWindowLayoutMode(0, 0);
if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
mPopup.setWidth(getDropDownAnchorView().getWidth());
} else {
mPopup.setWidth(mDropDownWidth);
}
}
- mPopup.setHeight(height);
+
+ if (mDropDownHeight == ViewGroup.LayoutParams.FILL_PARENT) {
+ heightSpec = ViewGroup.LayoutParams.FILL_PARENT;
+ } else {
+ if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ mPopup.setHeight(height);
+ } else {
+ mPopup.setHeight(mDropDownHeight);
+ }
+ }
+
+ mPopup.setWindowLayoutMode(widthSpec, heightSpec);
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
// use outside touchable to dismiss drop down when touching outside of it, so
@@ -1082,8 +1165,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mPopup.showAsDropDown(getDropDownAnchorView(),
mDropDownHorizontalOffset, mDropDownVerticalOffset);
mDropDownList.setSelection(ListView.INVALID_POSITION);
- mDropDownList.hideSelector();
- mDropDownList.requestFocus();
+ clearListSelection();
post(mHideSelector);
}
}
@@ -1119,6 +1201,22 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mHideSelector = new ListSelectorHider();
+ /**
+ * This Runnable exists for the sole purpose of checking if the view layout has got
+ * completed and if so call showDropDown to display the drop down. This is used to show
+ * the drop down as soon as possible after user opens up the search dialog, without
+ * waiting for the normal UI pipeline to do it's job which is slower than this method.
+ */
+ mShowDropDownRunnable = new Runnable() {
+ public void run() {
+ // View layout should be all done before displaying the drop down.
+ View view = getDropDownAnchorView();
+ if (view != null && view.getWindowToken() != null) {
+ showDropDown();
+ }
+ }
+ };
+
mDropDownList = new DropDownListView(context);
mDropDownList.setSelector(mDropDownListHighlight);
mDropDownList.setAdapter(mAdapter);
@@ -1126,6 +1224,22 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
mDropDownList.setFocusable(true);
mDropDownList.setFocusableInTouchMode(true);
+ mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> parent, View view,
+ int position, long id) {
+
+ if (position != -1) {
+ DropDownListView dropDownList = mDropDownList;
+
+ if (dropDownList != null) {
+ dropDownList.mListSelectionHidden = false;
+ }
+ }
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ }
+ });
if (mItemSelectedListener != null) {
mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
@@ -1180,10 +1294,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
final int maxHeight = mPopup.getMaxAvailableHeight(
getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations);
- final int measuredHeight = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
- 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
+ if (mDropDownAlwaysVisible) {
+ return maxHeight;
+ }
- return mDropDownAlwaysVisible ? maxHeight : measuredHeight;
+ return mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
+ 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
}
private View getHintView(Context context) {
@@ -1249,10 +1365,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private class ListSelectorHider implements Runnable {
public void run() {
- if (mDropDownList != null) {
- mDropDownList.hideSelector();
- mDropDownList.requestLayout();
- }
+ clearListSelection();
}
}
@@ -1279,6 +1392,36 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
* passed to the drop down; the list only looks focused.</p>
*/
private static class DropDownListView extends ListView {
+ /*
+ * WARNING: This is a workaround for a touch mode issue.
+ *
+ * Touch mode is propagated lazily to windows. This causes problems in
+ * the following scenario:
+ * - Type something in the AutoCompleteTextView and get some results
+ * - Move down with the d-pad to select an item in the list
+ * - Move up with the d-pad until the selection disappears
+ * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+ * and get new results; you are now in touch mode
+ * - The selection comes back on the first item in the list, even though
+ * the list is supposed to be in touch mode
+ *
+ * Using the soft keyboard triggers the touch mode change but that change
+ * is propagated to our window only after the first list layout, therefore
+ * after the list attempts to resurrect the selection.
+ *
+ * The trick to work around this issue is to pretend the list is in touch
+ * mode when we know that the selection should not appear, that is when
+ * we know the user moved the selection away from the list.
+ *
+ * This boolean is set to true whenever we explicitely hide the list's
+ * selection and reset to false whenver we know the user moved the
+ * selection back to the list.
+ *
+ * When this boolean is true, isInTouchMode() returns true, otherwise it
+ * returns super.isInTouchMode().
+ */
+ private boolean mListSelectionHidden;
+
/**
* <p>Creates a new list view wrapper.</p>
*
@@ -1324,6 +1467,12 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
return mSelectionBottomPadding;
}
+ @Override
+ public boolean isInTouchMode() {
+ // WARNING: Please read the comment where mListSelectionHidden is declared
+ return mListSelectionHidden || super.isInTouchMode();
+ }
+
/**
* <p>Returns the focus state in the drop down.</p>
*
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index abcc715..fd590ed 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -16,14 +16,15 @@
package android.widget;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
-
-import com.android.internal.R;
+import android.view.accessibility.AccessibilityEvent;
/**
@@ -194,5 +195,13 @@ public class CheckedTextView extends TextView implements Checkable {
invalidate();
}
}
-
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+ if (!populated) {
+ event.setChecked(mChecked);
+ }
+ return populated;
+ }
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index d4482dc..98b0976 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -26,7 +26,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
-
+import android.view.accessibility.AccessibilityEvent;
/**
* <p>
@@ -124,6 +124,7 @@ public abstract class CompoundButton extends Button implements Checkable {
if (mOnCheckedChangeWidgetListener != null) {
mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked);
}
+
mBroadcasting = false;
}
}
@@ -205,6 +206,25 @@ public abstract class CompoundButton extends Button implements Checkable {
}
@Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+
+ if (!populated) {
+ int resourceId = 0;
+ if (mChecked) {
+ resourceId = R.string.accessibility_compound_button_selected;
+ } else {
+ resourceId = R.string.accessibility_compound_button_unselected;
+ }
+ String state = getResources().getString(resourceId);
+ event.getText().add(state);
+ event.setChecked(mChecked);
+ }
+
+ return populated;
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 0fc8f49..5360621 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -1083,6 +1083,11 @@ public class ExpandableListView extends ListView {
@Override
public void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java
index 3368477..cd965fc 100644
--- a/core/java/android/widget/FastScroller.java
+++ b/core/java/android/widget/FastScroller.java
@@ -134,7 +134,7 @@ class FastScroller {
mScrollCompleted = true;
- getSections();
+ getSectionsFromIndexer();
mOverlayPos = new RectF();
mScrollFade = new ScrollFade();
@@ -250,7 +250,18 @@ class FastScroller {
}
}
- private void getSections() {
+ SectionIndexer getSectionIndexer() {
+ return mSectionIndexer;
+ }
+
+ Object[] getSections() {
+ if (mListAdapter == null && mList != null) {
+ getSectionsFromIndexer();
+ }
+ return mSections;
+ }
+
+ private void getSectionsFromIndexer() {
Adapter adapter = mList.getAdapter();
mSectionIndexer = null;
if (adapter instanceof HeaderViewListAdapter) {
@@ -391,8 +402,7 @@ class FastScroller {
boolean onInterceptTouchEvent(MotionEvent ev) {
if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (ev.getX() > mList.getWidth() - mThumbW && ev.getY() >= mThumbY &&
- ev.getY() <= mThumbY + mThumbH) {
+ if (isPointInside(ev.getX(), ev.getY())) {
setState(STATE_DRAGGING);
return true;
}
@@ -404,20 +414,20 @@ class FastScroller {
if (mState == STATE_NONE) {
return false;
}
- if (me.getAction() == MotionEvent.ACTION_DOWN) {
- if (me.getX() > mList.getWidth() - mThumbW
- && me.getY() >= mThumbY
- && me.getY() <= mThumbY + mThumbH) {
-
+
+ final int action = me.getAction();
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ if (isPointInside(me.getX(), me.getY())) {
setState(STATE_DRAGGING);
if (mListAdapter == null && mList != null) {
- getSections();
+ getSectionsFromIndexer();
}
cancelFling();
return true;
}
- } else if (me.getAction() == MotionEvent.ACTION_UP) {
+ } else if (action == MotionEvent.ACTION_UP) {
if (mState == STATE_DRAGGING) {
setState(STATE_VISIBLE);
final Handler handler = mHandler;
@@ -425,7 +435,7 @@ class FastScroller {
handler.postDelayed(mScrollFade, 1000);
return true;
}
- } else if (me.getAction() == MotionEvent.ACTION_MOVE) {
+ } else if (action == MotionEvent.ACTION_MOVE) {
if (mState == STATE_DRAGGING) {
final int viewHeight = mList.getHeight();
// Jitter
@@ -448,7 +458,11 @@ class FastScroller {
}
return false;
}
-
+
+ boolean isPointInside(float x, float y) {
+ return x > mList.getWidth() - mThumbW && y >= mThumbY && y <= mThumbY + mThumbH;
+ }
+
public class ScrollFade implements Runnable {
long mStartTime;
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 80fbf9e..3afd5d4 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -353,25 +353,24 @@ public class FrameLayout extends ViewGroup {
if (mForeground != null) {
final Drawable foreground = mForeground;
+
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
- if (foreground != null) {
- final Rect selfBounds = mSelfBounds;
- final Rect overlayBounds = mOverlayBounds;
-
- final int w = mRight-mLeft;
- final int h = mBottom-mTop;
-
- if (mForegroundInPadding) {
- selfBounds.set(0, 0, w, h);
- } else {
- selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
- }
+ final Rect selfBounds = mSelfBounds;
+ final Rect overlayBounds = mOverlayBounds;
- Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
- foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
- foreground.setBounds(overlayBounds);
+ final int w = mRight-mLeft;
+ final int h = mBottom-mTop;
+
+ if (mForegroundInPadding) {
+ selfBounds.set(0, 0, w, h);
+ } else {
+ selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom);
}
+
+ Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
+ foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
+ foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 02fc7c6..f86b37c 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -114,6 +114,8 @@ public class HorizontalScrollView extends FrameLayout {
private boolean mSmoothScrollingEnabled = true;
private int mTouchSlop;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
public HorizontalScrollView(Context context) {
this(context, null);
@@ -179,7 +181,10 @@ public class HorizontalScrollView extends FrameLayout {
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ final ViewConfiguration configuration = ViewConfiguration.get(mContext);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
@Override
@@ -477,12 +482,10 @@ public class HorizontalScrollView extends FrameLayout {
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000);
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity();
- if ((Math.abs(initialVelocity) >
- ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
- getChildCount() > 0) {
+ if ((Math.abs(initialVelocity) > mMinimumVelocity) && getChildCount() > 0) {
fling(-initialVelocity);
}
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index 4c1cbf6..d417e40 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -27,9 +27,35 @@ import java.util.Map;
/**
* <p>
- * An image button displays an image that can be pressed, or clicked, by the
- * user.
- * </p>
+ * Displays a button with an image (instead of text) that can be pressed
+ * or clicked by the user. By default, an ImageButton looks like a regular
+ * {@link android.widget.Button}, with the standard button background
+ * that changes color during different button states. The image on the surface
+ * of the button is defined either by the {@code android:src} attribute in the
+ * {@code &lt;ImageButton&gt;} XML element or by the
+ * {@link #setImageResource(int)} method.</p>
+ *
+ * <p>To remove the standard button background image, define your own
+ * background image or set the background color to be transparent.</p>
+ * <p>To indicate the different button states (focused, selected, etc.), you can
+ * define a different image for each state. E.g., a blue image by default, an
+ * orange one for when focused, and a yellow one for when pressed. An easy way to
+ * do this is with an XML drawable "selector." For example:</p>
+ * <pre>
+ * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+ * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ * &lt;item android:drawable="@drawable/button_normal" /&gt; &lt;!-- default --&gt;
+ * &lt;item android:state_pressed="true"
+ * android:drawable="@drawable/button_pressed" /&gt; &lt;!-- pressed --&gt;
+ * &lt;item android:state_focused="true"
+ * android:drawable="@drawable/button_focused" /&gt; &lt;!-- focused --&gt;
+ * &lt;/selector&gt;</pre>
+ *
+ * <p>Save the XML file in your project {@code res/drawable/} folder and then
+ * reference it as a drawable for the source of your ImageButton (in the
+ * {@code android:src} attribute). Android will automatically change the image
+ * based on the state of the button and the corresponding images
+ * defined in the XML.</p>
*
* <p><strong>XML attributes</strong></p>
* <p>
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 480b0b8..2796774 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -32,6 +32,8 @@ import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.RemoteViews.RemoteView;
@@ -848,7 +850,7 @@ public class ImageView extends View {
public int getBaseline() {
return mBaselineAligned ? getMeasuredHeight() : -1;
}
-
+
/**
* Set a tinting option for the image.
*
@@ -878,7 +880,7 @@ public class ImageView extends View {
invalidate();
}
}
-
+
public void setAlpha(int alpha) {
alpha &= 0xFF; // keep it legal
if (mAlpha != alpha) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5472d68..f8a6f89 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -21,6 +21,7 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.PixelFormat;
+import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
import android.os.Parcel;
@@ -35,6 +36,7 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.SoundEffectConstants;
+import android.view.accessibility.AccessibilityEvent;
import com.google.android.collect.Lists;
import com.android.internal.R;
@@ -132,6 +134,7 @@ public class ListView extends AbsListView {
// used for temporary calculations.
private final Rect mTempRect = new Rect();
+ private Paint mDividerPaint;
// the single allocated result per list view; kinda cheesey but avoids
// allocating these thingies too often.
@@ -171,6 +174,8 @@ public class ListView extends AbsListView {
setDividerHeight(dividerHeight);
}
+ setChoiceMode(a.getInt(R.styleable.ListView_choiceMode, CHOICE_MODE_NONE));
+
mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true);
mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true);
@@ -1845,6 +1850,39 @@ public class ListView extends AbsListView {
}
}
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ boolean populated = super.dispatchPopulateAccessibilityEvent(event);
+
+ // If the item count is less than 15 then subtract disabled items from the count and
+ // position. Otherwise ignore disabled items.
+ if (!populated) {
+ int itemCount = 0;
+ int currentItemIndex = getSelectedItemPosition();
+
+ ListAdapter adapter = getAdapter();
+ if (adapter != null) {
+ final int count = adapter.getCount();
+ if (count < 15) {
+ for (int i = 0; i < count; i++) {
+ if (adapter.isEnabled(i)) {
+ itemCount++;
+ } else if (i <= currentItemIndex) {
+ currentItemIndex--;
+ }
+ }
+ } else {
+ itemCount = count;
+ }
+ }
+
+ event.setItemCount(itemCount);
+ event.setCurrentItemIndex(currentItemIndex);
+ }
+
+ return populated;
+ }
+
/**
* setSelectionAfterHeaderView set the selection to be the first list item
* after the header views.
@@ -2786,12 +2824,20 @@ public class ListView extends AbsListView {
*/
@Override
public boolean isOpaque() {
- return (mCachingStarted && mIsCacheColorOpaque && mDividerIsOpaque) || super.isOpaque();
+ return (mCachingStarted && mIsCacheColorOpaque && mDividerIsOpaque &&
+ hasOpaqueScrollbars()) || super.isOpaque();
}
@Override
public void setCacheColorHint(int color) {
- mIsCacheColorOpaque = (color >>> 24) == 0xFF;
+ final boolean opaque = (color >>> 24) == 0xFF;
+ mIsCacheColorOpaque = opaque;
+ if (opaque) {
+ if (mDividerPaint == null) {
+ mDividerPaint = new Paint();
+ }
+ mDividerPaint.setColor(color);
+ }
super.setCacheColorHint(color);
}
@@ -2814,6 +2860,17 @@ public class ListView extends AbsListView {
final int first = mFirstPosition;
final boolean areAllItemsSelectable = mAreAllItemsSelectable;
final ListAdapter adapter = mAdapter;
+ // If the list is opaque *and* the background is not, we want to
+ // fill a rect where the dividers would be for non-selectable items
+ // If the list is opaque and the background is also opaque, we don't
+ // need to draw anything since the background will do it for us
+ final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
+
+ if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
+ mDividerPaint = new Paint();
+ mDividerPaint.setColor(getCacheColorHint());
+ }
+ final Paint paint = mDividerPaint;
if (!mStackFromBottom) {
int bottom;
@@ -2825,12 +2882,18 @@ public class ListView extends AbsListView {
View child = getChildAt(i);
bottom = child.getBottom();
// Don't draw dividers next to items that are not enabled
- if (bottom < listBottom && (areAllItemsSelectable ||
- (adapter.isEnabled(first + i) && (i == count - 1 ||
- adapter.isEnabled(first + i + 1))))) {
- bounds.top = bottom;
- bounds.bottom = bottom + dividerHeight;
- drawDivider(canvas, bounds, i);
+ if (bottom < listBottom) {
+ if ((areAllItemsSelectable ||
+ (adapter.isEnabled(first + i) && (i == count - 1 ||
+ adapter.isEnabled(first + i + 1))))) {
+ bounds.top = bottom;
+ bounds.bottom = bottom + dividerHeight;
+ drawDivider(canvas, bounds, i);
+ } else if (fillForMissingDividers) {
+ bounds.top = bottom;
+ bounds.bottom = bottom + dividerHeight;
+ canvas.drawRect(bounds, paint);
+ }
}
}
}
@@ -2844,16 +2907,22 @@ public class ListView extends AbsListView {
View child = getChildAt(i);
top = child.getTop();
// Don't draw dividers next to items that are not enabled
- if (top > listTop && (areAllItemsSelectable ||
- (adapter.isEnabled(first + i) && (i == count - 1 ||
- adapter.isEnabled(first + i + 1))))) {
- bounds.top = top - dividerHeight;
- bounds.bottom = top;
- // Give the method the child ABOVE the divider, so we
- // subtract one from our child
- // position. Give -1 when there is no child above the
- // divider.
- drawDivider(canvas, bounds, i - 1);
+ if (top > listTop) {
+ if ((areAllItemsSelectable ||
+ (adapter.isEnabled(first + i) && (i == count - 1 ||
+ adapter.isEnabled(first + i + 1))))) {
+ bounds.top = top - dividerHeight;
+ bounds.bottom = top;
+ // Give the method the child ABOVE the divider, so we
+ // subtract one from our child
+ // position. Give -1 when there is no child above the
+ // divider.
+ drawDivider(canvas, bounds, i - 1);
+ } else if (fillForMissingDividers) {
+ bounds.top = top - dividerHeight;
+ bounds.bottom = top;
+ canvas.drawRect(bounds, paint);
+ }
}
}
}
@@ -3195,9 +3264,13 @@ public class ListView extends AbsListView {
if (mChoiceMode == CHOICE_MODE_MULTIPLE) {
mCheckStates.put(position, value);
} else {
- boolean oldValue = mCheckStates.get(position, false);
+ // Clear the old value: if something was selected and value == false
+ // then it is unselected
mCheckStates.clear();
- if (!oldValue) {
+ // If value == true, select the appropriate position
+ // this may end up selecting the value we just cleared but this way
+ // we don't have to first to a get(position)
+ if (value) {
mCheckStates.put(position, true);
}
}
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index 05abc26..ae80277 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -195,6 +195,8 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
*/
@Override
protected void replaceText(CharSequence text) {
+ clearComposingText();
+
int end = getSelectionEnd();
int start = mTokenizer.findTokenStart(getText(), end);
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 78c7bd8..0c2cd55 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -18,6 +18,8 @@ package android.widget;
import com.android.internal.R;
+import android.content.Context;
+import android.content.res.TypedArray;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -33,8 +35,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.IBinder;
-import android.content.Context;
-import android.content.res.TypedArray;
import android.util.AttributeSet;
import java.lang.ref.WeakReference;
@@ -49,7 +49,7 @@ import java.lang.ref.WeakReference;
*/
public class PopupWindow {
/**
- * Mode for {@link #setInputMethodMode(int): the requirements for the
+ * Mode for {@link #setInputMethodMode(int)}: the requirements for the
* input method should be based on the focusability of the popup. That is
* if it is focusable than it needs to work with the input method, else
* it doesn't.
@@ -57,16 +57,15 @@ public class PopupWindow {
public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
/**
- * Mode for {@link #setInputMethodMode(int): this popup always needs to
+ * Mode for {@link #setInputMethodMode(int)}: this popup always needs to
* work with an input method, regardless of whether it is focusable. This
* means that it will always be displayed so that the user can also operate
* the input method while it is shown.
*/
-
public static final int INPUT_METHOD_NEEDED = 1;
/**
- * Mode for {@link #setInputMethodMode(int): this popup never needs to
+ * Mode for {@link #setInputMethodMode(int)}: this popup never needs to
* work with an input method, regardless of whether it is focusable. This
* means that it will always be displayed to use as much space on the
* screen as needed, regardless of whether this covers the input method.
@@ -823,6 +822,7 @@ public class PopupWindow {
p.flags = computeFlags(p.flags);
p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
p.token = token;
+ p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
@@ -990,7 +990,7 @@ public class PopupWindow {
int bottomEdge = displayFrame.bottom;
if (ignoreBottomDecorations) {
- bottomEdge = WindowManagerImpl.getDefault().getDefaultDisplay().getHeight();
+ bottomEdge = anchor.getContext().getResources().getDisplayMetrics().heightPixels;
}
final int distanceToBottom = bottomEdge - (anchorPos[1] + anchor.getHeight()) - yOffset;
final int distanceToTop = anchorPos[1] - displayFrame.top + yOffset;
@@ -1017,6 +1017,7 @@ public class PopupWindow {
unregisterForScrollChanged();
mWindowManager.removeView(mPopupView);
+
if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
((ViewGroup) mPopupView).removeView(mContentView);
}
@@ -1071,6 +1072,20 @@ public class PopupWindow {
mWindowManager.updateViewLayout(mPopupView, p);
}
}
+
+ /**
+ * <p>Updates the dimension of the popup window. Calling this function
+ * also updates the window with the current popup state as described
+ * for {@link #update()}.</p>
+ *
+ * @param width the new width
+ * @param height the new height
+ */
+ public void update(int width, int height) {
+ WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+ mPopupView.getLayoutParams();
+ update(p.x, p.y, width, height, false);
+ }
/**
* <p>Updates the position and the dimension of the popup window. Width and
@@ -1115,8 +1130,7 @@ public class PopupWindow {
return;
}
- WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
+ WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
boolean update = force;
@@ -1203,8 +1217,7 @@ public class PopupWindow {
registerForScrollChanged(anchor, xoff, yoff);
}
- WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
+ WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
if (updateDimension) {
if (width == -1) {
@@ -1316,7 +1329,16 @@ public class PopupWindow {
return super.onTouchEvent(event);
}
}
-
+
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ // clinets are interested in the content not the container, make it event source
+ if (mContentView != null) {
+ mContentView.sendAccessibilityEvent(eventType);
+ } else {
+ super.sendAccessibilityEvent(eventType);
+ }
+ }
}
}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 441414a..2c9e71e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -30,6 +30,7 @@ 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.util.AttributeSet;
@@ -683,7 +684,7 @@ public class ProgressBar extends View {
return;
}
- if (mIndeterminateDrawable instanceof AnimationDrawable) {
+ if (mIndeterminateDrawable instanceof Animatable) {
mShouldStartAnimationDrawable = true;
mAnimation = null;
} else {
@@ -708,8 +709,8 @@ public class ProgressBar extends View {
void stopAnimation() {
mAnimation = null;
mTransformation = null;
- if (mIndeterminateDrawable instanceof AnimationDrawable) {
- ((AnimationDrawable) mIndeterminateDrawable).stop();
+ if (mIndeterminateDrawable instanceof Animatable) {
+ ((Animatable) mIndeterminateDrawable).stop();
mShouldStartAnimationDrawable = false;
}
}
@@ -818,8 +819,8 @@ public class ProgressBar extends View {
}
d.draw(canvas);
canvas.restore();
- if (mShouldStartAnimationDrawable && d instanceof AnimationDrawable) {
- ((AnimationDrawable) d).start();
+ if (mShouldStartAnimationDrawable && d instanceof Animatable) {
+ ((Animatable) d).start();
mShouldStartAnimationDrawable = false;
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index edbb3db..e62dda5 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -16,40 +16,59 @@
package android.widget;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.TypedArray;
+import android.content.res.Resources;
+import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
+import android.util.SparseArray;
+import android.util.Poolable;
+import android.util.Pool;
+import android.util.Pools;
+import android.util.PoolableManager;
+import static android.util.Log.d;
import android.view.Gravity;
+import android.view.View;
import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.RemoteViews.RemoteView;
-import android.graphics.Rect;
-import com.android.internal.R;
+import java.util.Comparator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.LinkedList;
+import java.util.HashSet;
+import java.util.ArrayList;
/**
* A Layout where the positions of the children can be described in relation to each other or to the
* parent. For the sake of efficiency, the relations between views are evaluated in one pass, so if
* view Y is dependent on the position of view X, make sure the view X comes first in the layout.
- *
+ *
* <p>
* Note that you cannot have a circular dependency between the size of the RelativeLayout and the
* position of its children. For example, you cannot have a RelativeLayout whose height is set to
* {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT WRAP_CONTENT} and a child set to
* {@link #ALIGN_PARENT_BOTTOM}.
* </p>
- *
+ *
* <p>
* Also see {@link android.widget.RelativeLayout.LayoutParams RelativeLayout.LayoutParams} for
* layout attributes
* </p>
- *
+ *
* @attr ref android.R.styleable#RelativeLayout_gravity
* @attr ref android.R.styleable#RelativeLayout_ignoreGravity
*/
@RemoteView
public class RelativeLayout extends ViewGroup {
+ private static final String LOG_TAG = "RelativeLayout";
+
+ private static final boolean DEBUG_GRAPH = false;
+
public static final int TRUE = -1;
/**
@@ -137,6 +156,13 @@ public class RelativeLayout extends ViewGroup {
private final Rect mSelfBounds = new Rect();
private int mIgnoreGravity;
+ private SortedSet<View> mTopToBottomLeftToRightSet = null;
+
+ private boolean mDirtyHierarchy;
+ private View[] mSortedHorizontalChildren = new View[0];
+ private View[] mSortedVerticalChildren = new View[0];
+ private final DependencyGraph mGraph = new DependencyGraph();
+
public RelativeLayout(Context context) {
super(context);
}
@@ -225,7 +251,54 @@ public class RelativeLayout extends ViewGroup {
}
@Override
+ public void requestLayout() {
+ super.requestLayout();
+ mDirtyHierarchy = true;
+ }
+
+ private void sortChildren() {
+ int count = getChildCount();
+ if (mSortedVerticalChildren.length != count) mSortedVerticalChildren = new View[count];
+ if (mSortedHorizontalChildren.length != count) mSortedHorizontalChildren = new View[count];
+
+ final DependencyGraph graph = mGraph;
+ graph.clear();
+
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ graph.add(child);
+ }
+
+ if (DEBUG_GRAPH) {
+ d(LOG_TAG, "=== Sorted vertical children");
+ graph.log(getResources(), ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM);
+ d(LOG_TAG, "=== Sorted horizontal children");
+ graph.log(getResources(), LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT);
+ }
+
+ graph.getSortedViews(mSortedVerticalChildren, ABOVE, BELOW, ALIGN_BASELINE,
+ ALIGN_TOP, ALIGN_BOTTOM);
+ graph.getSortedViews(mSortedHorizontalChildren, LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT);
+
+ if (DEBUG_GRAPH) {
+ d(LOG_TAG, "=== Ordered list of vertical children");
+ for (View view : mSortedVerticalChildren) {
+ DependencyGraph.printViewId(getResources(), view);
+ }
+ d(LOG_TAG, "=== Ordered list of horizontal children");
+ for (View view : mSortedHorizontalChildren) {
+ DependencyGraph.printViewId(getResources(), view);
+ }
+ }
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mDirtyHierarchy) {
+ mDirtyHierarchy = false;
+ sortChildren();
+ }
+
int myWidth = -1;
int myHeight = -1;
@@ -254,7 +327,6 @@ public class RelativeLayout extends ViewGroup {
height = myHeight;
}
- int len = this.getChildCount();
mHasBaselineAlignedChild = false;
View ignore = null;
@@ -268,22 +340,50 @@ public class RelativeLayout extends ViewGroup {
int right = Integer.MIN_VALUE;
int bottom = Integer.MIN_VALUE;
+ boolean offsetHorizontalAxis = false;
+ boolean offsetVerticalAxis = false;
+
if ((horizontalGravity || verticalGravity) && mIgnoreGravity != View.NO_ID) {
ignore = findViewById(mIgnoreGravity);
}
- for (int i = 0; i < len; i++) {
- View child = getChildAt(i);
+ final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
+ final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
+
+ View[] views = mSortedHorizontalChildren;
+ int count = views.length;
+ for (int i = 0; i < count; i++) {
+ View child = views[i];
+ if (child.getVisibility() != GONE) {
+ LayoutParams params = (LayoutParams) child.getLayoutParams();
+
+ applyHorizontalSizeRules(params, myWidth);
+ measureChildHorizontal(child, params, myWidth, myHeight);
+ if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
+ offsetHorizontalAxis = true;
+ }
+ }
+ }
+
+ views = mSortedVerticalChildren;
+ count = views.length;
+
+ for (int i = 0; i < count; i++) {
+ View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
- applySizeRules(params, myWidth, myHeight);
+
+ applyVerticalSizeRules(params, myHeight);
measureChild(child, params, myWidth, myHeight);
- positionChild(child, params, myWidth, myHeight);
+ if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
+ offsetVerticalAxis = true;
+ }
- if (widthMode != MeasureSpec.EXACTLY) {
+ if (isWrapContentWidth) {
width = Math.max(width, params.mRight);
}
- if (heightMode != MeasureSpec.EXACTLY) {
+
+ if (isWrapContentHeight) {
height = Math.max(height, params.mBottom);
}
@@ -300,15 +400,15 @@ public class RelativeLayout extends ViewGroup {
}
if (mHasBaselineAlignedChild) {
- for (int i = 0; i < len; i++) {
+ for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
alignBaseline(child, params);
if (child != ignore || verticalGravity) {
- left = Math.min(left, params.mLeft - params.leftMargin);
- top = Math.min(top, params.mTop - params.topMargin);
+ left = Math.min(left, params.mLeft - params.leftMargin);
+ top = Math.min(top, params.mTop - params.topMargin);
}
if (child != ignore || horizontalGravity) {
@@ -319,8 +419,8 @@ public class RelativeLayout extends ViewGroup {
}
}
- if (widthMode != MeasureSpec.EXACTLY) {
- // Width already has left padding in it since it was calculated by looking at
+ if (isWrapContentWidth) {
+ // Width already has left padding in it since it was calculated by looking at
// the right of each child view
width += mPaddingRight;
@@ -330,9 +430,23 @@ public class RelativeLayout extends ViewGroup {
width = Math.max(width, getSuggestedMinimumWidth());
width = resolveSize(width, widthMeasureSpec);
+
+ if (offsetHorizontalAxis) {
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ LayoutParams params = (LayoutParams) child.getLayoutParams();
+ final int[] rules = params.getRules();
+ if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
+ centerHorizontal(child, params, width);
+ }
+ }
+ }
+ }
}
- if (heightMode != MeasureSpec.EXACTLY) {
- // Height already has top padding in it since it was calculated by looking at
+
+ if (isWrapContentHeight) {
+ // Height already has top padding in it since it was calculated by looking at
// the bottom of each child view
height += mPaddingBottom;
@@ -342,6 +456,19 @@ public class RelativeLayout extends ViewGroup {
height = Math.max(height, getSuggestedMinimumHeight());
height = resolveSize(height, heightMeasureSpec);
+
+ if (offsetVerticalAxis) {
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ LayoutParams params = (LayoutParams) child.getLayoutParams();
+ final int[] rules = params.getRules();
+ if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
+ centerVertical(child, params, height);
+ }
+ }
+ }
+ }
}
if (horizontalGravity || verticalGravity) {
@@ -355,7 +482,7 @@ public class RelativeLayout extends ViewGroup {
final int horizontalOffset = contentBounds.left - left;
final int verticalOffset = contentBounds.top - top;
if (horizontalOffset != 0 || verticalOffset != 0) {
- for (int i = 0; i < len; i++) {
+ for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE && child != ignore) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
@@ -409,9 +536,7 @@ public class RelativeLayout extends ViewGroup {
* @param myWidth Width of the the RelativeLayout
* @param myHeight Height of the RelativeLayout
*/
- private void measureChild(View child, LayoutParams params, int myWidth,
- int myHeight) {
-
+ private void measureChild(View child, LayoutParams params, int myWidth, int myHeight) {
int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
params.mRight, params.width,
params.leftMargin, params.rightMargin,
@@ -425,6 +550,21 @@ public class RelativeLayout extends ViewGroup {
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
+ private void measureChildHorizontal(View child, LayoutParams params, int myWidth, int myHeight) {
+ int childWidthMeasureSpec = getChildMeasureSpec(params.mLeft,
+ params.mRight, params.width,
+ params.leftMargin, params.rightMargin,
+ mPaddingLeft, mPaddingRight,
+ myWidth);
+ int childHeightMeasureSpec;
+ if (params.width == LayoutParams.FILL_PARENT) {
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
+ } else {
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
+ }
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ }
+
/**
* Get a measure spec that accounts for all of the constraints on this view.
* This includes size contstraints imposed by the RelativeLayout as well as
@@ -504,19 +644,9 @@ public class RelativeLayout extends ViewGroup {
return MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);
}
- /**
- * After the child has been measured, assign it a position. Some views may
- * already have final values for l,t,r,b. Others may have one or both edges
- * unfixed (i.e. set to -1) in each dimension. These will get positioned
- * based on which edge is fixed, the view's desired dimension, and whether
- * or not it is centered.
- *
- * @param child Child to position
- * @param params LayoutParams associated with child
- * @param myWidth Width of the the RelativeLayout
- * @param myHeight Height of the RelativeLayout
- */
- private void positionChild(View child, LayoutParams params, int myWidth, int myHeight) {
+ private boolean positionChildHorizontal(View child, LayoutParams params, int myWidth,
+ boolean wrapContent) {
+
int[] rules = params.getRules();
if (params.mLeft < 0 && params.mRight >= 0) {
@@ -527,13 +657,26 @@ public class RelativeLayout extends ViewGroup {
params.mRight = params.mLeft + child.getMeasuredWidth();
} else if (params.mLeft < 0 && params.mRight < 0) {
// Both left and right vary
- if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_HORIZONTAL]) {
- centerHorizontal(child, params, myWidth);
+ if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_HORIZONTAL] != 0) {
+ if (!wrapContent) {
+ centerHorizontal(child, params, myWidth);
+ } else {
+ params.mLeft = mPaddingLeft + params.leftMargin;
+ params.mRight = params.mLeft + child.getMeasuredWidth();
+ }
+ return true;
} else {
params.mLeft = mPaddingLeft + params.leftMargin;
params.mRight = params.mLeft + child.getMeasuredWidth();
}
}
+ return false;
+ }
+
+ private boolean positionChildVertical(View child, LayoutParams params, int myHeight,
+ boolean wrapContent) {
+
+ int[] rules = params.getRules();
if (params.mTop < 0 && params.mBottom >= 0) {
// Bottom is fixed, but top varies
@@ -543,26 +686,23 @@ public class RelativeLayout extends ViewGroup {
params.mBottom = params.mTop + child.getMeasuredHeight();
} else if (params.mTop < 0 && params.mBottom < 0) {
// Both top and bottom vary
- if (0 != rules[CENTER_IN_PARENT] || 0 != rules[CENTER_VERTICAL]) {
- centerVertical(child, params, myHeight);
+ if (rules[CENTER_IN_PARENT] != 0 || rules[CENTER_VERTICAL] != 0) {
+ if (!wrapContent) {
+ centerVertical(child, params, myHeight);
+ } else {
+ params.mTop = mPaddingTop + params.topMargin;
+ params.mBottom = params.mTop + child.getMeasuredHeight();
+ }
+ return true;
} else {
params.mTop = mPaddingTop + params.topMargin;
params.mBottom = params.mTop + child.getMeasuredHeight();
}
}
+ return false;
}
- /**
- * Set l,t,r,b values in the LayoutParams for one view based on its layout rules.
- * Big assumption #1: All antecedents of this view have been sized & positioned
- * Big assumption #2: The dimensions of the parent view (the RelativeLayout)
- * are already known if they are needed.
- *
- * @param childParams LayoutParams for the view being positioned
- * @param myWidth Width of the the RelativeLayout
- * @param myHeight Height of the RelativeLayout
- */
- private void applySizeRules(LayoutParams childParams, int myWidth, int myHeight) {
+ private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
int[] rules = childParams.getRules();
RelativeLayout.LayoutParams anchorParams;
@@ -622,6 +762,11 @@ public class RelativeLayout extends ViewGroup {
// FIXME uh oh...
}
}
+ }
+
+ private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) {
+ int[] rules = childParams.getRules();
+ RelativeLayout.LayoutParams anchorParams;
childParams.mTop = -1;
childParams.mBottom = -1;
@@ -684,18 +829,16 @@ public class RelativeLayout extends ViewGroup {
private View getRelatedView(int[] rules, int relation) {
int id = rules[relation];
if (id != 0) {
- View v = findViewById(id);
- if (v == null) {
- return null;
- }
+ DependencyGraph.Node node = mGraph.mKeyNodes.get(id);
+ if (node == null) return null;
+ View v = node.view;
// Find the first non-GONE view up the chain
while (v.getVisibility() == View.GONE) {
rules = ((LayoutParams) v.getLayoutParams()).getRules();
- v = v.findViewById(rules[relation]);
- if (v == null) {
- return null;
- }
+ node = mGraph.mKeyNodes.get((rules[relation]));
+ if (node == null) return null;
+ v = node.view;
}
return v;
@@ -782,6 +925,57 @@ public class RelativeLayout extends ViewGroup {
return new LayoutParams(p);
}
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ if (mTopToBottomLeftToRightSet == null) {
+ mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
+ }
+
+ // sort children top-to-bottom and left-to-right
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ mTopToBottomLeftToRightSet.add(getChildAt(i));
+ }
+
+ for (View view : mTopToBottomLeftToRightSet) {
+ if (view.dispatchPopulateAccessibilityEvent(event)) {
+ mTopToBottomLeftToRightSet.clear();
+ return true;
+ }
+ }
+
+ mTopToBottomLeftToRightSet.clear();
+ return false;
+ }
+
+ /**
+ * Compares two views in left-to-right and top-to-bottom fashion.
+ */
+ private class TopToBottomLeftToRightComparator implements Comparator<View> {
+ public int compare(View first, View second) {
+ // top - bottom
+ int topDifference = first.getTop() - second.getTop();
+ if (topDifference != 0) {
+ return topDifference;
+ }
+ // left - right
+ int leftDifference = first.getLeft() - second.getLeft();
+ if (leftDifference != 0) {
+ return leftDifference;
+ }
+ // break tie by height
+ int heightDiference = first.getHeight() - second.getHeight();
+ if (heightDiference != 0) {
+ return heightDiference;
+ }
+ // break tie by width
+ int widthDiference = first.getWidth() - second.getWidth();
+ if (widthDiference != 0) {
+ return widthDiference;
+ }
+ return 0;
+ }
+ }
+
/**
* Per-child layout information associated with RelativeLayout.
*
@@ -823,7 +1017,7 @@ public class RelativeLayout extends ViewGroup {
@ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf")
}, mapping = {
@ViewDebug.IntToString(from = TRUE, to = "true"),
- @ViewDebug.IntToString(from = 0, to = "FALSE/NO_ID")
+ @ViewDebug.IntToString(from = 0, to = "false/NO_ID")
})
private int[] mRules = new int[VERB_COUNT];
@@ -975,4 +1169,284 @@ public class RelativeLayout extends ViewGroup {
return mRules;
}
}
+
+ private static class DependencyGraph {
+ /**
+ * List of all views in the graph.
+ */
+ private ArrayList<Node> mNodes = new ArrayList<Node>();
+
+ /**
+ * List of nodes in the graph. Each node is identified by its
+ * view id (see View#getId()).
+ */
+ private SparseArray<Node> mKeyNodes = new SparseArray<Node>();
+
+ /**
+ * Temporary data structure used to build the list of roots
+ * for this graph.
+ */
+ private LinkedList<Node> mRoots = new LinkedList<Node>();
+
+ /**
+ * Clears the graph.
+ */
+ void clear() {
+ final ArrayList<Node> nodes = mNodes;
+ final int count = nodes.size();
+
+ for (int i = 0; i < count; i++) {
+ nodes.get(i).release();
+ }
+ nodes.clear();
+
+ mKeyNodes.clear();
+ mRoots.clear();
+ }
+
+ /**
+ * Adds a view to the graph.
+ *
+ * @param view The view to be added as a node to the graph.
+ */
+ void add(View view) {
+ final int id = view.getId();
+ final Node node = Node.acquire(view);
+
+ if (id != View.NO_ID) {
+ mKeyNodes.put(id, node);
+ }
+
+ mNodes.add(node);
+ }
+
+ /**
+ * Builds a sorted list of views. The sorting order depends on the dependencies
+ * between the view. For instance, if view C needs view A to be processed first
+ * and view A needs view B to be processed first, the dependency graph
+ * is: B -> A -> C. The sorted array will contain views B, A and C in this order.
+ *
+ * @param sorted The sorted list of views. The length of this array must
+ * be equal to getChildCount().
+ * @param rules The list of rules to take into account.
+ */
+ void getSortedViews(View[] sorted, int... rules) {
+ final LinkedList<Node> roots = findRoots(rules);
+ int index = 0;
+
+ while (roots.size() > 0) {
+ final Node node = roots.removeFirst();
+ final View view = node.view;
+ final int key = view.getId();
+
+ sorted[index++] = view;
+
+ final HashSet<Node> dependents = node.dependents;
+ for (Node dependent : dependents) {
+ final SparseArray<Node> dependencies = dependent.dependencies;
+
+ dependencies.remove(key);
+ if (dependencies.size() == 0) {
+ roots.add(dependent);
+ }
+ }
+ }
+
+ if (index < sorted.length) {
+ throw new IllegalStateException("Circular dependencies cannot exist"
+ + " in RelativeLayout");
+ }
+ }
+
+ /**
+ * Finds the roots of the graph. A root is a node with no dependency and
+ * with [0..n] dependents.
+ *
+ * @param rulesFilter The list of rules to consider when building the
+ * dependencies
+ *
+ * @return A list of node, each being a root of the graph
+ */
+ private LinkedList<Node> findRoots(int[] rulesFilter) {
+ final SparseArray<Node> keyNodes = mKeyNodes;
+ final ArrayList<Node> nodes = mNodes;
+ final int count = nodes.size();
+
+ // Find roots can be invoked several times, so make sure to clear
+ // all dependents and dependencies before running the algorithm
+ for (int i = 0; i < count; i++) {
+ final Node node = nodes.get(i);
+ node.dependents.clear();
+ node.dependencies.clear();
+ }
+
+ // Builds up the dependents and dependencies for each node of the graph
+ for (int i = 0; i < count; i++) {
+ final Node node = nodes.get(i);
+
+ final LayoutParams layoutParams = (LayoutParams) node.view.getLayoutParams();
+ final int[] rules = layoutParams.mRules;
+ final int rulesCount = rulesFilter.length;
+
+ // Look only the the rules passed in parameter, this way we build only the
+ // dependencies for a specific set of rules
+ for (int j = 0; j < rulesCount; j++) {
+ final int rule = rules[rulesFilter[j]];
+ if (rule > 0) {
+ // The node this node depends on
+ final Node dependency = keyNodes.get(rule);
+ if (dependency == node) {
+ throw new IllegalStateException("A view cannot have a dependency" +
+ " on itself");
+ }
+ if (dependency == null) {
+ continue;
+ }
+ // Add the current node as a dependent
+ dependency.dependents.add(node);
+ // Add a dependency to the current node
+ node.dependencies.put(rule, dependency);
+ }
+ }
+ }
+
+ final LinkedList<Node> roots = mRoots;
+ roots.clear();
+
+ // Finds all the roots in the graph: all nodes with no dependencies
+ for (int i = 0; i < count; i++) {
+ final Node node = nodes.get(i);
+ if (node.dependencies.size() == 0) roots.add(node);
+ }
+
+ return roots;
+ }
+
+ /**
+ * Prints the dependency graph for the specified rules.
+ *
+ * @param resources The context's resources to print the ids.
+ * @param rules The list of rules to take into account.
+ */
+ void log(Resources resources, int... rules) {
+ final LinkedList<Node> roots = findRoots(rules);
+ for (Node node : roots) {
+ printNode(resources, node);
+ }
+ }
+
+ static void printViewId(Resources resources, View view) {
+ if (view.getId() != View.NO_ID) {
+ d(LOG_TAG, resources.getResourceEntryName(view.getId()));
+ } else {
+ d(LOG_TAG, "NO_ID");
+ }
+ }
+
+ private static void appendViewId(Resources resources, Node node, StringBuilder buffer) {
+ if (node.view.getId() != View.NO_ID) {
+ buffer.append(resources.getResourceEntryName(node.view.getId()));
+ } else {
+ buffer.append("NO_ID");
+ }
+ }
+
+ private static void printNode(Resources resources, Node node) {
+ if (node.dependents.size() == 0) {
+ printViewId(resources, node.view);
+ } else {
+ for (Node dependent : node.dependents) {
+ StringBuilder buffer = new StringBuilder();
+ appendViewId(resources, node, buffer);
+ printdependents(resources, dependent, buffer);
+ }
+ }
+ }
+
+ private static void printdependents(Resources resources, Node node, StringBuilder buffer) {
+ buffer.append(" -> ");
+ appendViewId(resources, node, buffer);
+
+ if (node.dependents.size() == 0) {
+ d(LOG_TAG, buffer.toString());
+ } else {
+ for (Node dependent : node.dependents) {
+ StringBuilder subBuffer = new StringBuilder(buffer);
+ printdependents(resources, dependent, subBuffer);
+ }
+ }
+ }
+
+ /**
+ * A node in the dependency graph. A node is a view, its list of dependencies
+ * and its list of dependents.
+ *
+ * A node with no dependent is considered a root of the graph.
+ */
+ static class Node implements Poolable<Node> {
+ /**
+ * The view representing this node in the layout.
+ */
+ View view;
+
+ /**
+ * The list of dependents for this node; a dependent is a node
+ * that needs this node to be processed first.
+ */
+ final HashSet<Node> dependents = new HashSet<Node>();
+
+ /**
+ * The list of dependencies for this node.
+ */
+ final SparseArray<Node> dependencies = new SparseArray<Node>();
+
+ /*
+ * START POOL IMPLEMENTATION
+ */
+ // The pool is static, so all nodes instances are shared across
+ // activities, that's why we give it a rather high limit
+ private static final int POOL_LIMIT = 100;
+ private static final Pool<Node> sPool = Pools.synchronizedPool(
+ Pools.finitePool(new PoolableManager<Node>() {
+ public Node newInstance() {
+ return new Node();
+ }
+
+ public void onAcquired(Node element) {
+ }
+
+ public void onReleased(Node element) {
+ }
+ }, POOL_LIMIT)
+ );
+
+ private Node mNext;
+
+ public void setNextPoolable(Node element) {
+ mNext = element;
+ }
+
+ public Node getNextPoolable() {
+ return mNext;
+ }
+
+ static Node acquire(View view) {
+ final Node node = sPool.acquire();
+ node.view = view;
+
+ return node;
+ }
+
+ void release() {
+ view = null;
+ dependents.clear();
+ dependencies.clear();
+
+ sPool.release(this);
+ }
+ /*
+ * END POOL IMPLEMENTATION
+ */
+ }
+ }
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7936f65..2dac652 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -20,10 +20,8 @@ import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Parcel;
@@ -36,15 +34,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater.Filter;
import android.view.View.OnClickListener;
-import android.view.animation.Animation;
-import android.view.animation.AnimationUtils;
import java.lang.Class;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -69,13 +64,7 @@ public class RemoteViews implements Parcelable, Filter {
* The resource ID of the layout file. (Added to the parcel)
*/
private int mLayoutId;
-
- /**
- * The Context object used to inflate the layout file. Also may
- * be used by actions if they need access to the senders resources.
- */
- private Context mContext;
-
+
/**
* An array of actions to perform on the view tree once it has been
* inflated
@@ -85,7 +74,7 @@ public class RemoteViews implements Parcelable, Filter {
/**
* This annotation indicates that a subclass of View is alllowed to be used
- * with the {@link android.widget.RemoteViews} mechanism.
+ * with the {@link RemoteViews} mechanism.
*/
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@@ -116,7 +105,7 @@ public class RemoteViews implements Parcelable, Filter {
public int describeContents() {
return 0;
}
- };
+ }
/**
* Equivalent to calling
@@ -232,15 +221,17 @@ public class RemoteViews implements Parcelable, Filter {
targetDrawable = imageView.getDrawable();
}
- // Perform modifications only if values are set correctly
- if (alpha != -1) {
- targetDrawable.setAlpha(alpha);
- }
- if (colorFilter != -1 && filterMode != null) {
- targetDrawable.setColorFilter(colorFilter, filterMode);
- }
- if (level != -1) {
- targetDrawable.setLevel(level);
+ if (targetDrawable != null) {
+ // Perform modifications only if values are set correctly
+ if (alpha != -1) {
+ targetDrawable.setAlpha(alpha);
+ }
+ if (colorFilter != -1 && filterMode != null) {
+ targetDrawable.setColorFilter(colorFilter, filterMode);
+ }
+ if (level != -1) {
+ targetDrawable.setLevel(level);
+ }
}
}
@@ -289,6 +280,7 @@ public class RemoteViews implements Parcelable, Filter {
this.viewId = in.readInt();
this.methodName = in.readString();
this.type = in.readInt();
+ //noinspection ConstantIfStatement
if (false) {
Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
+ " methodName=" + this.methodName + " type=" + this.type);
@@ -340,31 +332,32 @@ public class RemoteViews implements Parcelable, Filter {
out.writeInt(this.viewId);
out.writeString(this.methodName);
out.writeInt(this.type);
+ //noinspection ConstantIfStatement
if (false) {
Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
+ " methodName=" + this.methodName + " type=" + this.type);
}
switch (this.type) {
case BOOLEAN:
- out.writeInt(((Boolean)this.value).booleanValue() ? 1 : 0);
+ out.writeInt((Boolean) this.value ? 1 : 0);
break;
case BYTE:
- out.writeByte(((Byte)this.value).byteValue());
+ out.writeByte((Byte) this.value);
break;
case SHORT:
- out.writeInt(((Short)this.value).shortValue());
+ out.writeInt((Short) this.value);
break;
case INT:
- out.writeInt(((Integer)this.value).intValue());
+ out.writeInt((Integer) this.value);
break;
case LONG:
- out.writeLong(((Long)this.value).longValue());
+ out.writeLong((Long) this.value);
break;
case FLOAT:
- out.writeFloat(((Float)this.value).floatValue());
+ out.writeFloat((Float) this.value);
break;
case DOUBLE:
- out.writeDouble(((Double)this.value).doubleValue());
+ out.writeDouble((Double) this.value);
break;
case CHAR:
out.writeInt((int)((Character)this.value).charValue());
@@ -430,7 +423,7 @@ public class RemoteViews implements Parcelable, Filter {
}
Class klass = view.getClass();
- Method method = null;
+ Method method;
try {
method = klass.getMethod(this.methodName, getParameterType());
}
@@ -446,6 +439,7 @@ public class RemoteViews implements Parcelable, Filter {
}
try {
+ //noinspection ConstantIfStatement
if (false) {
Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+ this.methodName + "(" + param.getName() + ") with "
@@ -816,13 +810,12 @@ public class RemoteViews implements Parcelable, Filter {
* @return The inflated view hierarchy
*/
public View apply(Context context, ViewGroup parent) {
- View result = null;
+ View result;
Context c = prepareContext(context);
- Resources r = c.getResources();
- LayoutInflater inflater = (LayoutInflater) c
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ LayoutInflater inflater = (LayoutInflater)
+ c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater = inflater.cloneInContext(c);
inflater.setFilter(this);
@@ -858,12 +851,12 @@ public class RemoteViews implements Parcelable, Filter {
}
private Context prepareContext(Context context) {
- Context c = null;
+ Context c;
String packageName = mPackage;
if (packageName != null) {
try {
- c = context.createPackageContext(packageName, 0);
+ c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED);
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Package name " + packageName + " not found");
c = context;
@@ -872,8 +865,6 @@ public class RemoteViews implements Parcelable, Filter {
c = context;
}
- mContext = c;
-
return c;
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index c9b3751..90e1242 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -115,6 +115,8 @@ public class ScrollView extends FrameLayout {
private boolean mSmoothScrollingEnabled = true;
private int mTouchSlop;
+ private int mMinimumVelocity;
+ private int mMaximumVelocity;
public ScrollView(Context context) {
this(context, null);
@@ -180,7 +182,10 @@ public class ScrollView extends FrameLayout {
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
- mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ final ViewConfiguration configuration = ViewConfiguration.get(mContext);
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
@Override
@@ -478,12 +483,10 @@ public class ScrollView extends FrameLayout {
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000);
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity();
- if ((Math.abs(initialVelocity) >
- ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) &&
- getChildCount() > 0) {
+ if ((Math.abs(initialVelocity) > mMinimumVelocity) && getChildCount() > 0) {
fling(-initialVelocity);
}
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index 92561ed..f706744 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -16,21 +16,22 @@
package android.widget;
-import android.view.ViewGroup;
-import android.view.View;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.SoundEffectConstants;
+import android.R;
import android.content.Context;
import android.content.res.TypedArray;
-import android.util.AttributeSet;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.graphics.Bitmap;
-import android.os.SystemClock;
import android.os.Handler;
import android.os.Message;
-import android.R;
+import android.os.SystemClock;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.SoundEffectConstants;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
/**
* SlidingDrawer hides content out of the screen and allows the user to drag a handle
@@ -746,6 +747,8 @@ public class SlidingDrawer extends ViewGroup {
openDrawer();
invalidate();
requestLayout();
+
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
/**
@@ -777,6 +780,7 @@ public class SlidingDrawer extends ViewGroup {
scrollListener.onScrollStarted();
}
animateClose(mVertical ? mHandle.getTop() : mHandle.getLeft());
+
if (scrollListener != null) {
scrollListener.onScrollEnded();
}
@@ -798,6 +802,9 @@ public class SlidingDrawer extends ViewGroup {
scrollListener.onScrollStarted();
}
animateOpen(mVertical ? mHandle.getTop() : mHandle.getLeft());
+
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+
if (scrollListener != null) {
scrollListener.onScrollEnded();
}
@@ -827,6 +834,7 @@ public class SlidingDrawer extends ViewGroup {
}
mExpanded = true;
+
if (mOnDrawerOpenListener != null) {
mOnDrawerOpenListener.onDrawerOpened();
}
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index dc2c70d..103d44d 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -87,8 +87,9 @@ public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchMode
/**
- * <p>Call setup() before adding tabs if loading TabHost using findViewById(). <i><b>However</i></b>: You do
- * not need to call setup() after getTabHost() in {@link android.app.TabActivity TabActivity}.
+ * <p>Call setup() before adding tabs if loading TabHost using findViewById().
+ * <i><b>However</i></b>: You do not need to call setup() after getTabHost()
+ * in {@link android.app.TabActivity TabActivity}.
* Example:</p>
<pre>mTabHost = (TabHost)findViewById(R.id.tabhost);
mTabHost.setup();
@@ -176,7 +177,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
// leaving touch mode.. if nothing has focus, let's give it to
// the indicator of the current tab
if (!mCurrentView.hasFocus() || mCurrentView.isFocused()) {
- mTabWidget.getChildAt(mCurrentTab).requestFocus();
+ mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
}
}
}
@@ -196,6 +197,12 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
View tabIndicator = tabSpec.mIndicatorStrategy.createIndicatorView();
tabIndicator.setOnKeyListener(mTabKeyListener);
+
+ // If this is a custom view, then do not draw the bottom strips for
+ // the tab indicators.
+ if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) {
+ mTabWidget.setDrawBottomStrips(false);
+ }
mTabWidget.addView(tabIndicator);
mTabSpecs.add(tabSpec);
@@ -234,7 +241,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
public View getCurrentTabView() {
if (mCurrentTab >= 0 && mCurrentTab < mTabSpecs.size()) {
- return mTabWidget.getChildAt(mCurrentTab);
+ return mTabWidget.getChildTabViewAt(mCurrentTab);
}
return null;
}
@@ -272,7 +279,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
&& (mCurrentView.isRootNamespace())
&& (mCurrentView.hasFocus())
&& (mCurrentView.findFocus().focusSearch(View.FOCUS_UP) == null)) {
- mTabWidget.getChildAt(mCurrentTab).requestFocus();
+ mTabWidget.getChildTabViewAt(mCurrentTab).requestFocus();
playSoundEffect(SoundEffectConstants.NAVIGATION_UP);
return true;
}
@@ -363,14 +370,14 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
*
* @param tag
* Which tab was selected.
- * @return The view to distplay the contents of the selected tab.
+ * @return The view to display the contents of the selected tab.
*/
View createTabContent(String tag);
}
/**
- * A tab has a tab indictor, content, and a tag that is used to keep
+ * A tab has a tab indicator, content, and a tag that is used to keep
* track of it. This builder helps choose among these options.
*
* For the tab indicator, your choices are:
@@ -410,6 +417,14 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
/**
+ * Specify a view as the tab indicator.
+ */
+ public TabSpec setIndicator(View view) {
+ mIndicatorStrategy = new ViewIndicatorStrategy(view);
+ return this;
+ }
+
+ /**
* Specify the id of the view that should be used as the content
* of the tab.
*/
@@ -436,7 +451,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
- String getTag() {
+ public String getTag() {
return mTag;
}
}
@@ -525,6 +540,22 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
/**
+ * How to create a tab indicator by specifying a view.
+ */
+ private class ViewIndicatorStrategy implements IndicatorStrategy {
+
+ private final View mView;
+
+ private ViewIndicatorStrategy(View view) {
+ mView = view;
+ }
+
+ public View createIndicatorView() {
+ return mView;
+ }
+ }
+
+ /**
* How to create the tab content via a view id.
*/
private class ViewIdContentStrategy implements ContentStrategy {
@@ -607,7 +638,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
mLaunchedView = wd;
- // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activies for now so they can get
+ // XXX Set FOCUS_AFTER_DESCENDANTS on embedded activities for now so they can get
// focus if none of their children have it. They need focus to be able to
// display menu items.
//
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 20cddcb..a26bfa2 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -49,6 +49,8 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
private Drawable mBottomLeftStrip;
private Drawable mBottomRightStrip;
private boolean mStripMoved;
+ private Drawable mDividerDrawable;
+ private boolean mDrawBottomStrips = true;
public TabWidget(Context context) {
this(context, null);
@@ -87,9 +89,68 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
setOnFocusChangeListener(this);
}
+ /**
+ * Returns the tab indicator view at the given index.
+ *
+ * @param index the zero-based index of the tab indicator view to return
+ * @return the tab indicator view at the given index
+ */
+ public View getChildTabViewAt(int index) {
+ // If we are using dividers, then instead of tab views at 0, 1, 2, ...
+ // we have tab views at 0, 2, 4, ...
+ if (mDividerDrawable != null) {
+ index *= 2;
+ }
+ return getChildAt(index);
+ }
+
+ /**
+ * Returns the number of tab indicator views.
+ * @return the number of tab indicator views.
+ */
+ public int getTabCount() {
+ int children = getChildCount();
+
+ // If we have dividers, then we will always have an odd number of
+ // children: 1, 3, 5, ... and we want to convert that sequence to
+ // this: 1, 2, 3, ...
+ if (mDividerDrawable != null) {
+ children = (children + 1) / 2;
+ }
+ return children;
+ }
+
+ /**
+ * Sets the drawable to use as a divider between the tab indicators.
+ * @param drawable the divider drawable
+ */
+ public void setDividerDrawable(Drawable drawable) {
+ mDividerDrawable = drawable;
+ }
+
+ /**
+ * Sets the drawable to use as a divider between the tab indicators.
+ * @param resId the resource identifier of the drawable to use as a
+ * divider.
+ */
+ public void setDividerDrawable(int resId) {
+ mDividerDrawable = mContext.getResources().getDrawable(resId);
+ }
+
+ /**
+ * Controls whether the bottom strips on the tab indicators are drawn or
+ * not. The default is to draw them. If the user specifies a custom
+ * view for the tab indicators, then the TabHost class calls this method
+ * to disable drawing of the bottom strips.
+ * @param drawBottomStrips true if the bottom strips should be drawn.
+ */
+ void setDrawBottomStrips(boolean drawBottomStrips) {
+ mDrawBottomStrips = drawBottomStrips;
+ }
+
@Override
public void childDrawableStateChanged(View child) {
- if (child == getChildAt(mSelectedTab)) {
+ if (child == getChildTabViewAt(mSelectedTab)) {
// To make sure that the bottom strip is redrawn
invalidate();
}
@@ -100,7 +161,14 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- View selectedChild = getChildAt(mSelectedTab);
+ // If the user specified a custom view for the tab indicators, then
+ // do not draw the bottom strips.
+ if (!mDrawBottomStrips) {
+ // Skip drawing the bottom strips.
+ return;
+ }
+
+ View selectedChild = getChildTabViewAt(mSelectedTab);
mBottomLeftStrip.setState(selectedChild.getDrawableState());
mBottomRightStrip.setState(selectedChild.getDrawableState());
@@ -157,13 +225,13 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
* @see #focusCurrentTab
*/
public void setCurrentTab(int index) {
- if (index < 0 || index >= getChildCount()) {
+ if (index < 0 || index >= getTabCount()) {
return;
}
- getChildAt(mSelectedTab).setSelected(false);
+ getChildTabViewAt(mSelectedTab).setSelected(false);
mSelectedTab = index;
- getChildAt(mSelectedTab).setSelected(true);
+ getChildTabViewAt(mSelectedTab).setSelected(true);
mStripMoved = true;
}
@@ -189,17 +257,17 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
// change the focus if applicable.
if (oldTab != index) {
- getChildAt(index).requestFocus();
+ getChildTabViewAt(index).requestFocus();
}
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
- int count = getChildCount();
+ int count = getTabCount();
- for (int i=0; i<count; i++) {
- View child = getChildAt(i);
+ for (int i = 0; i < count; i++) {
+ View child = getChildTabViewAt(i);
child.setEnabled(enabled);
}
}
@@ -218,17 +286,26 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
child.setFocusable(true);
child.setClickable(true);
+ // If we have dividers between the tabs and we already have at least one
+ // tab, then add a divider before adding the next tab.
+ if (mDividerDrawable != null && getTabCount() > 0) {
+ View divider = new View(mContext);
+ final LinearLayout.LayoutParams lp = new LayoutParams(
+ mDividerDrawable.getIntrinsicWidth(),
+ mDividerDrawable.getIntrinsicHeight());
+ lp.setMargins(0, 0, 0, 0);
+ divider.setLayoutParams(lp);
+ divider.setBackgroundDrawable(mDividerDrawable);
+ super.addView(divider);
+ }
super.addView(child);
// TODO: detect this via geometry with a tabwidget listener rather
// than potentially interfere with the view's listener
- child.setOnClickListener(new TabClickListener(getChildCount() - 1));
+ child.setOnClickListener(new TabClickListener(getTabCount() - 1));
child.setOnFocusChangeListener(this);
}
-
-
-
/**
* Provides a way for {@link TabHost} to be notified that the user clicked on a tab indicator.
*/
@@ -238,14 +315,15 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
public void onFocusChange(View v, boolean hasFocus) {
if (v == this && hasFocus) {
- getChildAt(mSelectedTab).requestFocus();
+ getChildTabViewAt(mSelectedTab).requestFocus();
return;
}
if (hasFocus) {
int i = 0;
- while (i < getChildCount()) {
- if (getChildAt(i) == v) {
+ int numTabs = getTabCount();
+ while (i < numTabs) {
+ if (getChildTabViewAt(i) == v) {
setCurrentTab(i);
mSelectionChangedListener.onTabSelectionChanged(i, false);
break;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index adfc74f..d8ed4f0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -16,6 +16,11 @@
package android.widget;
+import com.android.internal.util.FastMath;
+import com.android.internal.widget.EditableInputConnection;
+
+import org.xmlpull.v1.XmlPullParserException;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
@@ -31,17 +36,17 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ResultReceiver;
import android.os.SystemClock;
-import android.os.Message;
import android.text.BoringLayout;
+import android.text.ClipboardManager;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
import android.text.GraphicsOperations;
-import android.text.ClipboardManager;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
@@ -49,9 +54,9 @@ import android.text.ParcelableSpan;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
+import android.text.SpannableString;
import android.text.Spanned;
import android.text.SpannedString;
-import android.text.SpannableString;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
@@ -64,19 +69,18 @@ import android.text.method.KeyListener;
import android.text.method.LinkMovementMethod;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
-import android.text.method.TimeKeyListener;
-
import android.text.method.PasswordTransformationMethod;
import android.text.method.SingleLineTransformationMethod;
import android.text.method.TextKeyListener;
+import android.text.method.TimeKeyListener;
import android.text.method.TransformationMethod;
import android.text.style.ParagraphStyle;
import android.text.style.URLSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.FloatMath;
+import android.util.Log;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.Gravity;
@@ -89,25 +93,22 @@ import android.view.ViewDebug;
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;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.EditorInfo;
import android.widget.RemoteViews.RemoteView;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import com.android.internal.util.FastMath;
-import com.android.internal.widget.EditableInputConnection;
-
-import org.xmlpull.v1.XmlPullParserException;
-
/**
* Displays text to the user and optionally allows them to edit it. A TextView
* is a complete text editor, however the basic class is configured to not
@@ -126,6 +127,8 @@ import org.xmlpull.v1.XmlPullParserException;
* @attr ref android.R.styleable#TextView_textColor
* @attr ref android.R.styleable#TextView_textColorHighlight
* @attr ref android.R.styleable#TextView_textColorHint
+ * @attr ref android.R.styleable#TextView_textAppearance
+ * @attr ref android.R.styleable#TextView_textColorLink
* @attr ref android.R.styleable#TextView_textSize
* @attr ref android.R.styleable#TextView_textScaleX
* @attr ref android.R.styleable#TextView_typeface
@@ -163,13 +166,22 @@ import org.xmlpull.v1.XmlPullParserException;
* @attr ref android.R.styleable#TextView_capitalize
* @attr ref android.R.styleable#TextView_autoText
* @attr ref android.R.styleable#TextView_editable
+ * @attr ref android.R.styleable#TextView_freezesText
+ * @attr ref android.R.styleable#TextView_ellipsize
* @attr ref android.R.styleable#TextView_drawableTop
* @attr ref android.R.styleable#TextView_drawableBottom
* @attr ref android.R.styleable#TextView_drawableRight
* @attr ref android.R.styleable#TextView_drawableLeft
+ * @attr ref android.R.styleable#TextView_drawablePadding
* @attr ref android.R.styleable#TextView_lineSpacingExtra
* @attr ref android.R.styleable#TextView_lineSpacingMultiplier
* @attr ref android.R.styleable#TextView_marqueeRepeatLimit
+ * @attr ref android.R.styleable#TextView_inputType
+ * @attr ref android.R.styleable#TextView_imeOptions
+ * @attr ref android.R.styleable#TextView_privateImeOptions
+ * @attr ref android.R.styleable#TextView_imeActionLabel
+ * @attr ref android.R.styleable#TextView_imeActionId
+ * @attr ref android.R.styleable#TextView_editorExtras
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
@@ -404,6 +416,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean singleLine = false;
int maxlength = -1;
CharSequence text = "";
+ CharSequence hint = null;
int shadowcolor = 0;
float dx = 0, dy = 0, r = 0;
boolean password = false;
@@ -531,7 +544,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_hint:
- setHint(a.getText(attr));
+ hint = a.getText(attr);
break;
case com.android.internal.R.styleable.TextView_text:
@@ -861,6 +874,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
setText(text, bufferType);
+ if (hint != null) setHint(hint);
/*
* Views are not normally focusable unless specified to be.
@@ -1328,9 +1342,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
// We need to retain the last set padding, so just clear
// out all of the fields in the existing structure.
+ if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
dr.mDrawableLeft = null;
+ if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
dr.mDrawableTop = null;
+ if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
dr.mDrawableRight = null;
+ if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
dr.mDrawableBottom = null;
dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
@@ -1343,19 +1361,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mDrawables = dr = new Drawables();
}
+ if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
+ dr.mDrawableLeft.setCallback(null);
+ }
dr.mDrawableLeft = left;
+ if (dr.mDrawableTop != left && dr.mDrawableTop != null) {
+ dr.mDrawableTop.setCallback(null);
+ }
dr.mDrawableTop = top;
+ if (dr.mDrawableRight != left && dr.mDrawableRight != null) {
+ dr.mDrawableRight.setCallback(null);
+ }
dr.mDrawableRight = right;
+ if (dr.mDrawableBottom != left && dr.mDrawableBottom != null) {
+ dr.mDrawableBottom.setCallback(null);
+ }
dr.mDrawableBottom = bottom;
final Rect compoundRect = dr.mCompoundRect;
- int[] state = null;
+ int[] state;
state = getDrawableState();
if (left != null) {
left.setState(state);
left.copyBounds(compoundRect);
+ left.setCallback(this);
dr.mDrawableSizeLeft = compoundRect.width();
dr.mDrawableHeightLeft = compoundRect.height();
} else {
@@ -1365,6 +1396,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (right != null) {
right.setState(state);
right.copyBounds(compoundRect);
+ right.setCallback(this);
dr.mDrawableSizeRight = compoundRect.width();
dr.mDrawableHeightRight = compoundRect.height();
} else {
@@ -1374,6 +1406,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (top != null) {
top.setState(state);
top.copyBounds(compoundRect);
+ top.setCallback(this);
dr.mDrawableSizeTop = compoundRect.height();
dr.mDrawableWidthTop = compoundRect.width();
} else {
@@ -1383,6 +1416,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (bottom != null) {
bottom.setState(state);
bottom.copyBounds(compoundRect);
+ bottom.setCallback(this);
dr.mDrawableSizeBottom = compoundRect.height();
dr.mDrawableWidthBottom = compoundRect.width();
} else {
@@ -2785,8 +2819,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
checkForRelayout();
}
- if (mText.length() == 0)
+ if (mText.length() == 0) {
invalidate();
+ }
}
/**
@@ -3646,12 +3681,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected boolean isPaddingOffsetRequired() {
- return mShadowRadius != 0;
+ return mShadowRadius != 0 || mDrawables != null;
}
@Override
protected int getLeftPaddingOffset() {
- return (int) Math.min(0, mShadowDx - mShadowRadius);
+ return getCompoundPaddingLeft() - mPaddingLeft +
+ (int) Math.min(0, mShadowDx - mShadowRadius);
}
@Override
@@ -3666,7 +3702,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected int getRightPaddingOffset() {
- return (int) Math.max(0, mShadowDx + mShadowRadius);
+ return -(getCompoundPaddingRight() - mPaddingRight) +
+ (int) Math.max(0, mShadowDx + mShadowRadius);
}
@Override
@@ -3680,6 +3717,54 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
+ public void invalidateDrawable(Drawable drawable) {
+ if (verifyDrawable(drawable)) {
+ final Rect dirty = drawable.getBounds();
+ int scrollX = mScrollX;
+ int scrollY = mScrollY;
+
+ // IMPORTANT: The coordinates below are based on the coordinates computed
+ // for each compound drawable in onDraw(). Make sure to update each section
+ // accordingly.
+ final TextView.Drawables drawables = mDrawables;
+ if (drawables != null) {
+ if (drawable == drawables.mDrawableLeft) {
+ final int compoundPaddingTop = getCompoundPaddingTop();
+ final int compoundPaddingBottom = getCompoundPaddingBottom();
+ final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
+
+ scrollX += mPaddingLeft;
+ scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
+ } else if (drawable == drawables.mDrawableRight) {
+ final int compoundPaddingTop = getCompoundPaddingTop();
+ final int compoundPaddingBottom = getCompoundPaddingBottom();
+ final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
+
+ scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
+ scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
+ } else if (drawable == drawables.mDrawableTop) {
+ final int compoundPaddingLeft = getCompoundPaddingLeft();
+ final int compoundPaddingRight = getCompoundPaddingRight();
+ final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
+
+ scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
+ scrollY += mPaddingTop;
+ } else if (drawable == drawables.mDrawableBottom) {
+ final int compoundPaddingLeft = getCompoundPaddingLeft();
+ final int compoundPaddingRight = getCompoundPaddingRight();
+ final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
+
+ scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
+ scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
+ }
+ }
+
+ invalidate(dirty.left + scrollX, dirty.top + scrollY,
+ dirty.right + scrollX, dirty.bottom + scrollY);
+ }
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
restartMarqueeIfNeeded();
@@ -3707,6 +3792,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
if (dr.mDrawableLeft != null) {
canvas.save();
canvas.translate(scrollX + mPaddingLeft,
@@ -3716,6 +3803,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
canvas.restore();
}
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
if (dr.mDrawableRight != null) {
canvas.save();
canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight,
@@ -3724,6 +3813,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
canvas.restore();
}
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
if (dr.mDrawableTop != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2,
@@ -3732,6 +3823,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
canvas.restore();
}
+ // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
+ // Make sure to update invalidateDrawable() when changing this code.
if (dr.mDrawableBottom != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
@@ -4714,10 +4807,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
alignment = Layout.Alignment.ALIGN_NORMAL;
}
+ boolean shouldEllipsize = mEllipsize != null && mInput == null;
+
if (mText instanceof Spannable) {
mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w,
alignment, mSpacingMult,
- mSpacingAdd, mIncludePad, mEllipsize,
+ mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null,
ellipsisWidth);
} else {
if (boring == UNKNOWN_BORING) {
@@ -4744,7 +4839,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Log.e("aaa", "Boring: " + mTransformed);
mSavedLayout = (BoringLayout) mLayout;
- } else if (mEllipsize != null && boring.width <= w) {
+ } else if (shouldEllipsize && boring.width <= w) {
if (mSavedLayout != null) {
mLayout = mSavedLayout.
replaceOrMake(mTransformed, mTextPaint,
@@ -4757,7 +4852,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boring, mIncludePad, mEllipsize,
ellipsisWidth);
}
- } else if (mEllipsize != null) {
+ } else if (shouldEllipsize) {
mLayout = new StaticLayout(mTransformed,
0, mTransformed.length(),
mTextPaint, w, alignment, mSpacingMult,
@@ -4769,7 +4864,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mIncludePad);
// Log.e("aaa", "Boring but wide: " + mTransformed);
}
- } else if (mEllipsize != null) {
+ } else if (shouldEllipsize) {
mLayout = new StaticLayout(mTransformed,
0, mTransformed.length(),
mTextPaint, w, alignment, mSpacingMult,
@@ -4782,9 +4877,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ shouldEllipsize = mEllipsize != null;
mHintLayout = null;
if (mHint != null) {
+ if (shouldEllipsize) hintWidth = w;
+
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
mHintBoring);
@@ -4794,24 +4892,50 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (hintBoring != null) {
- if (hintBoring.width <= hintWidth) {
+ if (hintBoring.width <= hintWidth &&
+ (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
if (mSavedHintLayout != null) {
mHintLayout = mSavedHintLayout.
replaceOrMake(mHint, mTextPaint,
- hintWidth, alignment, mSpacingMult,
- mSpacingAdd, hintBoring, mIncludePad);
+ hintWidth, alignment, mSpacingMult, mSpacingAdd,
+ hintBoring, mIncludePad);
} else {
mHintLayout = BoringLayout.make(mHint, mTextPaint,
- hintWidth, alignment, mSpacingMult,
- mSpacingAdd, hintBoring, mIncludePad);
+ hintWidth, alignment, mSpacingMult, mSpacingAdd,
+ hintBoring, mIncludePad);
}
mSavedHintLayout = (BoringLayout) mHintLayout;
+ } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
+ if (mSavedHintLayout != null) {
+ mHintLayout = mSavedHintLayout.
+ replaceOrMake(mHint, mTextPaint,
+ hintWidth, alignment, mSpacingMult, mSpacingAdd,
+ hintBoring, mIncludePad, mEllipsize,
+ ellipsisWidth);
+ } else {
+ mHintLayout = BoringLayout.make(mHint, mTextPaint,
+ hintWidth, alignment, mSpacingMult, mSpacingAdd,
+ hintBoring, mIncludePad, mEllipsize,
+ ellipsisWidth);
+ }
+ } else if (shouldEllipsize) {
+ mHintLayout = new StaticLayout(mHint,
+ 0, mHint.length(),
+ mTextPaint, hintWidth, alignment, mSpacingMult,
+ mSpacingAdd, mIncludePad, mEllipsize,
+ ellipsisWidth);
} else {
mHintLayout = new StaticLayout(mHint, mTextPaint,
hintWidth, alignment, mSpacingMult, mSpacingAdd,
mIncludePad);
}
+ } else if (shouldEllipsize) {
+ mHintLayout = new StaticLayout(mHint,
+ 0, mHint.length(),
+ mTextPaint, hintWidth, alignment, mSpacingMult,
+ mSpacingAdd, mIncludePad, mEllipsize,
+ ellipsisWidth);
} else {
mHintLayout = new StaticLayout(mHint, mTextPaint,
hintWidth, alignment, mSpacingMult, mSpacingAdd,
@@ -4895,8 +5019,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private static final BoringLayout.Metrics UNKNOWN_BORING =
- new BoringLayout.Metrics();
+ private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@@ -4923,8 +5046,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (des < 0) {
- boring = BoringLayout.isBoring(mTransformed, mTextPaint,
- mBoring);
+ boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -4934,8 +5056,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (boring == null || boring == UNKNOWN_BORING) {
if (des < 0) {
- des = (int) FloatMath.ceil(Layout.
- getDesiredWidth(mTransformed, mTextPaint));
+ des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
}
width = des;
@@ -4953,13 +5074,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int hintDes = -1;
int hintWidth;
- if (mHintLayout != null) {
+ if (mHintLayout != null && mEllipsize == null) {
hintDes = desired(mHintLayout);
}
if (hintDes < 0) {
- hintBoring = BoringLayout.isBoring(mHint, mTextPaint,
- mHintBoring);
+ hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -4967,8 +5087,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
if (hintDes < 0) {
- hintDes = (int) FloatMath.ceil(Layout.
- getDesiredWidth(mHint, mTextPaint));
+ hintDes = (int) FloatMath.ceil(
+ Layout.getDesiredWidth(mHint, mTextPaint));
}
hintWidth = hintDes;
@@ -5014,20 +5134,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mLayout == null) {
makeNewLayout(want, hintWant, boring, hintBoring,
- width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
- false);
+ width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
} else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) ||
(mLayout.getEllipsizedWidth() !=
width - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
if (mHint == null && mEllipsize == null &&
want > mLayout.getWidth() &&
(mLayout instanceof BoringLayout ||
- (fromexisting && des >= 0 && des <= want))) {
+ (fromexisting && des >= 0 && des <= want))) {
mLayout.increaseWidthTo(want);
} else {
makeNewLayout(want, hintWant, boring, hintBoring,
- width - getCompoundPaddingLeft() - getCompoundPaddingRight(),
- false);
+ width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
}
} else {
// Width has not changed.
@@ -5048,11 +5166,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- int unpaddedHeight = height - getCompoundPaddingTop() -
- getCompoundPaddingBottom();
+ int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
- unpaddedHeight = Math.min(unpaddedHeight,
- mLayout.getLineTop(mMaximum));
+ unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
}
/*
@@ -5071,8 +5187,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private int getDesiredHeight() {
- return Math.max(getDesiredHeight(mLayout, true),
- getDesiredHeight(mHintLayout, false));
+ return Math.max(
+ getDesiredHeight(mLayout, true),
+ getDesiredHeight(mHintLayout, mEllipsize != null));
}
private int getDesiredHeight(Layout layout, boolean cap) {
@@ -5715,6 +5832,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void startMarquee() {
+ // Do not ellipsize EditText
+ if (mInput != null) return;
+
if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
return;
}
@@ -6129,10 +6249,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private class ChangeWatcher
implements TextWatcher, SpanWatcher {
+
+ private CharSequence mBeforeText;
+
public void beforeTextChanged(CharSequence buffer, int start,
int before, int after) {
if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ mBeforeText = buffer.toString();
+ }
+
TextView.this.sendBeforeTextChanged(buffer, start, before, after);
}
@@ -6141,6 +6269,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
TextView.this.handleTextChanged(buffer, start, before, after);
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled() &&
+ (isFocused() || isSelected() &&
+ isShown())) {
+ sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
+ mBeforeText = null;
+ }
}
public void afterTextChanged(Editable buffer) {
@@ -6336,6 +6471,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode != InputMethodManager.RESULT_SHOWN) {
+ final int len = mText.length();
+ if (mNewStart > len) {
+ mNewStart = len;
+ }
+ if (mNewEnd > len) {
+ mNewEnd = len;
+ }
Selection.setSelection((Spannable)mText, mNewStart, mNewEnd);
}
}
@@ -6525,9 +6667,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else if (getLineCount() == 1) {
switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
- return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) -
- getCompoundPaddingLeft() - getCompoundPaddingRight()) /
- getHorizontalFadingEdgeLength();
+ final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
+ getCompoundPaddingRight();
+ final float lineWidth = mLayout.getLineWidth(0);
+ return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
case Gravity.RIGHT:
return 0.0f;
case Gravity.CENTER_HORIZONTAL:
@@ -6776,6 +6919,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ boolean isPassword =
+ (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) ==
+ (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
+
+ if (!isPassword) {
+ CharSequence text = getText();
+ if (TextUtils.isEmpty(text)) {
+ text = getHint();
+ }
+ if (!TextUtils.isEmpty(text)) {
+ if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) {
+ text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1);
+ }
+ event.getText().add(text);
+ }
+ } else {
+ event.setPassword(isPassword);
+ }
+ return false;
+ }
+
+ void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
+ int fromIndex, int removedCount, int addedCount) {
+ AccessibilityEvent event =
+ AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ event.setFromIndex(fromIndex);
+ event.setRemovedCount(removedCount);
+ event.setAddedCount(addedCount);
+ event.setBeforeText(beforeText);
+ sendAccessibilityEventUnchecked(event);
+ }
+
+ @Override
protected void onCreateContextMenu(ContextMenu menu) {
super.onCreateContextMenu(menu);
boolean added = false;
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index ff74787..670692f 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -21,8 +21,8 @@ import android.app.ITransientNotification;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
-import android.os.RemoteException;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.view.Gravity;
@@ -278,7 +278,7 @@ public class Toast {
}
tv.setText(s);
}
-
+
// =======================================================================================
// All the gunk below is the interaction with the Notification Service, which handles
// the proper ordering of these system-wide.
@@ -373,6 +373,7 @@ public class Toast {
TAG, "REMOVE! " + mView + " in " + this);
mWM.removeView(mView);
}
+
mView = null;
}
}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 6d3a2d3..20dd8a6 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -55,6 +55,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
private SurfaceHolder mSurfaceHolder = null;
private MediaPlayer mMediaPlayer = null;
private boolean mIsPrepared;
+ private boolean mIsPlaybackCompleted;
private int mVideoWidth;
private int mVideoHeight;
private int mSurfaceWidth;
@@ -260,7 +261,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mSeekWhenPrepared = 0;
}
if (mStartWhenPrepared) {
- mMediaPlayer.start();
+ start();
mStartWhenPrepared = false;
if (mMediaController != null) {
mMediaController.show();
@@ -281,7 +282,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mSeekWhenPrepared = 0;
}
if (mStartWhenPrepared) {
- mMediaPlayer.start();
+ start();
mStartWhenPrepared = false;
}
}
@@ -291,6 +292,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
private MediaPlayer.OnCompletionListener mCompletionListener =
new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
+ mIsPlaybackCompleted = true;
if (mMediaController != null) {
mMediaController.hide();
}
@@ -405,7 +407,9 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
mMediaPlayer.seekTo(mSeekWhenPrepared);
mSeekWhenPrepared = 0;
}
- mMediaPlayer.start();
+ if (!mIsPlaybackCompleted) {
+ start();
+ }
if (mMediaController != null) {
mMediaController.show();
}
@@ -490,6 +494,7 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
public void start() {
+ mIsPlaybackCompleted = false;
if (mMediaPlayer != null && mIsPrepared) {
mMediaPlayer.start();
mStartWhenPrepared = false;
diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java
index f4f23a8..0dcaf95 100644
--- a/core/java/android/widget/ViewSwitcher.java
+++ b/core/java/android/widget/ViewSwitcher.java
@@ -16,8 +16,6 @@
package android.widget;
-import java.util.Map;
-
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java
index d9fb78b..bae4dad 100644
--- a/core/java/android/widget/ZoomButtonsController.java
+++ b/core/java/android/widget/ZoomButtonsController.java
@@ -81,27 +81,27 @@ public class ZoomButtonsController implements View.OnTouchListener {
private static final int ZOOM_CONTROLS_TOUCH_PADDING = 20;
private int mTouchPaddingScaledSq;
- private Context mContext;
- private WindowManager mWindowManager;
+ private final Context mContext;
+ private final WindowManager mWindowManager;
private boolean mAutoDismissControls = true;
/**
* The view that is being zoomed by this zoom controller.
*/
- private View mOwnerView;
+ private final View mOwnerView;
/**
* The location of the owner view on the screen. This is recalculated
* each time the zoom controller is shown.
*/
- private int[] mOwnerViewRawLocation = new int[2];
+ private final int[] mOwnerViewRawLocation = new int[2];
/**
* The container that is added as a window.
*/
- private FrameLayout mContainer;
+ private final FrameLayout mContainer;
private LayoutParams mContainerLayoutParams;
- private int[] mContainerRawLocation = new int[2];
+ private final int[] mContainerRawLocation = new int[2];
private ZoomControls mControls;
@@ -113,7 +113,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
/**
* The {@link #mTouchTargetView}'s location in window, set on touch down.
*/
- private int[] mTouchTargetWindowLocation = new int[2];
+ private final int[] mTouchTargetWindowLocation = new int[2];
/**
* If the zoom controller is dismissed but the user is still in a touch
@@ -128,8 +128,8 @@ public class ZoomButtonsController implements View.OnTouchListener {
/** Whether the container has been added to the window manager. */
private boolean mIsVisible;
- private Rect mTempRect = new Rect();
- private int[] mTempIntArray = new int[2];
+ private final Rect mTempRect = new Rect();
+ private final int[] mTempIntArray = new int[2];
private OnZoomListener mCallback;
@@ -141,13 +141,13 @@ public class ZoomButtonsController implements View.OnTouchListener {
*/
private Runnable mPostedVisibleInitializer;
- private IntentFilter mConfigurationChangedFilter =
+ private final IntentFilter mConfigurationChangedFilter =
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED);
/**
* Needed to reposition the zoom controls after configuration changes.
*/
- private BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mConfigurationChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!mIsVisible) return;
@@ -167,7 +167,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
*/
private static final int MSG_POST_SET_VISIBLE = 4;
- private Handler mHandler = new Handler() {
+ private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -444,6 +444,9 @@ public class ZoomButtonsController implements View.OnTouchListener {
}
private void refreshPositioningVariables() {
+ // if the mOwnerView is detached from window then skip.
+ if (mOwnerView.getWindowToken() == null) return;
+
// Position the zoom controls on the bottom of the owner view.
int ownerHeight = mOwnerView.getHeight();
int ownerWidth = mOwnerView.getWidth();
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index e1ff2a5..4bac593 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -18,6 +18,8 @@ package com.android.internal.app;
import com.android.internal.os.BatteryStatsImpl;
+import android.telephony.SignalStrength;
+
interface IBatteryStats {
byte[] getStatistics();
void noteStartWakelock(int uid, String name, int type);
@@ -33,8 +35,9 @@ interface IBatteryStats {
void noteUserActivity(int uid, int event);
void notePhoneOn();
void notePhoneOff();
- void notePhoneSignalStrength(int asu);
+ void notePhoneSignalStrength(in SignalStrength signalStrength);
void notePhoneDataConnectionState(int dataType, boolean hasData);
+ void noteAirplaneMode(boolean isAirplaneMode);
void noteWifiOn(int uid);
void noteWifiOff(int uid);
void noteWifiRunning();
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index ce39768..af06965 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -16,6 +16,8 @@
package com.android.internal.backup;
+import android.backup.RestoreSet;
+import android.content.pm.PackageInfo;
import android.os.ParcelFileDescriptor;
/** {@hide} */
@@ -25,7 +27,7 @@ interface IBackupTransport {
1. set up the connection to the destination
- set up encryption
- for Google cloud, log in using the user's gaia credential or whatever
- - for sd, spin off the backup transport and establish communication with it
+ - for adb, just set up the all-in-one destination file
2. send each app's backup transaction
- parse the data file for key/value pointers etc
- send key/blobsize set to the Google cloud, get back quota ok/rejected response
@@ -36,34 +38,112 @@ interface IBackupTransport {
- sd target streams raw data into encryption envelope then to sd?
3. shut down connection to destination
- cloud: tear down connection etc
- - sd: close the file and shut down the writer proxy
+ - adb: close the file
*/
/**
- * Establish a connection to the back-end data repository, if necessary. If the transport
- * needs to initialize state that is not tied to individual applications' backup operations,
- * this is where it should be done.
+ * Ask the transport where, on local device storage, to keep backup state blobs.
+ * This is per-transport so that mock transports used for testing can coexist with
+ * "live" backup services without interfering with the live bookkeeping. The
+ * returned string should be a name that is expected to be unambiguous among all
+ * available backup transports; the name of the class implementing the transport
+ * is a good choice.
*
- * @return Zero on success; a nonzero error code on failure.
+ * @return A unique name, suitable for use as a file or directory name, that the
+ * Backup Manager could use to disambiguate state files associated with
+ * different backup transports.
*/
- int startSession();
+ String transportDirName();
/**
- * Send one application's data to the backup destination.
+ * Verify that this is a suitable time for a backup pass. This should return zero
+ * if a backup is reasonable right now, some positive value otherwise. This method
+ * will be called outside of the {@link #startSession}/{@link #endSession} pair.
*
- * @param packageName The identity of the application whose data is being backed up.
+ * <p>If this is not a suitable time for a backup, the transport should return a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ *
+ * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+ * in milliseconds to suggest deferring the backup pass for a while.
+ */
+ long requestBackupTime();
+
+ /**
+ * Send one application's data to the backup destination. The transport may send
+ * the data immediately, or may buffer it. After this is called, {@link #finishBackup}
+ * must be called to ensure the data is sent and recorded successfully.
+ *
+ * @param packageInfo The identity of the application whose data is being backed up.
+ * This specifically includes the signature list for the package.
* @param data The data stream that resulted from invoking the application's
- * BackupService.doBackup() method. This may be a pipe rather than a
- * file on persistent media, so it may not be seekable.
- * @return Zero on success; a nonzero error code on failure.
+ * BackupService.doBackup() method. This may be a pipe rather than a file on
+ * persistent media, so it may not be seekable.
+ * @return false if errors occurred (the backup should be aborted and rescheduled),
+ * true if everything is OK so far (but {@link #finishBackup} must be called).
+ */
+ boolean performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd);
+
+ /**
+ * Erase the give application's data from the backup destination. This clears
+ * out the given package's data from the current backup set, making it as though
+ * the app had never yet been backed up. After this is called, {@link finishBackup}
+ * must be called to ensure that the operation is recorded successfully.
+ *
+ * @return false if errors occurred (the backup should be aborted and rescheduled),
+ * true if everything is OK so far (but {@link #finishBackup} must be called).
+ */
+ boolean clearBackupData(in PackageInfo packageInfo);
+
+ /**
+ * Finish sending application data to the backup destination. This must be
+ * called after {@link #performBackup} or {@link clearBackupData} to ensure that
+ * all data is sent. Only when this method returns true can a backup be assumed
+ * to have succeeded.
+ *
+ * @return false if errors occurred (the backup should be aborted and rescheduled),
+ * true if everything is OK.
*/
- int performBackup(String packageName, in ParcelFileDescriptor data);
+ boolean finishBackup();
/**
- * Terminate the backup session, closing files, freeing memory, and cleaning up whatever
- * other state the transport required.
+ * Get the set of backups currently available over this transport.
*
- * @return Zero on success; a nonzero error code on failure. Even on failure, the session
- * is torn down and must be restarted if another backup is attempted.
+ * @return Descriptions of the set of restore images available for this device,
+ * or null if an error occurred (the attempt should be rescheduled).
+ **/
+ RestoreSet[] getAvailableRestoreSets();
+
+ /**
+ * Start restoring application data from backup. After calling this function,
+ * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
+ * to walk through the actual application data.
+ *
+ * @param token A backup token as returned by {@link #getAvailableRestoreSets}.
+ * @param packages List of applications to restore (if data is available).
+ * Application data will be restored in the order given.
+ * @return false if errors occurred (the restore should be aborted and rescheduled),
+ * true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
+ */
+ boolean startRestore(long token, in PackageInfo[] packages);
+
+ /**
+ * Get the package name of the next application with data in the backup store.
+ * @return The name of one of the packages supplied to {@link #startRestore},
+ * or "" (the empty string) if no more backup data is available,
+ * or null if an error occurred (the restore should be aborted and rescheduled).
+ */
+ String nextRestorePackage();
+
+ /**
+ * Get the data for the application returned by {@link #nextRestorePackage}.
+ * @param data An open, writable file into which the backup data should be stored.
+ * @return false if errors occurred (the restore should be aborted and rescheduled),
+ * true if everything is OK so far (go ahead and call {@link #nextRestorePackage}).
+ */
+ boolean getRestoreData(in ParcelFileDescriptor outFd);
+
+ /**
+ * End a restore session (aborting any in-process data transfer as necessary),
+ * freeing any resources and connections used during the restore process.
*/
- int endSession();
+ void finishRestore();
}
diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java
new file mode 100644
index 0000000..2facce2
--- /dev/null
+++ b/core/java/com/android/internal/backup/LocalTransport.java
@@ -0,0 +1,200 @@
+package com.android.internal.backup;
+
+import android.backup.BackupDataInput;
+import android.backup.BackupDataOutput;
+import android.backup.RestoreSet;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import org.bouncycastle.util.encoders.Base64;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Backup transport for stashing stuff into a known location on disk, and
+ * later restoring from there. For testing only.
+ */
+
+public class LocalTransport extends IBackupTransport.Stub {
+ private static final String TAG = "LocalTransport";
+ private static final boolean DEBUG = true;
+
+ private static final String TRANSPORT_DIR_NAME
+ = "com.android.internal.backup.LocalTransport";
+
+ private Context mContext;
+ private PackageManager mPackageManager;
+ private File mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup");
+ private PackageInfo[] mRestorePackages = null;
+ private int mRestorePackage = -1; // Index into mRestorePackages
+
+
+ public LocalTransport(Context context) {
+ if (DEBUG) Log.v(TAG, "Transport constructed");
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ }
+
+
+ public String transportDirName() throws RemoteException {
+ return TRANSPORT_DIR_NAME;
+ }
+
+ public long requestBackupTime() throws RemoteException {
+ // any time is a good time for local backup
+ return 0;
+ }
+
+ public boolean performBackup(PackageInfo packageInfo, ParcelFileDescriptor data)
+ throws RemoteException {
+ if (DEBUG) Log.v(TAG, "performBackup() pkg=" + packageInfo.packageName);
+
+ File packageDir = new File(mDataDir, packageInfo.packageName);
+ packageDir.mkdirs();
+
+ // Each 'record' in the restore set is kept in its own file, named by
+ // the record key. Wind through the data file, extracting individual
+ // record operations and building a set of all the updates to apply
+ // in this update.
+ BackupDataInput changeSet = new BackupDataInput(data.getFileDescriptor());
+ try {
+ int bufSize = 512;
+ byte[] buf = new byte[bufSize];
+ while (changeSet.readNextHeader()) {
+ String key = changeSet.getKey();
+ String base64Key = new String(Base64.encode(key.getBytes()));
+ File entityFile = new File(packageDir, base64Key);
+
+ int dataSize = changeSet.getDataSize();
+
+ if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize
+ + " key64=" + base64Key);
+
+ if (dataSize >= 0) {
+ FileOutputStream entity = new FileOutputStream(entityFile);
+
+ if (dataSize > bufSize) {
+ bufSize = dataSize;
+ buf = new byte[bufSize];
+ }
+ changeSet.readEntityData(buf, 0, dataSize);
+ if (DEBUG) Log.v(TAG, " data size " + dataSize);
+
+ try {
+ entity.write(buf, 0, dataSize);
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
+ return false;
+ } finally {
+ entity.close();
+ }
+ } else {
+ entityFile.delete();
+ }
+ }
+ return true;
+ } catch (IOException e) {
+ // oops, something went wrong. abort the operation and return error.
+ Log.v(TAG, "Exception reading backup input:", e);
+ return false;
+ }
+ }
+
+ public boolean clearBackupData(PackageInfo packageInfo) {
+ if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
+
+ File packageDir = new File(mDataDir, packageInfo.packageName);
+ for (File f : packageDir.listFiles()) {
+ f.delete();
+ }
+ packageDir.delete();
+ return true;
+ }
+
+ public boolean finishBackup() throws RemoteException {
+ if (DEBUG) Log.v(TAG, "finishBackup()");
+ return true;
+ }
+
+ // Restore handling
+ public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException {
+ // one hardcoded restore set
+ RestoreSet set = new RestoreSet("Local disk image", "flash", 0);
+ RestoreSet[] array = { set };
+ return array;
+ }
+
+ public boolean startRestore(long token, PackageInfo[] packages) {
+ if (DEBUG) Log.v(TAG, "start restore " + token);
+ mRestorePackages = packages;
+ mRestorePackage = -1;
+ return true;
+ }
+
+ public String nextRestorePackage() {
+ if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
+ while (++mRestorePackage < mRestorePackages.length) {
+ String name = mRestorePackages[mRestorePackage].packageName;
+ if (new File(mDataDir, name).isDirectory()) {
+ if (DEBUG) Log.v(TAG, " nextRestorePackage() = " + name);
+ return name;
+ }
+ }
+
+ if (DEBUG) Log.v(TAG, " no more packages to restore");
+ return "";
+ }
+
+ public boolean getRestoreData(ParcelFileDescriptor outFd) {
+ if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
+ if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
+ File packageDir = new File(mDataDir, mRestorePackages[mRestorePackage].packageName);
+
+ // The restore set is the concatenation of the individual record blobs,
+ // each of which is a file in the package's directory
+ File[] blobs = packageDir.listFiles();
+ if (blobs == null) {
+ Log.e(TAG, "Error listing directory: " + packageDir);
+ return false; // nextRestorePackage() ensures the dir exists, so this is an error
+ }
+
+ // We expect at least some data if the directory exists in the first place
+ if (DEBUG) Log.v(TAG, " getRestoreData() found " + blobs.length + " key files");
+ BackupDataOutput out = new BackupDataOutput(outFd.getFileDescriptor());
+ try {
+ for (File f : blobs) {
+ FileInputStream in = new FileInputStream(f);
+ try {
+ int size = (int) f.length();
+ byte[] buf = new byte[size];
+ in.read(buf);
+ String key = new String(Base64.decode(f.getName()));
+ if (DEBUG) Log.v(TAG, " ... key=" + key + " size=" + size);
+ out.writeEntityHeader(key, size);
+ out.writeEntityData(buf, size);
+ } finally {
+ in.close();
+ }
+ }
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read backup records", e);
+ return false;
+ }
+ }
+
+ public void finishRestore() {
+ if (DEBUG) Log.v(TAG, "finishRestore()");
+ }
+}
diff --git a/core/java/com/android/internal/backup/SystemBackupAgent.java b/core/java/com/android/internal/backup/SystemBackupAgent.java
new file mode 100644
index 0000000..6b396d7
--- /dev/null
+++ b/core/java/com/android/internal/backup/SystemBackupAgent.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.android.internal.backup;
+
+import android.backup.AbsoluteFileBackupHelper;
+import android.backup.BackupHelperAgent;
+
+/**
+ * Backup agent for various system-managed data
+ */
+public class SystemBackupAgent extends BackupHelperAgent {
+ // the set of files that we back up whole, as absolute paths
+ String[] mFiles = {
+ /* WallpaperService.WALLPAPER_FILE */
+ "/data/data/com.android.settings/files/wallpaper",
+ };
+
+ public void onCreate() {
+ addHelper("system_files", new AbsoluteFileBackupHelper(this, mFiles));
+ }
+}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e8356a2..a03802d 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -23,23 +23,24 @@ import android.os.ParcelFormatException;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
+import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.SparseArray;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
-import java.util.Set;
/**
* All information we are collecting about things that can happen that impact
@@ -54,7 +55,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 34;
+ private static final int VERSION = 39;
private final File mFile;
private final File mBackupFile;
@@ -95,7 +96,7 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mScreenOn;
StopwatchTimer mScreenOnTimer;
-
+
int mScreenBrightnessBin = -1;
final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -104,6 +105,12 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mPhoneOn;
StopwatchTimer mPhoneOnTimer;
+ boolean mAudioOn;
+ StopwatchTimer mAudioOnTimer;
+
+ boolean mVideoOn;
+ StopwatchTimer mVideoOnTimer;
+
int mPhoneSignalStrengthBin = -1;
final StopwatchTimer[] mPhoneSignalStrengthsTimer =
new StopwatchTimer[NUM_SIGNAL_STRENGTH_BINS];
@@ -132,18 +139,27 @@ public final class BatteryStatsImpl extends BatteryStats {
long mTrackBatteryUptimeStart;
long mTrackBatteryPastRealtime;
long mTrackBatteryRealtimeStart;
-
+
long mUnpluggedBatteryUptime;
long mUnpluggedBatteryRealtime;
-
+
/*
* These keep track of battery levels (1-100) at the last plug event and the last unplug event.
*/
int mDischargeStartLevel;
int mDischargeCurrentLevel;
-
+
long mLastWriteTime = 0; // Milliseconds
-
+
+ // Mobile data transferred while on battery
+ private long[] mMobileDataTx = new long[4];
+ private long[] mMobileDataRx = new long[4];
+ private long[] mTotalDataTx = new long[4];
+ private long[] mTotalDataRx = new long[4];
+
+ private long mRadioDataUptime;
+ private long mRadioDataStart;
+
/*
* Holds a SamplingTimer associated with each kernel wakelock name being tracked.
*/
@@ -175,6 +191,8 @@ public final class BatteryStatsImpl extends BatteryStats {
private final Map<String, KernelWakelockStats> mProcWakelockFileStats =
new HashMap<String, KernelWakelockStats>();
+ private HashMap<String, Integer> mUidCache = new HashMap<String, Integer>();
+
// For debugging
public BatteryStatsImpl() {
mFile = mBackupFile = null;
@@ -319,6 +337,13 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
long mUnpluggedTime;
+ /**
+ * Constructs from a parcel.
+ * @param type
+ * @param unpluggables
+ * @param powerType
+ * @param in
+ */
Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) {
mType = type;
@@ -631,7 +656,6 @@ public final class BatteryStatsImpl extends BatteryStats {
* was actually held for an interesting duration.
*/
long mAcquireTime;
-
StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool,
ArrayList<Unpluggable> unpluggables, Parcel in) {
@@ -692,6 +716,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ boolean isRunningLocked() {
+ return mNesting > 0;
+ }
+
void stopRunningLocked(BatteryStatsImpl stats) {
// Ignore attempt to stop a timer that isn't running
if (mNesting == 0) {
@@ -882,7 +910,40 @@ public final class BatteryStatsImpl extends BatteryStats {
}
return kwlt;
}
-
+
+ private void doDataPlug(long[] dataTransfer, long currentBytes) {
+ dataTransfer[STATS_LAST] = dataTransfer[STATS_UNPLUGGED];
+ dataTransfer[STATS_UNPLUGGED] = -1;
+ }
+
+ private void doDataUnplug(long[] dataTransfer, long currentBytes) {
+ dataTransfer[STATS_UNPLUGGED] = currentBytes;
+ }
+
+ private long getCurrentRadioDataUptimeMs() {
+ try {
+ File awakeTimeFile = new File("/sys/devices/virtual/net/rmnet0/awake_time_ms");
+ if (!awakeTimeFile.exists()) return 0;
+ BufferedReader br = new BufferedReader(new FileReader(awakeTimeFile));
+ String line = br.readLine();
+ br.close();
+ return Long.parseLong(line);
+ } catch (NumberFormatException nfe) {
+ // Nothing
+ } catch (IOException ioe) {
+ // Nothing
+ }
+ return 0;
+ }
+
+ public long getRadioDataUptimeMs() {
+ if (mRadioDataStart == -1) {
+ return mRadioDataUptime;
+ } else {
+ return getCurrentRadioDataUptimeMs() - mRadioDataStart;
+ }
+ }
+
public void doUnplug(long batteryUptime, long batteryRealtime) {
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
Uid u = mUidStats.valueAt(iu);
@@ -894,8 +955,16 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
}
+ // Track total mobile data
+ doDataUnplug(mMobileDataRx, NetStat.getMobileRxBytes());
+ doDataUnplug(mMobileDataTx, NetStat.getMobileTxBytes());
+ doDataUnplug(mTotalDataRx, NetStat.getTotalRxBytes());
+ doDataUnplug(mTotalDataTx, NetStat.getTotalTxBytes());
+ // Track radio awake time
+ mRadioDataStart = getCurrentRadioDataUptimeMs();
+ mRadioDataUptime = 0;
}
-
+
public void doPlug(long batteryUptime, long batteryRealtime) {
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
Uid u = mUidStats.valueAt(iu);
@@ -911,16 +980,23 @@ public final class BatteryStatsImpl extends BatteryStats {
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
}
+ doDataPlug(mMobileDataRx, NetStat.getMobileRxBytes());
+ doDataPlug(mMobileDataTx, NetStat.getMobileTxBytes());
+ doDataPlug(mTotalDataRx, NetStat.getTotalRxBytes());
+ doDataPlug(mTotalDataTx, NetStat.getTotalTxBytes());
+ // Track radio awake time
+ mRadioDataUptime = getRadioDataUptimeMs();
+ mRadioDataStart = -1;
}
-
+
public void noteStartGps(int uid) {
- mUidStats.get(uid).noteStartGps();
+ getUidStatsLocked(uid).noteStartGps();
}
public void noteStopGps(int uid) {
- mUidStats.get(uid).noteStopGps();
+ getUidStatsLocked(uid).noteStopGps();
}
-
+
public void noteScreenOnLocked() {
if (!mScreenOn) {
mScreenOn = true;
@@ -962,10 +1038,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void noteUserActivityLocked(int uid, int event) {
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteUserActivityLocked(event);
- }
+ getUidStatsLocked(uid).noteUserActivityLocked(event);
}
public void notePhoneOnLocked() {
@@ -981,15 +1054,43 @@ public final class BatteryStatsImpl extends BatteryStats {
mPhoneOnTimer.stopRunningLocked(this);
}
}
-
- public void notePhoneSignalStrengthLocked(int asu) {
+
+ public void noteAirplaneModeLocked(boolean isAirplaneMode) {
+ final int bin = mPhoneSignalStrengthBin;
+ if (bin >= 0) {
+ if (!isAirplaneMode) {
+ if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) {
+ mPhoneSignalStrengthsTimer[bin].startRunningLocked(this);
+ }
+ } else {
+ for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) {
+ while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) {
+ mPhoneSignalStrengthsTimer[i].stopRunningLocked(this);
+ }
+ }
+ }
+ }
+ }
+
+ public void notePhoneSignalStrengthLocked(SignalStrength signalStrength) {
// Bin the strength.
int bin;
- if (asu < 0 || asu >= 99) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
- else if (asu >= 16) bin = SIGNAL_STRENGTH_GREAT;
- else if (asu >= 8) bin = SIGNAL_STRENGTH_GOOD;
- else if (asu >= 4) bin = SIGNAL_STRENGTH_MODERATE;
- else bin = SIGNAL_STRENGTH_POOR;
+
+ if (!signalStrength.isGsm()) {
+ int dBm = signalStrength.getCdmaDbm();
+ if (dBm >= -75) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ else if (dBm >= -85) bin = SIGNAL_STRENGTH_GREAT;
+ else if (dBm >= -95) bin = SIGNAL_STRENGTH_GOOD;
+ else if (dBm >= -100) bin = SIGNAL_STRENGTH_MODERATE;
+ else bin = SIGNAL_STRENGTH_POOR;
+ } else {
+ int asu = signalStrength.getGsmSignalStrength();
+ if (asu < 0 || asu >= 99) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ else if (asu >= 16) bin = SIGNAL_STRENGTH_GREAT;
+ else if (asu >= 8) bin = SIGNAL_STRENGTH_GOOD;
+ else if (asu >= 4) bin = SIGNAL_STRENGTH_MODERATE;
+ else bin = SIGNAL_STRENGTH_POOR;
+ }
if (mPhoneSignalStrengthBin != bin) {
if (mPhoneSignalStrengthBin >= 0) {
mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this);
@@ -1017,6 +1118,7 @@ public final class BatteryStatsImpl extends BatteryStats {
break;
}
}
+ if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
if (mPhoneDataConnectionType >= 0) {
mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this);
@@ -1033,16 +1135,10 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (mWifiOnUid != uid) {
if (mWifiOnUid >= 0) {
- Uid u = mUidStats.get(mWifiOnUid);
- if (u != null) {
- u.noteWifiTurnedOffLocked();
- }
+ getUidStatsLocked(mWifiOnUid).noteWifiTurnedOffLocked();
}
mWifiOnUid = uid;
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteWifiTurnedOnLocked();
- }
+ getUidStatsLocked(uid).noteWifiTurnedOnLocked();
}
}
@@ -1052,14 +1148,43 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiOnTimer.stopRunningLocked(this);
}
if (mWifiOnUid >= 0) {
- Uid u = mUidStats.get(mWifiOnUid);
- if (u != null) {
- u.noteWifiTurnedOffLocked();
- }
+ getUidStatsLocked(mWifiOnUid).noteWifiTurnedOffLocked();
mWifiOnUid = -1;
}
}
+
+ public void noteAudioOnLocked(int uid) {
+ if (!mAudioOn) {
+ mAudioOn = true;
+ mAudioOnTimer.startRunningLocked(this);
+ }
+ getUidStatsLocked(uid).noteAudioTurnedOnLocked();
+ }
+ public void noteAudioOffLocked(int uid) {
+ if (mAudioOn) {
+ mAudioOn = false;
+ mAudioOnTimer.stopRunningLocked(this);
+ }
+ getUidStatsLocked(uid).noteAudioTurnedOffLocked();
+ }
+
+ public void noteVideoOnLocked(int uid) {
+ if (!mVideoOn) {
+ mVideoOn = true;
+ mVideoOnTimer.startRunningLocked(this);
+ }
+ getUidStatsLocked(uid).noteVideoTurnedOnLocked();
+ }
+
+ public void noteVideoOffLocked(int uid) {
+ if (mVideoOn) {
+ mVideoOn = false;
+ mVideoOnTimer.stopRunningLocked(this);
+ }
+ getUidStatsLocked(uid).noteVideoTurnedOffLocked();
+ }
+
public void noteWifiRunningLocked() {
if (!mWifiRunning) {
mWifiRunning = true;
@@ -1089,45 +1214,27 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public void noteFullWifiLockAcquiredLocked(int uid) {
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteFullWifiLockAcquiredLocked();
- }
+ getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked();
}
public void noteFullWifiLockReleasedLocked(int uid) {
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteFullWifiLockReleasedLocked();
- }
+ getUidStatsLocked(uid).noteFullWifiLockReleasedLocked();
}
public void noteScanWifiLockAcquiredLocked(int uid) {
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteScanWifiLockAcquiredLocked();
- }
+ getUidStatsLocked(uid).noteScanWifiLockAcquiredLocked();
}
public void noteScanWifiLockReleasedLocked(int uid) {
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteScanWifiLockReleasedLocked();
- }
+ getUidStatsLocked(uid).noteScanWifiLockReleasedLocked();
}
public void noteWifiMulticastEnabledLocked(int uid) {
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteWifiMulticastEnabledLocked();
- }
+ getUidStatsLocked(uid).noteWifiMulticastEnabledLocked();
}
public void noteWifiMulticastDisabledLocked(int uid) {
- Uid u = mUidStats.get(uid);
- if (u != null) {
- u.noteWifiMulticastDisabledLocked();
- }
+ getUidStatsLocked(uid).noteWifiMulticastDisabledLocked();
}
@Override public long getScreenOnTime(long batteryRealtime, int which) {
@@ -1139,7 +1246,7 @@ public final class BatteryStatsImpl extends BatteryStats {
return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
batteryRealtime, which);
}
-
+
@Override public int getInputEventCount(int which) {
return mInputEventCounter.getCountLocked(which);
}
@@ -1147,7 +1254,7 @@ public final class BatteryStatsImpl extends BatteryStats {
@Override public long getPhoneOnTime(long batteryRealtime, int which) {
return mPhoneOnTimer.getTotalTimeLocked(batteryRealtime, which);
}
-
+
@Override public long getPhoneSignalStrengthTime(int strengthBin,
long batteryRealtime, int which) {
return mPhoneSignalStrengthsTimer[strengthBin].getTotalTimeLocked(
@@ -1214,9 +1321,15 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mScanWifiLockOut;
StopwatchTimer mScanWifiLockTimer;
-
+
boolean mWifiMulticastEnabled;
StopwatchTimer mWifiMulticastTimer;
+
+ boolean mAudioTurnedOn;
+ StopwatchTimer mAudioTurnedOnTimer;
+
+ boolean mVideoTurnedOn;
+ StopwatchTimer mVideoTurnedOnTimer;
Counter[] mUserActivityCounters;
@@ -1247,6 +1360,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables);
mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED,
null, mUnpluggables);
+ mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON, null, mUnpluggables);
+ mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON, null, mUnpluggables);
}
@Override
@@ -1268,11 +1383,13 @@ public final class BatteryStatsImpl extends BatteryStats {
public Map<String, ? extends BatteryStats.Uid.Pkg> getPackageStats() {
return mPackageStats;
}
-
+
+ @Override
public int getUid() {
return mUid;
}
-
+
+ @Override
public long getTcpBytesReceived(int which) {
if (which == STATS_LAST) {
return mLoadedTcpBytesReceived;
@@ -1291,7 +1408,8 @@ public final class BatteryStatsImpl extends BatteryStats {
return mCurrentTcpBytesReceived + (mStartedTcpBytesReceived >= 0
? (NetStat.getUidRxBytes(mUid) - mStartedTcpBytesReceived) : 0);
}
-
+
+ @Override
public long getTcpBytesSent(int which) {
if (which == STATS_LAST) {
return mLoadedTcpBytesSent;
@@ -1331,6 +1449,38 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@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;
@@ -1374,7 +1524,17 @@ public final class BatteryStatsImpl extends BatteryStats {
public long getWifiTurnedOnTime(long batteryRealtime, int which) {
return mWifiTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
}
-
+
+ @Override
+ public long getAudioTurnedOnTime(long batteryRealtime, int which) {
+ return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ }
+
+ @Override
+ public long getVideoTurnedOnTime(long batteryRealtime, int which) {
+ return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which);
+ }
+
@Override
public long getFullWifiLockTime(long batteryRealtime, int which) {
return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which);
@@ -1425,7 +1585,7 @@ public final class BatteryStatsImpl extends BatteryStats {
return mCurrentTcpBytesSent + (mStartedTcpBytesSent >= 0
? (NetStat.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0);
}
-
+
void writeToParcelLocked(Parcel out, long batteryRealtime) {
out.writeInt(mWakelockStats.size());
for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) {
@@ -1463,6 +1623,8 @@ public final class BatteryStatsImpl extends BatteryStats {
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) {
@@ -1522,6 +1684,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables, in);
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);
mScanWifiLockOut = false;
mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables, in);
mWifiMulticastEnabled = false;
@@ -1632,7 +1798,8 @@ public final class BatteryStatsImpl extends BatteryStats {
public Timer getSensorTime() {
return mTimer;
}
-
+
+ @Override
public int getHandle() {
return mHandle;
}
@@ -1658,6 +1825,11 @@ public final class BatteryStatsImpl extends BatteryStats {
int mStarts;
/**
+ * Amount of time the process was running in the foreground.
+ */
+ long mForegroundTime;
+
+ /**
* The amount of user time loaded from a previous save.
*/
long mLoadedUserTime;
@@ -1673,6 +1845,11 @@ public final class BatteryStatsImpl extends BatteryStats {
int mLoadedStarts;
/**
+ * The amount of foreground time loaded from a previous save.
+ */
+ long mLoadedForegroundTime;
+
+ /**
* The amount of user time loaded from the previous run.
*/
long mLastUserTime;
@@ -1688,6 +1865,11 @@ public final class BatteryStatsImpl extends BatteryStats {
int mLastStarts;
/**
+ * The amount of foreground time loaded from the previous run
+ */
+ long mLastForegroundTime;
+
+ /**
* The amount of user time when last unplugged.
*/
long mUnpluggedUserTime;
@@ -1702,6 +1884,11 @@ public final class BatteryStatsImpl extends BatteryStats {
*/
int mUnpluggedStarts;
+ /**
+ * The amount of foreground time since unplugged.
+ */
+ long mUnpluggedForegroundTime;
+
Proc() {
mUnpluggables.add(this);
}
@@ -1710,6 +1897,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggedUserTime = mUserTime;
mUnpluggedSystemTime = mSystemTime;
mUnpluggedStarts = mStarts;
+ mUnpluggedForegroundTime = mForegroundTime;
}
public void plug(long batteryUptime, long batteryRealtime) {
@@ -1721,30 +1909,38 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mUserTime);
out.writeLong(mSystemTime);
+ out.writeLong(mForegroundTime);
out.writeInt(mStarts);
out.writeLong(mLoadedUserTime);
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);
out.writeInt(mUnpluggedStarts);
}
void readFromParcelLocked(Parcel in) {
mUserTime = in.readLong();
mSystemTime = in.readLong();
+ mForegroundTime = in.readLong();
mStarts = in.readInt();
mLoadedUserTime = in.readLong();
mLoadedSystemTime = in.readLong();
+ mLoadedForegroundTime = in.readLong();
mLoadedStarts = in.readInt();
mLastUserTime = in.readLong();
mLastSystemTime = in.readLong();
+ mLastForegroundTime = in.readLong();
mLastStarts = in.readInt();
mUnpluggedUserTime = in.readLong();
mUnpluggedSystemTime = in.readLong();
+ mUnpluggedForegroundTime = in.readLong();
mUnpluggedStarts = in.readInt();
}
@@ -1757,6 +1953,10 @@ public final class BatteryStatsImpl extends BatteryStats {
mSystemTime += stime;
}
+ public void addForegroundTimeLocked(long ttime) {
+ mForegroundTime += ttime;
+ }
+
public void incStartsLocked() {
mStarts++;
}
@@ -1794,6 +1994,22 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public long getForegroundTime(int which) {
+ long val;
+ if (which == STATS_LAST) {
+ val = mLastForegroundTime;
+ } else {
+ val = mForegroundTime;
+ if (which == STATS_CURRENT) {
+ val -= mLoadedForegroundTime;
+ } else if (which == STATS_UNPLUGGED) {
+ val -= mUnpluggedForegroundTime;
+ }
+ }
+ return val;
+ }
+
+ @Override
public int getStarts(int which) {
int val;
if (which == STATS_LAST) {
@@ -2315,7 +2531,7 @@ public final class BatteryStatsImpl extends BatteryStats {
StopwatchTimer t = getSensorTimerLocked(Sensor.GPS, false);
if (t != null) {
t.stopRunningLocked(BatteryStatsImpl.this);
- }
+ }
}
public BatteryStatsImpl getBatteryStats() {
@@ -2526,7 +2742,44 @@ public final class BatteryStatsImpl extends BatteryStats {
public long getBatteryRealtime(long curTime) {
return getBatteryRealtimeLocked(curTime);
}
-
+
+ private long getTcpBytes(long current, long[] dataBytes, int which) {
+ if (which == STATS_LAST) {
+ return dataBytes[STATS_LAST];
+ } else {
+ if (which == STATS_UNPLUGGED) {
+ if (dataBytes[STATS_UNPLUGGED] < 0) {
+ return dataBytes[STATS_LAST];
+ } else {
+ return current - dataBytes[STATS_UNPLUGGED];
+ }
+ } else if (which == STATS_TOTAL) {
+ return (current - dataBytes[STATS_CURRENT]) + dataBytes[STATS_TOTAL];
+ }
+ return current - dataBytes[STATS_CURRENT];
+ }
+ }
+
+ /** Only STATS_UNPLUGGED works properly */
+ public long getMobileTcpBytesSent(int which) {
+ return getTcpBytes(NetStat.getMobileTxBytes(), mMobileDataTx, which);
+ }
+
+ /** Only STATS_UNPLUGGED works properly */
+ public long getMobileTcpBytesReceived(int which) {
+ return getTcpBytes(NetStat.getMobileRxBytes(), mMobileDataRx, which);
+ }
+
+ /** Only STATS_UNPLUGGED works properly */
+ public long getTotalTcpBytesSent(int which) {
+ return getTcpBytes(NetStat.getTotalTxBytes(), mTotalDataTx, which);
+ }
+
+ /** Only STATS_UNPLUGGED works properly */
+ public long getTotalTcpBytesReceived(int which) {
+ return getTcpBytes(NetStat.getTotalRxBytes(), mTotalDataRx, which);
+ }
+
@Override
public int getDischargeStartLevel() {
synchronized(this) {
@@ -2567,7 +2820,7 @@ public final class BatteryStatsImpl extends BatteryStats {
public void removeUidStatsLocked(int uid) {
mUidStats.remove(uid);
}
-
+
/**
* Retrieve the statistics object for a particular process, creating
* if needed.
@@ -2578,6 +2831,24 @@ public final class BatteryStatsImpl extends BatteryStats {
}
/**
+ * Retrieve the statistics object for a particular process, given
+ * the name of the process.
+ * @param name process name
+ * @return the statistics object for the process
+ */
+ public Uid.Proc getProcessStatsLocked(String name, int pid) {
+ int uid;
+ if (mUidCache.containsKey(name)) {
+ uid = mUidCache.get(name);
+ } else {
+ uid = Process.getUidForPid(pid);
+ mUidCache.put(name, uid);
+ }
+ Uid u = getUidStatsLocked(uid);
+ return u.getProcessStatsLocked(name);
+ }
+
+ /**
* Retrieve the statistics object for a particular process, creating
* if needed.
*/
@@ -2752,6 +3023,10 @@ public final class BatteryStatsImpl extends BatteryStats {
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);
u.mScanWifiLockOut = false;
u.mScanWifiLockTimer.readSummaryFromParcelLocked(in);
u.mWifiMulticastEnabled = false;
@@ -2888,6 +3163,8 @@ public final class BatteryStatsImpl extends BatteryStats {
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);
@@ -3046,11 +3323,24 @@ public final class BatteryStatsImpl extends BatteryStats {
mDischargeCurrentLevel = in.readInt();
mLastWriteTime = in.readLong();
+ mMobileDataRx[STATS_LAST] = in.readLong();
+ mMobileDataRx[STATS_UNPLUGGED] = -1;
+ mMobileDataTx[STATS_LAST] = in.readLong();
+ mMobileDataTx[STATS_UNPLUGGED] = -1;
+ mTotalDataRx[STATS_LAST] = in.readLong();
+ mTotalDataRx[STATS_UNPLUGGED] = -1;
+ mTotalDataTx[STATS_LAST] = in.readLong();
+ mTotalDataTx[STATS_UNPLUGGED] = -1;
+
+ mRadioDataUptime = in.readLong();
+ mRadioDataStart = -1;
+
mKernelWakelockStats.clear();
int NKW = in.readInt();
for (int ikw = 0; ikw < NKW; ikw++) {
if (in.readInt() != 0) {
String wakelockName = in.readString();
+ in.readInt(); // Extra 0/1 written by Timer.writeTimerToParcel
SamplingTimer kwlt = new SamplingTimer(mUnpluggables, mOnBattery, in);
mKernelWakelockStats.put(wakelockName, kwlt);
}
@@ -3119,6 +3409,14 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mDischargeCurrentLevel);
out.writeLong(mLastWriteTime);
+ out.writeLong(getMobileTcpBytesReceived(STATS_UNPLUGGED));
+ out.writeLong(getMobileTcpBytesSent(STATS_UNPLUGGED));
+ out.writeLong(getTotalTcpBytesReceived(STATS_UNPLUGGED));
+ out.writeLong(getTotalTcpBytesSent(STATS_UNPLUGGED));
+
+ // Write radio uptime for data
+ out.writeLong(getRadioDataUptimeMs());
+
out.writeInt(mKernelWakelockStats.size());
for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) {
SamplingTimer kwlt = ent.getValue();
diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java
new file mode 100644
index 0000000..4a8d8b1
--- /dev/null
+++ b/core/java/com/android/internal/os/PowerProfile.java
@@ -0,0 +1,238 @@
+/*
+ * 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 com.android.internal.os;
+
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Reports power consumption values for various device activities. Reads values from an XML file.
+ * Customize the XML file for different devices.
+ * [hidden]
+ */
+public class PowerProfile {
+
+ /**
+ * No power consumption, or accounted for elsewhere.
+ */
+ public static final String POWER_NONE = "none";
+
+ /**
+ * Power consumption when CPU is in power collapse mode.
+ */
+ public static final String POWER_CPU_IDLE = "cpu.idle";
+
+ /**
+ * Power consumption when CPU is running at normal speed.
+ */
+ public static final String POWER_CPU_NORMAL = "cpu.normal";
+
+ /**
+ * Power consumption when CPU is running at full speed.
+ */
+ public static final String POWER_CPU_FULL = "cpu.full";
+
+ /**
+ * Power consumption when WiFi driver is scanning for networks.
+ */
+ public static final String POWER_WIFI_SCAN = "wifi.scan";
+
+ /**
+ * Power consumption when WiFi driver is on.
+ */
+ public static final String POWER_WIFI_ON = "wifi.on";
+
+ /**
+ * Power consumption when WiFi driver is transmitting/receiving.
+ */
+ public static final String POWER_WIFI_ACTIVE = "wifi.active";
+
+ /**
+ * Power consumption when GPS is on.
+ */
+ public static final String POWER_GPS_ON = "gps.on";
+
+ /**
+ * Power consumption when Bluetooth driver is on.
+ */
+ public static final String POWER_BLUETOOTH_ON = "bluetooth.on";
+
+ /**
+ * Power consumption when Bluetooth driver is transmitting/receiving.
+ */
+ public static final String POWER_BLUETOOTH_ACTIVE = "bluetooth.active";
+
+ /**
+ * Power consumption when screen is on, not including the backlight power.
+ */
+ public static final String POWER_SCREEN_ON = "screen.on";
+
+ /**
+ * Power consumption when cell radio is on but not on a call.
+ */
+ public static final String POWER_RADIO_ON = "radio.on";
+
+ /**
+ * Power consumption when talking on the phone.
+ */
+ public static final String POWER_RADIO_ACTIVE = "radio.active";
+
+ /**
+ * Power consumption at full backlight brightness. If the backlight is at
+ * 50% brightness, then this should be multiplied by 0.5
+ */
+ public static final String POWER_SCREEN_FULL = "screen.full";
+
+ /**
+ * Power consumed by the audio hardware when playing back audio content. This is in addition
+ * to the CPU power, probably due to a DSP and / or amplifier.
+ */
+ public static final String POWER_AUDIO = "dsp.audio";
+
+ /**
+ * Power consumed by any media hardware when playing back video content. This is in addition
+ * to the CPU power, probably due to a DSP.
+ */
+ public static final String POWER_VIDEO = "dsp.video";
+
+ static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>();
+
+ private static final String TAG_DEVICE = "device";
+ private static final String TAG_ITEM = "item";
+ private static final String TAG_ARRAY = "array";
+ private static final String TAG_ARRAYITEM = "value";
+ private static final String ATTR_NAME = "name";
+
+ public PowerProfile(Context context) {
+ // Read the XML file for the given profile (normally only one per
+ // device)
+ if (sPowerMap.size() == 0) {
+ readPowerValuesFromXml(context);
+ }
+ }
+
+ private void readPowerValuesFromXml(Context context) {
+ int id = com.android.internal.R.xml.power_profile;
+ XmlResourceParser parser = context.getResources().getXml(id);
+ boolean parsingArray = false;
+ ArrayList<Double> array = new ArrayList<Double>();
+ String arrayName = null;
+
+ try {
+ XmlUtils.beginDocument(parser, TAG_DEVICE);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String element = parser.getName();
+ if (element == null) break;
+
+ if (parsingArray && !element.equals(TAG_ARRAYITEM)) {
+ // Finish array
+ sPowerMap.put(arrayName, array.toArray(new Double[array.size()]));
+ parsingArray = false;
+ }
+ if (element.equals(TAG_ARRAY)) {
+ parsingArray = true;
+ array.clear();
+ arrayName = parser.getAttributeValue(null, ATTR_NAME);
+ } else if (element.equals(TAG_ITEM) || element.equals(TAG_ARRAYITEM)) {
+ String name = null;
+ if (!parsingArray) name = parser.getAttributeValue(null, ATTR_NAME);
+ if (parser.next() == XmlPullParser.TEXT) {
+ String power = parser.getText();
+ double value = 0;
+ try {
+ value = Double.valueOf(power);
+ } catch (NumberFormatException nfe) {
+ }
+ if (element.equals(TAG_ITEM)) {
+ sPowerMap.put(name, value);
+ } else if (parsingArray) {
+ array.add(value);
+ }
+ }
+ }
+ }
+ if (parsingArray) {
+ sPowerMap.put(arrayName, array.toArray(new Double[array.size()]));
+ }
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ parser.close();
+ }
+ }
+
+ /**
+ * Returns the average current in mA consumed by the subsystem
+ * @param type the subsystem type
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePower(String type) {
+ if (sPowerMap.containsKey(type)) {
+ Object data = sPowerMap.get(type);
+ if (data instanceof Double[]) {
+ return ((Double[])data)[0];
+ } else {
+ return (Double) sPowerMap.get(type);
+ }
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Returns the average current in mA consumed by the subsystem for the given level.
+ * @param type the subsystem type
+ * @param level the level of power at which the subsystem is running. For instance, the
+ * signal strength of the cell network between 0 and 4 (if there are 4 bars max.).
+ * If there is no data for multiple levels, the level is ignored.
+ * @return the average current in milliAmps.
+ */
+ public double getAveragePower(String type, int level) {
+ if (sPowerMap.containsKey(type)) {
+ Object data = sPowerMap.get(type);
+ if (data instanceof Double[]) {
+ final Double[] values = (Double[]) data;
+ if (values.length > level && level >= 0) {
+ return values[level];
+ } else if (level < 0) {
+ return 0;
+ } else {
+ return values[values.length - 1];
+ }
+ } else {
+ return (Double) data;
+ }
+ } else {
+ return 0;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ac8b589..f67a235 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -467,7 +467,7 @@ public class ZygoteInit {
"--setuid=1000",
"--setgid=1000",
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003",
- "--capabilities=121715744,121715744",
+ "--capabilities=130104352,130104352",
"--runtime-init",
"--nice-name=system_server",
"com.android.server.SystemServer",
diff --git a/core/java/com/android/internal/util/BitwiseInputStream.java b/core/java/com/android/internal/util/BitwiseInputStream.java
index 4757919..86f74f3 100644
--- a/core/java/com/android/internal/util/BitwiseInputStream.java
+++ b/core/java/com/android/internal/util/BitwiseInputStream.java
@@ -65,30 +65,31 @@ public class BitwiseInputStream {
/**
* Read some data and increment the current position.
*
- * @param bits the amount of data to read (gte 0, lte 8)
+ * The 8-bit limit on access to bitwise streams is intentional to
+ * avoid endianness issues.
*
+ * @param bits the amount of data to read (gte 0, lte 8)
* @return byte of read data (possibly partially filled, from lsb)
*/
- public byte read(int bits) throws AccessException {
+ public int read(int bits) throws AccessException {
int index = mPos >>> 3;
int offset = 16 - (mPos & 0x07) - bits; // &7==%8
if ((bits < 0) || (bits > 8) || ((mPos + bits) > mEnd)) {
throw new AccessException("illegal read " +
"(pos " + mPos + ", end " + mEnd + ", bits " + bits + ")");
}
- int data = (mBuf[index] & 0x00FF) << 8;
- if (offset < 8) data |= (mBuf[index + 1] & 0xFF);
+ int data = (mBuf[index] & 0xFF) << 8;
+ if (offset < 8) data |= mBuf[index + 1] & 0xFF;
data >>>= offset;
data &= (-1 >>> (32 - bits));
mPos += bits;
- return (byte)data;
+ return data;
}
/**
* Read data in bulk into a byte array and increment the current position.
*
* @param bits the amount of data to read
- *
* @return newly allocated byte array of read data
*/
public byte[] readByteArray(int bits) throws AccessException {
diff --git a/core/java/com/android/internal/util/BitwiseOutputStream.java b/core/java/com/android/internal/util/BitwiseOutputStream.java
index 1b974ce..70c0be8 100644
--- a/core/java/com/android/internal/util/BitwiseOutputStream.java
+++ b/core/java/com/android/internal/util/BitwiseOutputStream.java
@@ -82,6 +82,9 @@ public class BitwiseOutputStream {
/**
* Write some data and increment the current position.
*
+ * The 8-bit limit on access to bitwise streams is intentional to
+ * avoid endianness issues.
+ *
* @param bits the amount of data to write (gte 0, lte 8)
* @param data to write, will be masked to expose only bits param from lsb
*/
@@ -95,8 +98,8 @@ public class BitwiseOutputStream {
int offset = 16 - (mPos & 0x07) - bits; // &7==%8
data <<= offset;
mPos += bits;
- mBuf[index] |= (data >>> 8);
- if (offset < 8) mBuf[index + 1] |= (data & 0x00FF);
+ mBuf[index] |= data >>> 8;
+ if (offset < 8) mBuf[index + 1] |= data & 0xFF;
}
/**
diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java
index 871c925..922f5be 100644
--- a/core/java/com/google/android/net/GoogleHttpClient.java
+++ b/core/java/com/google/android/net/GoogleHttpClient.java
@@ -37,6 +37,10 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.LayeredSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.impl.client.EntityEnclosingRequestWrapper;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.params.HttpParams;
@@ -44,6 +48,8 @@ import org.apache.http.protocol.HttpContext;
import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
@@ -66,25 +72,22 @@ public class GoogleHttpClient implements HttpClient {
private final AndroidHttpClient mClient;
private final ContentResolver mResolver;
- private final String mUserAgent;
+ private final String mAppName, mUserAgent;
+ private final ThreadLocal<Boolean> mConnectionAllocated = new ThreadLocal<Boolean>();
/**
- * Create an HTTP client. Normally one client is shared throughout an app.
- * @param resolver to use for accessing URL rewriting rules.
- * @param userAgent to report in your HTTP requests.
- * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)}
+ * Create an HTTP client without SSL session persistence.
+ * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)}
*/
public GoogleHttpClient(ContentResolver resolver, String userAgent) {
mClient = AndroidHttpClient.newInstance(userAgent);
mResolver = resolver;
- mUserAgent = userAgent;
+ mUserAgent = mAppName = userAgent;
}
/**
- * GoogleHttpClient(Context, String, boolean) - without SSL session
- * persistence.
- *
- * @deprecated use Context instead of ContentResolver.
+ * Create an HTTP client without SSL session persistence.
+ * @deprecated Use {@link #GoogleHttpClient(android.content.Context, String, boolean)}
*/
public GoogleHttpClient(ContentResolver resolver, String appAndVersion,
boolean gzipCapable) {
@@ -111,21 +114,72 @@ public class GoogleHttpClient implements HttpClient {
* headers. Needed because Google servers require gzip in the User-Agent
* in order to return gzip'd content.
*/
- public GoogleHttpClient(Context context, String appAndVersion,
- boolean gzipCapable) {
- this(context.getContentResolver(), SSLClientSessionCacheFactory.getCache(context),
+ public GoogleHttpClient(Context context, String appAndVersion, boolean gzipCapable) {
+ this(context.getContentResolver(),
+ SSLClientSessionCacheFactory.getCache(context),
appAndVersion, gzipCapable);
}
- private GoogleHttpClient(ContentResolver resolver, SSLClientSessionCache cache,
+ private GoogleHttpClient(ContentResolver resolver,
+ SSLClientSessionCache cache,
String appAndVersion, boolean gzipCapable) {
String userAgent = appAndVersion + " (" + Build.DEVICE + " " + Build.ID + ")";
if (gzipCapable) {
userAgent = userAgent + "; gzip";
}
+
mClient = AndroidHttpClient.newInstance(userAgent, cache);
mResolver = resolver;
+ mAppName = appAndVersion;
mUserAgent = userAgent;
+
+ // Wrap all the socket factories with the appropriate wrapper. (Apache
+ // HTTP, curse its black and stupid heart, inspects the SocketFactory to
+ // see if it's a LayeredSocketFactory, so we need two wrapper classes.)
+ SchemeRegistry registry = getConnectionManager().getSchemeRegistry();
+ for (String name : registry.getSchemeNames()) {
+ Scheme scheme = registry.unregister(name);
+ SocketFactory sf = scheme.getSocketFactory();
+ if (sf instanceof LayeredSocketFactory) {
+ sf = new WrappedLayeredSocketFactory((LayeredSocketFactory) sf);
+ } else {
+ sf = new WrappedSocketFactory(sf);
+ }
+ registry.register(new Scheme(name, sf, scheme.getDefaultPort()));
+ }
+ }
+
+ /**
+ * Delegating wrapper for SocketFactory records when sockets are connected.
+ * We use this to know whether a connection was created vs reused, to
+ * gather per-app statistics about connection reuse rates.
+ * (Note, we record only *connection*, not *creation* of sockets --
+ * what we care about is the network overhead of an actual TCP connect.)
+ */
+ private class WrappedSocketFactory implements SocketFactory {
+ private SocketFactory mDelegate;
+ private WrappedSocketFactory(SocketFactory delegate) { mDelegate = delegate; }
+ public final Socket createSocket() throws IOException { return mDelegate.createSocket(); }
+ public final boolean isSecure(Socket s) { return mDelegate.isSecure(s); }
+
+ public final Socket connectSocket(
+ Socket s, String h, int p,
+ InetAddress la, int lp, HttpParams params) throws IOException {
+ mConnectionAllocated.set(Boolean.TRUE);
+ return mDelegate.connectSocket(s, h, p, la, lp, params);
+ }
+ }
+
+ /** Like WrappedSocketFactory, but for the LayeredSocketFactory subclass. */
+ private class WrappedLayeredSocketFactory
+ extends WrappedSocketFactory implements LayeredSocketFactory {
+ private LayeredSocketFactory mDelegate;
+ private WrappedLayeredSocketFactory(LayeredSocketFactory sf) { super(sf); mDelegate = sf; }
+
+ public final Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ return mDelegate.createSocket(s, host, port, autoClose);
+ }
}
/**
@@ -140,24 +194,21 @@ public class GoogleHttpClient implements HttpClient {
public HttpResponse executeWithoutRewriting(
HttpUriRequest request, HttpContext context)
throws IOException {
- String code = "Error";
+ int code = -1;
long start = SystemClock.elapsedRealtime();
try {
HttpResponse response;
- // TODO: if we're logging network stats, and if the apache library is configured
- // to follow redirects, count each redirect as an additional round trip.
+ mConnectionAllocated.set(null);
- // see if we're logging network stats.
- boolean logNetworkStats = NetworkStatsEntity.shouldLogNetworkStats();
+ if (NetworkStatsEntity.shouldLogNetworkStats()) {
+ // TODO: if we're logging network stats, and if the apache library is configured
+ // to follow redirects, count each redirect as an additional round trip.
- if (logNetworkStats) {
int uid = android.os.Process.myUid();
long startTx = NetStat.getUidTxBytes(uid);
long startRx = NetStat.getUidRxBytes(uid);
response = mClient.execute(request, context);
- code = Integer.toString(response.getStatusLine().getStatusCode());
-
HttpEntity origEntity = response == null ? null : response.getEntity();
if (origEntity != null) {
// yeah, we compute the same thing below. we do need to compute this here
@@ -165,30 +216,39 @@ public class GoogleHttpClient implements HttpClient {
long now = SystemClock.elapsedRealtime();
long elapsed = now - start;
NetworkStatsEntity entity = new NetworkStatsEntity(origEntity,
- mUserAgent, uid, startTx, startRx,
+ mAppName, uid, startTx, startRx,
elapsed /* response latency */, now /* processing start time */);
response.setEntity(entity);
}
} else {
response = mClient.execute(request, context);
- code = Integer.toString(response.getStatusLine().getStatusCode());
}
+ code = response.getStatusLine().getStatusCode();
return response;
- } catch (IOException e) {
- code = "IOException";
- throw e;
} finally {
// Record some statistics to the checkin service about the outcome.
// Note that this is only describing execute(), not body download.
+ // We assume the database writes are much faster than network I/O,
+ // and not worth running in a background thread or anything.
try {
long elapsed = SystemClock.elapsedRealtime() - start;
ContentValues values = new ContentValues();
- values.put(Checkin.Stats.TAG,
- Checkin.Stats.Tag.HTTP_STATUS + ":" +
- mUserAgent + ":" + code);
values.put(Checkin.Stats.COUNT, 1);
values.put(Checkin.Stats.SUM, elapsed / 1000.0);
+
+ values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REQUEST + ":" + mAppName);
+ mResolver.insert(Checkin.Stats.CONTENT_URI, values);
+
+ // No sockets and no exceptions means we successfully reused a connection
+ if (mConnectionAllocated.get() == null && code >= 0) {
+ values.put(Checkin.Stats.TAG, Checkin.Stats.Tag.HTTP_REUSED + ":" + mAppName);
+ mResolver.insert(Checkin.Stats.CONTENT_URI, values);
+ }
+
+ String status = code < 0 ? "IOException" : Integer.toString(code);
+ values.put(Checkin.Stats.TAG,
+ Checkin.Stats.Tag.HTTP_STATUS + ":" + mAppName + ":" + status);
mResolver.insert(Checkin.Stats.CONTENT_URI, values);
} catch (Exception e) {
Log.e(TAG, "Error recording stats", e);
diff --git a/core/java/com/google/android/util/GoogleWebContentHelper.java b/core/java/com/google/android/util/GoogleWebContentHelper.java
index 2911420..8291e29 100644
--- a/core/java/com/google/android/util/GoogleWebContentHelper.java
+++ b/core/java/com/google/android/util/GoogleWebContentHelper.java
@@ -130,7 +130,18 @@ public class GoogleWebContentHelper {
mWebView.loadUrl(mSecureUrl);
return this;
}
-
+
+ /**
+ * Loads data into the webview and also provides a failback url
+ * @return This {@link GoogleWebContentHelper} so methods can be chained.
+ */
+ public GoogleWebContentHelper loadDataWithFailUrl(String base, String data,
+ String mimeType, String encoding, String failUrl) {
+ ensureViews();
+ mWebView.loadDataWithBaseURL(base, data, mimeType, encoding, failUrl);
+ return this;
+ }
+
/**
* Helper to handle the back key. Returns true if the back key was handled,
* otherwise returns false.