summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java21
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java76
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl2
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl27
-rw-r--r--core/java/android/accessibilityservice/UiTestAutomationBridge.java496
-rw-r--r--core/java/android/accounts/AccountAuthenticatorCache.java91
-rw-r--r--core/java/android/accounts/AccountAuthenticatorResponse.java2
-rw-r--r--core/java/android/accounts/AccountManager.java2
-rw-r--r--core/java/android/accounts/AccountManagerService.java2548
-rw-r--r--core/java/android/accounts/IAccountAuthenticatorCache.java66
-rw-r--r--core/java/android/app/Activity.java16
-rw-r--r--core/java/android/app/ActivityManager.java25
-rw-r--r--core/java/android/app/ActivityManagerNative.java117
-rw-r--r--core/java/android/app/ActivityThread.java61
-rw-r--r--core/java/android/app/AppOpsManager.aidl (renamed from core/java/android/util/Pool.java)17
-rw-r--r--core/java/android/app/AppOpsManager.java533
-rw-r--r--core/java/android/app/Application.java51
-rw-r--r--core/java/android/app/ApplicationPackageManager.java53
-rw-r--r--core/java/android/app/ApplicationThreadNative.java40
-rw-r--r--core/java/android/app/ContextImpl.java96
-rw-r--r--core/java/android/app/DownloadManager.java1
-rw-r--r--core/java/android/app/IActivityManager.java26
-rw-r--r--core/java/android/app/IApplicationThread.java6
-rw-r--r--core/java/android/app/INotificationListener.aidl (renamed from core/java/android/net/IThrottleManager.aidl)30
-rw-r--r--core/java/android/app/INotificationManager.aidl15
-rw-r--r--core/java/android/app/IUiAutomationConnection.aidl40
-rw-r--r--core/java/android/app/Instrumentation.java63
-rw-r--r--core/java/android/app/LoadedApk.java7
-rw-r--r--core/java/android/app/Notification.java131
-rw-r--r--core/java/android/app/NotificationManager.java8
-rw-r--r--core/java/android/app/PendingIntent.java59
-rw-r--r--core/java/android/app/SearchManager.java13
-rw-r--r--core/java/android/app/UiAutomation.java699
-rw-r--r--core/java/android/app/UiAutomationConnection.java239
-rw-r--r--core/java/android/app/backup/BackupAgent.java36
-rw-r--r--core/java/android/app/backup/FullBackup.java1
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java145
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java43
-rw-r--r--core/java/android/bluetooth/BluetoothTetheringDataTracker.java1
-rw-r--r--core/java/android/content/AsyncTaskLoader.java2
-rw-r--r--core/java/android/content/ClipboardManager.java12
-rw-r--r--core/java/android/content/ContentProvider.java265
-rw-r--r--core/java/android/content/ContentProviderClient.java24
-rw-r--r--core/java/android/content/ContentProviderNative.java89
-rw-r--r--core/java/android/content/ContentResolver.java84
-rw-r--r--core/java/android/content/ContentService.java838
-rw-r--r--core/java/android/content/Context.java55
-rw-r--r--core/java/android/content/ContextWrapper.java34
-rw-r--r--core/java/android/content/CursorLoader.java11
-rw-r--r--core/java/android/content/IClipboard.aidl11
-rw-r--r--core/java/android/content/IContentProvider.java27
-rw-r--r--core/java/android/content/Intent.java113
-rw-r--r--core/java/android/content/IntentFilter.java3
-rw-r--r--core/java/android/content/PeriodicSync.java21
-rw-r--r--core/java/android/content/SyncAdaptersCache.java4
-rw-r--r--core/java/android/content/SyncInfo.java2
-rw-r--r--core/java/android/content/SyncManager.java2632
-rw-r--r--core/java/android/content/SyncOperation.java171
-rw-r--r--core/java/android/content/SyncQueue.java220
-rw-r--r--core/java/android/content/SyncStatusInfo.java17
-rw-r--r--core/java/android/content/SyncStorageEngine.java2303
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl15
-rw-r--r--core/java/android/content/pm/PackageManager.java93
-rw-r--r--core/java/android/content/pm/PackageParser.java66
-rw-r--r--core/java/android/content/pm/ParceledListSlice.java215
-rw-r--r--core/java/android/content/res/Resources.java227
-rw-r--r--core/java/android/content/res/StringBlock.java87
-rw-r--r--core/java/android/content/res/TypedArray.java2
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java5
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java29
-rw-r--r--core/java/android/ddm/DdmHandleHello.java26
-rw-r--r--core/java/android/ddm/DdmHandleViewDebug.java416
-rw-r--r--core/java/android/ddm/DdmRegister.java1
-rw-r--r--core/java/android/hardware/Camera.java9
-rw-r--r--core/java/android/hardware/Sensor.java3
-rw-r--r--core/java/android/hardware/SensorManager.java52
-rw-r--r--core/java/android/hardware/SerialPort.java8
-rw-r--r--core/java/android/hardware/SystemSensorManager.java588
-rw-r--r--core/java/android/hardware/input/InputManager.java22
-rw-r--r--core/java/android/hardware/usb/IUsbManager.aidl3
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java2
-rw-r--r--core/java/android/net/CaptivePortalTracker.java69
-rw-r--r--core/java/android/net/ConnectivityManager.java488
-rw-r--r--core/java/android/net/DhcpInfo.java5
-rw-r--r--core/java/android/net/DhcpInfoInternal.java166
-rw-r--r--core/java/android/net/DhcpResults.aidl (renamed from core/java/android/util/PoolableManager.java)18
-rw-r--r--core/java/android/net/DhcpResults.java247
-rw-r--r--core/java/android/net/DhcpStateMachine.java19
-rw-r--r--core/java/android/net/EthernetDataTracker.java7
-rw-r--r--core/java/android/net/LinkProperties.java41
-rw-r--r--core/java/android/net/NetworkInfo.java6
-rw-r--r--core/java/android/net/NetworkStats.java12
-rw-r--r--core/java/android/net/NetworkTemplate.java4
-rw-r--r--core/java/android/net/NetworkUtils.java13
-rw-r--r--core/java/android/net/RouteInfo.java4
-rw-r--r--core/java/android/net/ThrottleManager.java214
-rw-r--r--core/java/android/net/TrafficStats.java256
-rw-r--r--core/java/android/net/http/AndroidHttpClient.java36
-rw-r--r--core/java/android/nfc/NfcActivityManager.java18
-rw-r--r--core/java/android/nfc/NfcAdapter.java10
-rw-r--r--core/java/android/os/BatteryStats.java42
-rw-r--r--core/java/android/os/Build.java5
-rw-r--r--core/java/android/os/Bundle.java87
-rw-r--r--core/java/android/os/INetworkManagementService.aidl29
-rw-r--r--core/java/android/os/IUserManager.aidl3
-rw-r--r--core/java/android/os/IVibratorService.aidl4
-rw-r--r--core/java/android/os/MessageQueue.java20
-rw-r--r--core/java/android/os/NullVibrator.java16
-rw-r--r--core/java/android/os/Parcel.java37
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java7
-rw-r--r--core/java/android/os/Process.java10
-rw-r--r--core/java/android/os/SchedulingPolicyService.java65
-rw-r--r--core/java/android/os/StatFs.java50
-rw-r--r--core/java/android/os/SystemVibrator.java32
-rw-r--r--core/java/android/os/Trace.java5
-rw-r--r--core/java/android/os/UserHandle.java46
-rw-r--r--core/java/android/os/UserManager.java98
-rw-r--r--core/java/android/os/Vibrator.java14
-rw-r--r--core/java/android/os/WorkSource.java410
-rw-r--r--core/java/android/os/storage/StorageManager.java53
-rw-r--r--core/java/android/preference/PreferenceActivity.java11
-rw-r--r--core/java/android/provider/CalendarContract.java17
-rw-r--r--core/java/android/provider/Downloads.java3
-rw-r--r--core/java/android/provider/Settings.java91
-rw-r--r--core/java/android/server/package.html5
-rw-r--r--core/java/android/server/search/SearchManagerService.java279
-rw-r--r--core/java/android/server/search/Searchables.java464
-rw-r--r--core/java/android/server/search/package.html5
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java26
-rw-r--r--core/java/android/speech/tts/FileSynthesisCallback.java94
-rw-r--r--core/java/android/speech/tts/ITextToSpeechService.aidl29
-rw-r--r--core/java/android/speech/tts/SynthesisCallback.java5
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java398
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java169
-rw-r--r--core/java/android/test/AndroidTestCase.java10
-rw-r--r--core/java/android/text/DynamicLayout.java27
-rw-r--r--core/java/android/text/GraphicsOperations.java13
-rw-r--r--core/java/android/text/Html.java58
-rw-r--r--core/java/android/text/Layout.java57
-rw-r--r--core/java/android/text/MeasuredText.java7
-rw-r--r--core/java/android/text/SpannableStringBuilder.java63
-rw-r--r--core/java/android/text/TextDirectionHeuristic.java28
-rw-r--r--core/java/android/text/TextDirectionHeuristics.java147
-rw-r--r--core/java/android/text/TextLine.java24
-rw-r--r--core/java/android/text/TextUtils.java2
-rw-r--r--core/java/android/text/bidi/BidiFormatter.java1123
-rw-r--r--core/java/android/text/format/DateFormat.java12
-rw-r--r--core/java/android/text/method/DigitsKeyListener.java39
-rw-r--r--core/java/android/text/method/QwertyKeyListener.java1
-rw-r--r--core/java/android/text/style/EasyEditSpan.java85
-rw-r--r--core/java/android/text/style/SuggestionSpan.java49
-rw-r--r--core/java/android/util/DisplayMetrics.java9
-rw-r--r--core/java/android/util/FinitePool.java94
-rw-r--r--core/java/android/util/LongSparseLongArray.java228
-rw-r--r--core/java/android/util/Pools.java148
-rwxr-xr-xcore/java/android/util/PropertyValueModel.java143
-rw-r--r--core/java/android/util/SparseLongArray.java2
-rw-r--r--core/java/android/util/SynchronizedPool.java48
-rwxr-xr-xcore/java/android/util/ValueModel.java74
-rw-r--r--core/java/android/view/AccessibilityInteractionController.java238
-rw-r--r--core/java/android/view/Choreographer.java2
-rw-r--r--core/java/android/view/Display.java14
-rw-r--r--core/java/android/view/DisplayEventReceiver.java4
-rw-r--r--core/java/android/view/DisplayInfo.java101
-rw-r--r--core/java/android/view/DisplayList.java522
-rw-r--r--core/java/android/view/GLES20Canvas.java136
-rw-r--r--core/java/android/view/GLES20DisplayList.java198
-rw-r--r--core/java/android/view/GLES20Layer.java4
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java57
-rw-r--r--core/java/android/view/GLES20RenderLayer.java9
-rw-r--r--core/java/android/view/GestureDetector.java17
-rw-r--r--core/java/android/view/HardwareCanvas.java77
-rw-r--r--core/java/android/view/HardwareLayer.java11
-rw-r--r--core/java/android/view/HardwareRenderer.java863
-rw-r--r--core/java/android/view/IMagnificationCallbacks.aidl (renamed from core/java/android/view/IDisplayContentChangeListener.aidl)16
-rw-r--r--core/java/android/view/IWindow.aidl2
-rw-r--r--core/java/android/view/IWindowManager.aidl62
-rw-r--r--core/java/android/view/IWindowSession.aidl5
-rw-r--r--core/java/android/view/KeyCharacterMap.java16
-rw-r--r--core/java/android/view/KeyEvent.java22
-rw-r--r--core/java/android/view/MagnificationSpec.aidl (renamed from core/java/android/view/WindowInfo.aidl)2
-rw-r--r--core/java/android/view/MagnificationSpec.java123
-rw-r--r--core/java/android/view/SimulatedDpad.java2
-rw-r--r--core/java/android/view/Surface.java640
-rw-r--r--core/java/android/view/SurfaceControl.java565
-rw-r--r--core/java/android/view/SurfaceView.java5
-rw-r--r--core/java/android/view/VelocityTracker.java58
-rw-r--r--core/java/android/view/View.java508
-rw-r--r--core/java/android/view/ViewDebug.java120
-rw-r--r--core/java/android/view/ViewGroup.java326
-rw-r--r--core/java/android/view/ViewParent.java101
-rw-r--r--core/java/android/view/ViewRootImpl.java571
-rw-r--r--core/java/android/view/VolumePanel.java97
-rw-r--r--core/java/android/view/WindowInfo.java175
-rw-r--r--core/java/android/view/WindowManager.java132
-rw-r--r--core/java/android/view/WindowManagerGlobal.java23
-rw-r--r--core/java/android/view/WindowManagerPolicy.java157
-rw-r--r--core/java/android/view/WindowOrientationListener.java715
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java249
-rw-r--r--core/java/android/view/accessibility/AccessibilityInteractionClient.java93
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java398
-rw-r--r--core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl15
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java56
-rw-r--r--core/java/android/webkit/AccessibilityInjector.java9
-rw-r--r--core/java/android/webkit/AccessibilityInjectorFallback.java150
-rw-r--r--core/java/android/webkit/BrowserFrame.java2
-rw-r--r--core/java/android/webkit/CacheManager.java126
-rw-r--r--core/java/android/webkit/EventLogTags.logtags1
-rw-r--r--core/java/android/webkit/FindActionModeCallback.java36
-rw-r--r--core/java/android/webkit/GeolocationPermissions.java3
-rw-r--r--core/java/android/webkit/HttpAuthHandler.java2
-rw-r--r--core/java/android/webkit/SslErrorHandler.java8
-rw-r--r--core/java/android/webkit/ViewStateSerializer.java3
-rw-r--r--core/java/android/webkit/WebSettings.java68
-rw-r--r--core/java/android/webkit/WebSettingsClassic.java8
-rw-r--r--core/java/android/webkit/WebView.java77
-rw-r--r--core/java/android/webkit/WebViewClassic.java75
-rw-r--r--core/java/android/webkit/WebViewClient.java9
-rw-r--r--core/java/android/webkit/WebViewCore.java19
-rw-r--r--core/java/android/webkit/WebViewDatabase.java20
-rw-r--r--core/java/android/webkit/WebViewFactory.java2
-rw-r--r--core/java/android/webkit/WebViewProvider.java10
-rw-r--r--core/java/android/widget/AbsListView.java2
-rw-r--r--core/java/android/widget/AbsSeekBar.java4
-rw-r--r--core/java/android/widget/AbsSpinner.java2
-rw-r--r--core/java/android/widget/ArrayAdapter.java18
-rw-r--r--core/java/android/widget/CheckBox.java23
-rw-r--r--core/java/android/widget/EditText.java23
-rw-r--r--core/java/android/widget/Editor.java160
-rw-r--r--core/java/android/widget/Gallery.java2
-rw-r--r--core/java/android/widget/GridLayout.java77
-rw-r--r--core/java/android/widget/ImageView.java59
-rw-r--r--core/java/android/widget/LinearLayout.java28
-rw-r--r--core/java/android/widget/ListView.java18
-rw-r--r--core/java/android/widget/MediaController.java12
-rw-r--r--core/java/android/widget/ProgressBar.java64
-rw-r--r--core/java/android/widget/QuickContactBadge.java63
-rw-r--r--core/java/android/widget/RelativeLayout.java193
-rw-r--r--core/java/android/widget/RemoteViews.java135
-rw-r--r--core/java/android/widget/RemoteViewsListAdapter.java121
-rw-r--r--core/java/android/widget/SearchView.java1
-rw-r--r--core/java/android/widget/SeekBar.java20
-rw-r--r--core/java/android/widget/SpellChecker.java15
-rw-r--r--core/java/android/widget/Spinner.java89
-rw-r--r--core/java/android/widget/TableLayout.java2
-rw-r--r--core/java/android/widget/TableRow.java2
-rw-r--r--core/java/android/widget/TextClock.java10
-rw-r--r--core/java/android/widget/TextView.java289
-rwxr-xr-xcore/java/android/widget/ValueEditor.java53
-rw-r--r--core/java/android/widget/VideoView.java90
-rw-r--r--core/java/com/android/internal/app/IAppOpsCallback.aidl (renamed from core/java/android/util/Poolable.java)16
-rw-r--r--core/java/com/android/internal/app/IAppOpsService.aidl36
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl2
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetHost.aidl8
-rw-r--r--core/java/com/android/internal/appwidget/IAppWidgetService.aidl47
-rw-r--r--core/java/com/android/internal/content/PackageMonitor.java41
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodUtils.java839
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java84
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java303
-rw-r--r--core/java/com/android/internal/os/SomeArgs.java2
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java53
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java69
-rw-r--r--core/java/com/android/internal/policy/PolicyManager.java2
-rw-r--r--core/java/com/android/internal/statusbar/StatusBarNotification.java50
-rw-r--r--core/java/com/android/internal/util/FastXmlSerializer.java33
-rw-r--r--core/java/com/android/internal/util/IndentingPrintWriter.java77
-rw-r--r--core/java/com/android/internal/util/ProcFileReader.java50
-rw-r--r--core/java/com/android/internal/util/StateMachine.java662
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java11
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java2
-rw-r--r--core/java/com/android/internal/widget/ActionBarContainer.java11
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java16
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java1
-rw-r--r--core/java/com/android/internal/widget/DigitalClock.java245
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java14
-rw-r--r--core/java/com/android/internal/widget/LockSettingsService.java405
-rw-r--r--core/java/com/android/internal/widget/SizeAdaptiveLayout.java12
277 files changed, 16752 insertions, 17512 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 9d6ee80..811b92a 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -339,7 +339,10 @@ public abstract class AccessibilityService extends Service {
private static final String LOG_TAG = "AccessibilityService";
- interface Callbacks {
+ /**
+ * @hide
+ */
+ public interface Callbacks {
public void onAccessibilityEvent(AccessibilityEvent event);
public void onInterrupt();
public void onServiceConnected();
@@ -454,7 +457,7 @@ public abstract class AccessibilityService extends Service {
*
* @return The accessibility service info.
*
- * @see AccessibilityNodeInfo
+ * @see AccessibilityServiceInfo
*/
public final AccessibilityServiceInfo getServiceInfo() {
IAccessibilityServiceConnection connection =
@@ -538,8 +541,10 @@ public abstract class AccessibilityService extends Service {
/**
* Implements the internal {@link IAccessibilityServiceClient} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
+ *
+ * @hide
*/
- static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
+ public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
static final int NO_ID = -1;
@@ -548,6 +553,7 @@ public abstract class AccessibilityService extends Service {
private static final int DO_ON_INTERRUPT = 20;
private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
private static final int DO_ON_GESTURE = 40;
+ private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50;
private final HandlerCaller mCaller;
@@ -580,6 +586,11 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(message);
}
+ public void clearAccessibilityNodeInfoCache() {
+ Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
+ mCaller.sendMessage(message);
+ }
+
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT :
@@ -604,6 +615,7 @@ public abstract class AccessibilityService extends Service {
mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
+ AccessibilityInteractionClient.getInstance().clearCache();
mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
}
return;
@@ -611,6 +623,9 @@ public abstract class AccessibilityService extends Service {
final int gestureId = message.arg1;
mCallback.onGesture(gestureId);
return;
+ case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE:
+ AccessibilityInteractionClient.getInstance().clearCache();
+ return;
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 75a4f83..d82b9a3 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -33,6 +33,7 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -148,8 +149,47 @@ public class AccessibilityServiceInfo implements Parcelable {
* accessibility service that has this flag set. Hence, clearing this
* flag does not guarantee that the device will not be in touch exploration
* mode since there may be another enabled service that requested it.
+ * <p>
+ * For accessibility services targeting API version higher than
+ * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} that want to set
+ * this flag have to request the
+ * {@link android.Manifest.permission#CAN_REQUEST_TOUCH_EXPLORATION_MODE}
+ * permission or the flag will be ignored.
+ * </p>
+ * <p>
+ * Services targeting API version equal to or lower than
+ * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} will work normally, i.e.
+ * the first time they are run, if this flag is specified, a dialog is
+ * shown to the user to confirm enabling explore by touch.
+ * </p>
+ */
+ public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 0x0000004;
+
+ /**
+ * This flag requests from the system to enable web accessibility enhancing
+ * extensions. Such extensions aim to provide improved accessibility support
+ * for content presented in a {@link android.webkit.WebView}. An example of such
+ * an extension is injecting JavaScript from a secure source. The system will enable
+ * enhanced web accessibility if there is at least one accessibility service
+ * that has this flag set. Hence, clearing this flag does not guarantee that the
+ * device will not have enhanced web accessibility enabled since there may be
+ * another enabled service that requested it.
+ * <p>
+ * Clients that want to set this flag have to request the
+ * {@link android.Manifest.permission#CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY}
+ * permission or the flag will be ignored.
+ * </p>
*/
- public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE= 0x0000004;
+ public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008;
+
+ /**
+ * This flag requests that the {@link AccessibilityNodeInfo}s obtained
+ * by an {@link AccessibilityService} contain the id of the source view.
+ * The source view id will be a fully qualified resource name of the
+ * form "package:id/name", for example "foo.bar:id/my_list", and it is
+ * useful for UI test automation. This flag is not set by default.
+ */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
/**
* The event types an {@link AccessibilityService} is interested in.
@@ -359,6 +399,13 @@ public class AccessibilityServiceInfo implements Parcelable {
}
/**
+ * @hide
+ */
+ public void setComponentName(ComponentName component) {
+ mId = component.flattenToShortString();
+ }
+
+ /**
* The accessibility service id.
* <p>
* <strong>Generated by the system.</strong>
@@ -475,6 +522,33 @@ public class AccessibilityServiceInfo implements Parcelable {
}
@Override
+ public int hashCode() {
+ return 31 * 1 + ((mId == null) ? 0 : mId.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ AccessibilityServiceInfo other = (AccessibilityServiceInfo) obj;
+ if (mId == null) {
+ if (other.mId != null) {
+ return false;
+ }
+ } else if (!mId.equals(other.mId)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
appendEventTypes(stringBuilder, eventTypes);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index d459fd5..5d684e3 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -33,4 +33,6 @@ import android.view.accessibility.AccessibilityEvent;
void onInterrupt();
void onGesture(int gesture);
+
+ void clearAccessibilityNodeInfoCache();
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index dd50f3c..7a29f35 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,6 +18,7 @@ package android.accessibilityservice;
import android.os.Bundle;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -44,9 +45,9 @@ interface IAccessibilityServiceConnection {
* @param callback Callback which to receive the result.
* @param flags Additional flags.
* @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
+ * @return Whether the call succeeded.
*/
- float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+ boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, long threadId);
@@ -66,9 +67,9 @@ interface IAccessibilityServiceConnection {
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
+ * @return Whether the call succeeded.
*/
- float findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
+ boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
long threadId);
@@ -84,15 +85,15 @@ interface IAccessibilityServiceConnection {
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
- * @param id The id of the node.
+ * @param viewId The fully qualified resource name of the view id to find.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
+ * @return Whether the call succeeded.
*/
- float findAccessibilityNodeInfoByViewId(int accessibilityWindowId, long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- long threadId);
+ boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId);
/**
* Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified
@@ -110,9 +111,9 @@ interface IAccessibilityServiceConnection {
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
+ * @return Whether the call succeeded.
*/
- float findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+ boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
/**
@@ -131,9 +132,9 @@ interface IAccessibilityServiceConnection {
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
* @param threadId The id of the calling thread.
- * @return The current window scale, where zero means a failure.
+ * @return Whether the call succeeded.
*/
- float focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+ boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
/**
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
deleted file mode 100644
index 6837386..0000000
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/*
- * Copyright (C) 2012 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.AccessibilityService.Callbacks;
-import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
-import android.content.Context;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.IAccessibilityManager;
-
-import com.android.internal.util.Predicate;
-
-import java.util.List;
-import java.util.concurrent.TimeoutException;
-
-/**
- * This class represents a bridge that can be used for UI test
- * automation. It is responsible for connecting to the system,
- * keeping track of the last accessibility event, and exposing
- * window content querying APIs. This class is designed to be
- * used from both an Android application and a Java program
- * run from the shell.
- *
- * @hide
- */
-public class UiTestAutomationBridge {
-
- private static final String LOG_TAG = UiTestAutomationBridge.class.getSimpleName();
-
- private static final int TIMEOUT_REGISTER_SERVICE = 5000;
-
- public static final int ACTIVE_WINDOW_ID = AccessibilityNodeInfo.ACTIVE_WINDOW_ID;
-
- public static final long ROOT_NODE_ID = AccessibilityNodeInfo.ROOT_NODE_ID;
-
- public static final int UNDEFINED = -1;
-
- private static final int FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS =
- AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
- | AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS
- | AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS;
-
- private final Object mLock = new Object();
-
- private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
-
- private IAccessibilityServiceClientWrapper mListener;
-
- private AccessibilityEvent mLastEvent;
-
- private volatile boolean mWaitingForEventDelivery;
-
- private volatile boolean mUnprocessedEventAvailable;
-
- private HandlerThread mHandlerThread;
-
- /**
- * Gets the last received {@link AccessibilityEvent}.
- *
- * @return The event.
- */
- public AccessibilityEvent getLastAccessibilityEvent() {
- return mLastEvent;
- }
-
- /**
- * Callback for receiving an {@link AccessibilityEvent}.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- *
- * @param event The received event.
- */
- public void onAccessibilityEvent(AccessibilityEvent event) {
- /* hook - do nothing */
- }
-
- /**
- * Callback for requests to stop feedback.
- *
- * <strong>Note:</strong> This method is <strong>NOT</strong>
- * executed on the application main thread. The client are
- * responsible for proper synchronization.
- */
- public void onInterrupt() {
- /* hook - do nothing */
- }
-
- /**
- * Connects this service.
- *
- * @throws IllegalStateException If already connected.
- */
- public void connect() {
- if (isConnected()) {
- throw new IllegalStateException("Already connected.");
- }
-
- // Serialize binder calls to a handler on a dedicated thread
- // different from the main since we expose APIs that block
- // the main thread waiting for a result the deliver of which
- // on the main thread will prevent that thread from waking up.
- // The serialization is needed also to ensure that events are
- // examined in delivery order. Otherwise, a fair locking
- // is needed for making sure the binder calls are interleaved
- // with check for the expected event and also to make sure the
- // binder threads are allowed to proceed in the received order.
- mHandlerThread = new HandlerThread("UiTestAutomationBridge");
- mHandlerThread.setDaemon(true);
- mHandlerThread.start();
- Looper looper = mHandlerThread.getLooper();
-
- mListener = new IAccessibilityServiceClientWrapper(null, looper, new Callbacks() {
- @Override
- public void onServiceConnected() {
- /* do nothing */
- }
-
- @Override
- public void onInterrupt() {
- UiTestAutomationBridge.this.onInterrupt();
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- synchronized (mLock) {
- while (true) {
- mLastEvent = AccessibilityEvent.obtain(event);
- if (!mWaitingForEventDelivery) {
- mLock.notifyAll();
- break;
- }
- if (!mUnprocessedEventAvailable) {
- mUnprocessedEventAvailable = true;
- mLock.notifyAll();
- break;
- }
- try {
- mLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- UiTestAutomationBridge.this.onAccessibilityEvent(event);
- }
-
- @Override
- public void onSetConnectionId(int connectionId) {
- synchronized (mLock) {
- mConnectionId = connectionId;
- mLock.notifyAll();
- }
- }
-
- @Override
- public boolean onGesture(int gestureId) {
- return false;
- }
- });
-
- final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-
- final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
- info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-
- try {
- manager.registerUiTestAutomationService(mListener, info);
- } catch (RemoteException re) {
- throw new IllegalStateException("Cound not register UiAutomationService.", re);
- }
-
- synchronized (mLock) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- if (isConnected()) {
- return;
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = TIMEOUT_REGISTER_SERVICE - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- throw new IllegalStateException("Cound not register UiAutomationService.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Disconnects this service.
- *
- * @throws IllegalStateException If already disconnected.
- */
- public void disconnect() {
- if (!isConnected()) {
- throw new IllegalStateException("Already disconnected.");
- }
-
- mHandlerThread.quit();
-
- IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
- ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
-
- try {
- manager.unregisterUiTestAutomationService(mListener);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while unregistering UiTestAutomationService", re);
- }
- }
-
- /**
- * Gets whether this service is connected.
- *
- * @return True if connected.
- */
- public boolean isConnected() {
- return (mConnectionId != AccessibilityInteractionClient.NO_ID);
- }
-
- /**
- * Executes a command and waits for a specific accessibility event type up
- * to a given timeout.
- *
- * @param command The command to execute before starting to wait for the event.
- * @param predicate Predicate for recognizing the awaited event.
- * @param timeoutMillis The max wait time in milliseconds.
- */
- public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
- Predicate<AccessibilityEvent> predicate, long timeoutMillis)
- throws TimeoutException, Exception {
- // TODO: This is broken - remove from here when finalizing this as public APIs.
- synchronized (mLock) {
- // Prepare to wait for an event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- if (mLastEvent != null) {
- mLastEvent.recycle();
- mLastEvent = null;
- }
- // Execute the command.
- command.run();
- // Wait for the event.
- final long startTimeMillis = SystemClock.uptimeMillis();
- while (true) {
- // If the expected event is received, that's it.
- if ((mUnprocessedEventAvailable && predicate.apply(mLastEvent))) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- return mLastEvent;
- }
- // Ask for another event.
- mWaitingForEventDelivery = true;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- // Check if timed out and if not wait.
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- mWaitingForEventDelivery = false;
- mUnprocessedEventAvailable = false;
- mLock.notifyAll();
- throw new TimeoutException("Expacted event not received within: "
- + timeoutMillis + " ms.");
- }
- try {
- mLock.wait(remainingTimeMillis);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Waits for the accessibility event stream to become idle, which is not to
- * have received a new accessibility event within <code>idleTimeout</code>,
- * and do so within a maximal global timeout as specified by
- * <code>globalTimeout</code>.
- *
- * @param idleTimeout The timeout between two event to consider the device idle.
- * @param globalTimeout The maximal global timeout in which to wait for idle.
- */
- public void waitForIdle(long idleTimeout, long globalTimeout) {
- final long startTimeMillis = SystemClock.uptimeMillis();
- long lastEventTime = (mLastEvent != null)
- ? mLastEvent.getEventTime() : SystemClock.uptimeMillis();
- synchronized (mLock) {
- while (true) {
- final long currentTimeMillis = SystemClock.uptimeMillis();
- final long sinceLastEventTimeMillis = currentTimeMillis - lastEventTime;
- if (sinceLastEventTimeMillis > idleTimeout) {
- return;
- }
- if (mLastEvent != null) {
- lastEventTime = mLastEvent.getEventTime();
- }
- final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
- final long remainingTimeMillis = globalTimeout - elapsedTimeMillis;
- if (remainingTimeMillis <= 0) {
- return;
- }
- try {
- mLock.wait(idleTimeout);
- } catch (InterruptedException e) {
- /* ignore */
- }
- }
- }
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id in the active
- * window. The search is performed from the root node.
- *
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityIdInActiveWindow(
- long accessibilityNodeId) {
- return findAccessibilityNodeInfoByAccessibilityId(ACTIVE_WINDOW_ID, accessibilityNodeId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by accessibility id.
- *
- * @param accessibilityWindowId A unique window id. Use {@link #ACTIVE_WINDOW_ID} to query
- * the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id for
- * which to search.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
- int accessibilityWindowId, long accessibilityNodeId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(mConnectionId,
- accessibilityWindowId, accessibilityNodeId,
- FIND_ACCESSIBILITY_NODE_INFO_PREFETCH_FLAGS);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id in the active
- * window. The search is performed from the root node.
- *
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewIdInActiveWindow(int viewId) {
- return findAccessibilityNodeInfoByViewId(ACTIVE_WINDOW_ID, ROOT_NODE_ID, viewId);
- }
-
- /**
- * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in
- * the window whose id is specified and starts from the node whose accessibility
- * id is specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param viewId The id of a View.
- * @return The current window scale, where zero means a failure.
- */
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
- long accessibilityNodeId, int viewId) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByViewId(connectionId, accessibilityWindowId,
- accessibilityNodeId, viewId);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text in the active
- * window. The search is performed from the root node.
- *
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByTextInActiveWindow(String text) {
- return findAccessibilityNodeInfosByText(ACTIVE_WINDOW_ID, ROOT_NODE_ID, text);
- }
-
- /**
- * Finds {@link AccessibilityNodeInfo}s by View text. The match is case
- * insensitive containment. The search is performed in the window whose
- * id is specified and starts from the node whose accessibility id is
- * specified.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique view id or virtual descendant id from
- * where to start the search. Use {@link #ROOT_NODE_ID} to start from the root.
- * @param text The searched text.
- * @return The current window scale, where zero means a failure.
- */
- public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int accessibilityWindowId,
- long accessibilityNodeId, String text) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfosByText(connectionId, accessibilityWindowId,
- accessibilityNodeId, text);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}
- * in the active window.
- *
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityActionInActiveWindow(long accessibilityNodeId, int action,
- Bundle arguments) {
- return performAccessibilityAction(ACTIVE_WINDOW_ID, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Performs an accessibility action on an {@link AccessibilityNodeInfo}.
- *
- * @param accessibilityWindowId A unique window id. Use
- * {@link #ACTIVE_WINDOW_ID} to query the currently active window.
- * @param accessibilityNodeId A unique node id (accessibility and virtual descendant id).
- * @param action The action to perform.
- * @param arguments Optional action arguments.
- * @return Whether the action was performed.
- */
- public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
- int action, Bundle arguments) {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance().performAccessibilityAction(connectionId,
- accessibilityWindowId, accessibilityNodeId, action, arguments);
- }
-
- /**
- * Gets the root {@link AccessibilityNodeInfo} in the active window.
- *
- * @return The root info.
- */
- public AccessibilityNodeInfo getRootAccessibilityNodeInfoInActiveWindow() {
- // Cache the id to avoid locking
- final int connectionId = mConnectionId;
- ensureValidConnection(connectionId);
- return AccessibilityInteractionClient.getInstance()
- .findAccessibilityNodeInfoByAccessibilityId(connectionId, ACTIVE_WINDOW_ID,
- ROOT_NODE_ID, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
- }
-
- private void ensureValidConnection(int connectionId) {
- if (connectionId == UNDEFINED) {
- throw new IllegalStateException("UiAutomationService not connected."
- + " Did you call #register()?");
- }
- }
-}
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java
deleted file mode 100644
index f937cde..0000000
--- a/core/java/android/accounts/AccountAuthenticatorCache.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accounts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.RegisteredServicesCache;
-import android.content.pm.XmlSerializerAndParser;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-
-/**
- * A cache of services that export the {@link IAccountAuthenticator} interface. This cache
- * is built by interrogating the {@link PackageManager} and is updated as packages are added,
- * removed and changed. The authenticators are referred to by their account type and
- * are made available via the {@link RegisteredServicesCache#getServiceInfo} method.
- * @hide
- */
-/* package private */ class AccountAuthenticatorCache
- extends RegisteredServicesCache<AuthenticatorDescription>
- implements IAccountAuthenticatorCache {
- private static final String TAG = "Account";
- private static final MySerializer sSerializer = new MySerializer();
-
- public AccountAuthenticatorCache(Context context) {
- super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
- AccountManager.AUTHENTICATOR_META_DATA_NAME,
- AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
- }
-
- public AuthenticatorDescription parseServiceAttributes(Resources res,
- String packageName, AttributeSet attrs) {
- TypedArray sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AccountAuthenticator);
- try {
- final String accountType =
- sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType);
- final int labelId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_label, 0);
- final int iconId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_icon, 0);
- final int smallIconId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
- final int prefId = sa.getResourceId(
- com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
- final boolean customTokens = sa.getBoolean(
- com.android.internal.R.styleable.AccountAuthenticator_customTokens, false);
- if (TextUtils.isEmpty(accountType)) {
- return null;
- }
- return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
- smallIconId, prefId, customTokens);
- } finally {
- sa.recycle();
- }
- }
-
- private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> {
- public void writeAsXml(AuthenticatorDescription item, XmlSerializer out)
- throws IOException {
- out.attribute(null, "type", item.type);
- }
-
- public AuthenticatorDescription createFromXml(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type"));
- }
- }
-}
diff --git a/core/java/android/accounts/AccountAuthenticatorResponse.java b/core/java/android/accounts/AccountAuthenticatorResponse.java
index 614e139..41f26ac 100644
--- a/core/java/android/accounts/AccountAuthenticatorResponse.java
+++ b/core/java/android/accounts/AccountAuthenticatorResponse.java
@@ -33,7 +33,7 @@ public class AccountAuthenticatorResponse implements Parcelable {
/**
* @hide
*/
- /* package private */ AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) {
+ public AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) {
mAccountAuthenticatorResponse = response;
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index bcb35d5..6d9bb1d 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -1846,7 +1846,7 @@ public class AccountManager {
* Returns an intent to an {@link Activity} that prompts the user to choose from a list of
* accounts.
* The caller will then typically start the activity by calling
- * <code>startActivityWithResult(intent, ...);</code>.
+ * <code>startActivityForResult(intent, ...);</code>.
* <p>
* On success the activity returns a Bundle with the account name and type specified using
* keys {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE}.
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
deleted file mode 100644
index 2b1a2b2..0000000
--- a/core/java/android/accounts/AccountManagerService.java
+++ /dev/null
@@ -1,2548 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accounts;
-
-import android.Manifest;
-import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.RegisteredServicesCache;
-import android.content.pm.RegisteredServicesCacheListener;
-import android.content.pm.UserInfo;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.R;
-import com.android.internal.util.IndentingPrintWriter;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Sets;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * A system service that provides account, password, and authtoken management for all
- * accounts on the device. Some of these calls are implemented with the help of the corresponding
- * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
- * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
- * AccountManager accountManager = AccountManager.get(context);
- * @hide
- */
-public class AccountManagerService
- extends IAccountManager.Stub
- implements RegisteredServicesCacheListener<AuthenticatorDescription> {
- private static final String TAG = "AccountManagerService";
-
- private static final int TIMEOUT_DELAY_MS = 1000 * 60;
- private static final String DATABASE_NAME = "accounts.db";
- private static final int DATABASE_VERSION = 4;
-
- private final Context mContext;
-
- private final PackageManager mPackageManager;
- private UserManager mUserManager;
-
- private HandlerThread mMessageThread;
- private final MessageHandler mMessageHandler;
-
- // Messages that can be sent on mHandler
- private static final int MESSAGE_TIMED_OUT = 3;
-
- private final IAccountAuthenticatorCache mAuthenticatorCache;
-
- private static final String TABLE_ACCOUNTS = "accounts";
- private static final String ACCOUNTS_ID = "_id";
- private static final String ACCOUNTS_NAME = "name";
- private static final String ACCOUNTS_TYPE = "type";
- private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
- private static final String ACCOUNTS_PASSWORD = "password";
-
- private static final String TABLE_AUTHTOKENS = "authtokens";
- private static final String AUTHTOKENS_ID = "_id";
- private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
- private static final String AUTHTOKENS_TYPE = "type";
- private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
-
- private static final String TABLE_GRANTS = "grants";
- private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
- private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
- private static final String GRANTS_GRANTEE_UID = "uid";
-
- private static final String TABLE_EXTRAS = "extras";
- private static final String EXTRAS_ID = "_id";
- private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
- private static final String EXTRAS_KEY = "key";
- private static final String EXTRAS_VALUE = "value";
-
- private static final String TABLE_META = "meta";
- private static final String META_KEY = "key";
- private static final String META_VALUE = "value";
-
- private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
- new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
- private static final Intent ACCOUNTS_CHANGED_INTENT;
-
- private static final String COUNT_OF_MATCHING_GRANTS = ""
- + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
- + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
- + " AND " + GRANTS_GRANTEE_UID + "=?"
- + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
- + " AND " + ACCOUNTS_NAME + "=?"
- + " AND " + ACCOUNTS_TYPE + "=?";
-
- private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
- AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
- private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
- AUTHTOKENS_AUTHTOKEN};
-
- private static final String SELECTION_USERDATA_BY_ACCOUNT =
- EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
- private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
-
- private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
- private final AtomicInteger mNotificationIds = new AtomicInteger(1);
-
- static class UserAccounts {
- private final int userId;
- private final DatabaseHelper openHelper;
- private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
- credentialsPermissionNotificationIds =
- new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
- private final HashMap<Account, Integer> signinRequiredNotificationIds =
- new HashMap<Account, Integer>();
- private final Object cacheLock = new Object();
- /** protected by the {@link #cacheLock} */
- private final HashMap<String, Account[]> accountCache =
- new LinkedHashMap<String, Account[]>();
- /** protected by the {@link #cacheLock} */
- private HashMap<Account, HashMap<String, String>> userDataCache =
- new HashMap<Account, HashMap<String, String>>();
- /** protected by the {@link #cacheLock} */
- private HashMap<Account, HashMap<String, String>> authTokenCache =
- new HashMap<Account, HashMap<String, String>>();
-
- UserAccounts(Context context, int userId) {
- this.userId = userId;
- synchronized (cacheLock) {
- openHelper = new DatabaseHelper(context, userId);
- }
- }
- }
-
- private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>();
-
- private static AtomicReference<AccountManagerService> sThis =
- new AtomicReference<AccountManagerService>();
- private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
-
- static {
- ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
- ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- }
-
-
- /**
- * This should only be called by system code. One should only call this after the service
- * has started.
- * @return a reference to the AccountManagerService instance
- * @hide
- */
- public static AccountManagerService getSingleton() {
- return sThis.get();
- }
-
- public AccountManagerService(Context context) {
- this(context, context.getPackageManager(), new AccountAuthenticatorCache(context));
- }
-
- public AccountManagerService(Context context, PackageManager packageManager,
- IAccountAuthenticatorCache authenticatorCache) {
- mContext = context;
- mPackageManager = packageManager;
-
- mMessageThread = new HandlerThread("AccountManagerService");
- mMessageThread.start();
- mMessageHandler = new MessageHandler(mMessageThread.getLooper());
-
- mAuthenticatorCache = authenticatorCache;
- mAuthenticatorCache.setListener(this, null /* Handler */);
-
- sThis.set(this);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- intentFilter.addDataScheme("package");
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context1, Intent intent) {
- purgeOldGrantsAll();
- }
- }, intentFilter);
-
- IntentFilter userFilter = new IntentFilter();
- userFilter.addAction(Intent.ACTION_USER_REMOVED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent);
- }
- }, userFilter);
- }
-
- public void systemReady() {
- }
-
- private UserManager getUserManager() {
- if (mUserManager == null) {
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- }
- return mUserManager;
- }
-
- private UserAccounts initUser(int userId) {
- synchronized (mUsers) {
- UserAccounts accounts = mUsers.get(userId);
- if (accounts == null) {
- accounts = new UserAccounts(mContext, userId);
- mUsers.append(userId, accounts);
- purgeOldGrants(accounts);
- validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
- }
- return accounts;
- }
- }
-
- private void purgeOldGrantsAll() {
- synchronized (mUsers) {
- for (int i = 0; i < mUsers.size(); i++) {
- purgeOldGrants(mUsers.valueAt(i));
- }
- }
- }
-
- private void purgeOldGrants(UserAccounts accounts) {
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- final Cursor cursor = db.query(TABLE_GRANTS,
- new String[]{GRANTS_GRANTEE_UID},
- null, null, GRANTS_GRANTEE_UID, null, null);
- try {
- while (cursor.moveToNext()) {
- final int uid = cursor.getInt(0);
- final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
- if (packageExists) {
- continue;
- }
- Log.d(TAG, "deleting grants for UID " + uid
- + " because its package is no longer installed");
- db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
- new String[]{Integer.toString(uid)});
- }
- } finally {
- cursor.close();
- }
- }
- }
-
- /**
- * Validate internal set of accounts against installed authenticators for
- * given user. Clears cached authenticators before validating.
- */
- public void validateAccounts(int userId) {
- final UserAccounts accounts = getUserAccounts(userId);
-
- // Invalidate user-specific cache to make sure we catch any
- // removed authenticators.
- validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
- }
-
- /**
- * Validate internal set of accounts against installed authenticators for
- * given user. Clear cached authenticators before validating when requested.
- */
- private void validateAccountsInternal(
- UserAccounts accounts, boolean invalidateAuthenticatorCache) {
- if (invalidateAuthenticatorCache) {
- mAuthenticatorCache.invalidateCache(accounts.userId);
- }
-
- final HashSet<AuthenticatorDescription> knownAuth = Sets.newHashSet();
- for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> service :
- mAuthenticatorCache.getAllServices(accounts.userId)) {
- knownAuth.add(service.type);
- }
-
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- boolean accountDeleted = false;
- Cursor cursor = db.query(TABLE_ACCOUNTS,
- new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
- null, null, null, null, null);
- try {
- accounts.accountCache.clear();
- final HashMap<String, ArrayList<String>> accountNamesByType =
- new LinkedHashMap<String, ArrayList<String>>();
- while (cursor.moveToNext()) {
- final long accountId = cursor.getLong(0);
- final String accountType = cursor.getString(1);
- final String accountName = cursor.getString(2);
-
- if (!knownAuth.contains(AuthenticatorDescription.newKey(accountType))) {
- Slog.w(TAG, "deleting account " + accountName + " because type "
- + accountType + " no longer has a registered authenticator");
- db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
- accountDeleted = true;
- final Account account = new Account(accountName, accountType);
- accounts.userDataCache.remove(account);
- accounts.authTokenCache.remove(account);
- } else {
- ArrayList<String> accountNames = accountNamesByType.get(accountType);
- if (accountNames == null) {
- accountNames = new ArrayList<String>();
- accountNamesByType.put(accountType, accountNames);
- }
- accountNames.add(accountName);
- }
- }
- for (Map.Entry<String, ArrayList<String>> cur
- : accountNamesByType.entrySet()) {
- final String accountType = cur.getKey();
- final ArrayList<String> accountNames = cur.getValue();
- final Account[] accountsForType = new Account[accountNames.size()];
- int i = 0;
- for (String accountName : accountNames) {
- accountsForType[i] = new Account(accountName, accountType);
- ++i;
- }
- accounts.accountCache.put(accountType, accountsForType);
- }
- } finally {
- cursor.close();
- if (accountDeleted) {
- sendAccountsChangedBroadcast(accounts.userId);
- }
- }
- }
- }
-
- private UserAccounts getUserAccountsForCaller() {
- return getUserAccounts(UserHandle.getCallingUserId());
- }
-
- protected UserAccounts getUserAccounts(int userId) {
- synchronized (mUsers) {
- UserAccounts accounts = mUsers.get(userId);
- if (accounts == null) {
- accounts = initUser(userId);
- mUsers.append(userId, accounts);
- }
- return accounts;
- }
- }
-
- private void onUserRemoved(Intent intent) {
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userId < 1) return;
-
- UserAccounts accounts;
- synchronized (mUsers) {
- accounts = mUsers.get(userId);
- mUsers.remove(userId);
- }
- if (accounts == null) {
- File dbFile = new File(getDatabaseName(userId));
- dbFile.delete();
- return;
- }
-
- synchronized (accounts.cacheLock) {
- accounts.openHelper.close();
- File dbFile = new File(getDatabaseName(userId));
- dbFile.delete();
- }
- }
-
- @Override
- public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
- Slog.d(TAG, "onServiceChanged() for userId " + userId);
- validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
- }
-
- public String getPassword(Account account) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getPassword: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
-
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- return readPasswordInternal(accounts, account);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private String readPasswordInternal(UserAccounts accounts, Account account) {
- if (account == null) {
- return null;
- }
-
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
- ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getString(0);
- }
- return null;
- } finally {
- cursor.close();
- }
- }
- }
-
- public String getUserData(Account account, String key) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getUserData: " + account
- + ", key " + key
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- if (key == null) throw new IllegalArgumentException("key is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- return readUserDataInternal(accounts, account, key);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public AuthenticatorDescription[] getAuthenticatorTypes() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthenticatorTypes: "
- + "caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- final int userId = UserHandle.getCallingUserId();
- final long identityToken = clearCallingIdentity();
- try {
- Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
- authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
- AuthenticatorDescription[] types =
- new AuthenticatorDescription[authenticatorCollection.size()];
- int i = 0;
- for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
- : authenticatorCollection) {
- types[i] = authenticator.type;
- i++;
- }
- return types;
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean addAccount(Account account, String password, Bundle extras) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccount: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
-
- UserAccounts accounts = getUserAccountsForCaller();
- // fails if the account already exists
- long identityToken = clearCallingIdentity();
- try {
- return addAccountInternal(accounts, account, password, extras);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
- Bundle extras) {
- if (account == null) {
- return false;
- }
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long numMatches = DatabaseUtils.longForQuery(db,
- "select count(*) from " + TABLE_ACCOUNTS
- + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type});
- if (numMatches > 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping since the account already exists");
- return false;
- }
- ContentValues values = new ContentValues();
- values.put(ACCOUNTS_NAME, account.name);
- values.put(ACCOUNTS_TYPE, account.type);
- values.put(ACCOUNTS_PASSWORD, password);
- long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
- if (accountId < 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping the DB insert failed");
- return false;
- }
- if (extras != null) {
- for (String key : extras.keySet()) {
- final String value = extras.getString(key);
- if (insertExtraLocked(db, accountId, key, value) < 0) {
- Log.w(TAG, "insertAccountIntoDatabase: " + account
- + ", skipping since insertExtra failed for key " + key);
- return false;
- }
- }
- }
- db.setTransactionSuccessful();
- insertAccountIntoCacheLocked(accounts, account);
- } finally {
- db.endTransaction();
- }
- sendAccountsChangedBroadcast(accounts.userId);
- return true;
- }
- }
-
- private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_KEY, key);
- values.put(EXTRAS_ACCOUNTS_ID, accountId);
- values.put(EXTRAS_VALUE, value);
- return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
- }
-
- public void hasFeatures(IAccountManagerResponse response,
- Account account, String[] features) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "hasFeatures: " + account
- + ", response " + response
- + ", features " + stringArrayToString(features)
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (features == null) throw new IllegalArgumentException("features is null");
- checkReadAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- new TestFeaturesSession(accounts, response, account, features).bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private class TestFeaturesSession extends Session {
- private final String[] mFeatures;
- private final Account mAccount;
-
- public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
- Account account, String[] features) {
- super(accounts, response, account.type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
- mFeatures = features;
- mAccount = account;
- }
-
- public void run() throws RemoteException {
- try {
- mAuthenticator.hasFeatures(this, mAccount, mFeatures);
- } catch (RemoteException e) {
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
- }
- }
-
- public void onResult(Bundle result) {
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- if (result == null) {
- response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
- return;
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- final Bundle newResult = new Bundle();
- newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
- result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
- response.onResult(newResult);
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
- }
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", hasFeatures"
- + ", " + mAccount
- + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
- }
- }
-
- public void removeAccount(IAccountManagerResponse response, Account account) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "removeAccount: " + account
- + ", response " + response
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- checkManageAccountsPermission();
- UserHandle user = Binder.getCallingUserHandle();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
-
- cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
- synchronized(accounts.credentialsPermissionNotificationIds) {
- for (Pair<Pair<Account, String>, Integer> pair:
- accounts.credentialsPermissionNotificationIds.keySet()) {
- if (account.equals(pair.first.first)) {
- int id = accounts.credentialsPermissionNotificationIds.get(pair);
- cancelNotification(id, user);
- }
- }
- }
-
- try {
- new RemoveAccountSession(accounts, response, account).bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private class RemoveAccountSession extends Session {
- final Account mAccount;
- public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
- Account account) {
- super(accounts, response, account.type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
- mAccount = account;
- }
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", removeAccount"
- + ", account " + mAccount;
- }
-
- public void run() throws RemoteException {
- mAuthenticator.getAccountRemovalAllowed(this, mAccount);
- }
-
- public void onResult(Bundle result) {
- if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
- && !result.containsKey(AccountManager.KEY_INTENT)) {
- final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
- if (removalAllowed) {
- removeAccountInternal(mAccounts, mAccount);
- }
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- Bundle result2 = new Bundle();
- result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
- try {
- response.onResult(result2);
- } catch (RemoteException e) {
- // ignore
- }
- }
- }
- super.onResult(result);
- }
- }
-
- /* For testing */
- protected void removeAccountInternal(Account account) {
- removeAccountInternal(getUserAccountsForCaller(), account);
- }
-
- private void removeAccountInternal(UserAccounts accounts, Account account) {
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
- new String[]{account.name, account.type});
- removeAccountFromCacheLocked(accounts, account);
- sendAccountsChangedBroadcast(accounts.userId);
- }
- }
-
- public void invalidateAuthToken(String accountType, String authToken) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "invalidateAuthToken: accountType " + accountType
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- if (authToken == null) throw new IllegalArgumentException("authToken is null");
- checkManageAccountsOrUseCredentialsPermissions();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- invalidateAuthTokenLocked(accounts, db, accountType, authToken);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
- String accountType, String authToken) {
- if (authToken == null || accountType == null) {
- return;
- }
- Cursor cursor = db.rawQuery(
- "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
- + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
- + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
- + " FROM " + TABLE_ACCOUNTS
- + " JOIN " + TABLE_AUTHTOKENS
- + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
- + " = " + AUTHTOKENS_ACCOUNTS_ID
- + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
- + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
- new String[]{authToken, accountType});
- try {
- while (cursor.moveToNext()) {
- long authTokenId = cursor.getLong(0);
- String accountName = cursor.getString(1);
- String authTokenType = cursor.getString(2);
- db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
- writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType),
- authTokenType, null);
- }
- } finally {
- cursor.close();
- }
- }
-
- private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type,
- String authToken) {
- if (account == null || type == null) {
- return false;
- }
- cancelNotification(getSigninRequiredNotificationId(accounts, account),
- new UserHandle(accounts.userId));
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId < 0) {
- return false;
- }
- db.delete(TABLE_AUTHTOKENS,
- AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
- new String[]{type});
- ContentValues values = new ContentValues();
- values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
- values.put(AUTHTOKENS_TYPE, type);
- values.put(AUTHTOKENS_AUTHTOKEN, authToken);
- if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
- db.setTransactionSuccessful();
- writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken);
- return true;
- }
- return false;
- } finally {
- db.endTransaction();
- }
- }
- }
-
- public String peekAuthToken(Account account, String authTokenType) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "peekAuthToken: " + account
- + ", authTokenType " + authTokenType
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- return readAuthTokenInternal(accounts, account, authTokenType);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void setAuthToken(Account account, String authTokenType, String authToken) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setAuthToken: " + account
- + ", authTokenType " + authTokenType
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- saveAuthTokenToDatabase(accounts, account, authTokenType, authToken);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void setPassword(Account account, String password) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setAuthToken: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- setPasswordInternal(accounts, account, password);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void setPasswordInternal(UserAccounts accounts, Account account, String password) {
- if (account == null) {
- return;
- }
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- final ContentValues values = new ContentValues();
- values.put(ACCOUNTS_PASSWORD, password);
- final long accountId = getAccountIdLocked(db, account);
- if (accountId >= 0) {
- final String[] argsAccountId = {String.valueOf(accountId)};
- db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
- db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
- accounts.authTokenCache.remove(account);
- db.setTransactionSuccessful();
- }
- } finally {
- db.endTransaction();
- }
- sendAccountsChangedBroadcast(accounts.userId);
- }
- }
-
- private void sendAccountsChangedBroadcast(int userId) {
- Log.i(TAG, "the accounts changed, sending broadcast of "
- + ACCOUNTS_CHANGED_INTENT.getAction());
- mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
- }
-
- public void clearPassword(Account account) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "clearPassword: " + account
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (account == null) throw new IllegalArgumentException("account is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- setPasswordInternal(accounts, account, null);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void setUserData(Account account, String key, String value) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "setUserData: " + account
- + ", key " + key
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (key == null) throw new IllegalArgumentException("key is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- checkAuthenticateAccountsPermission(account);
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- setUserdataInternal(accounts, account, key, value);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void setUserdataInternal(UserAccounts accounts, Account account, String key,
- String value) {
- if (account == null || key == null) {
- return;
- }
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId < 0) {
- return;
- }
- long extrasId = getExtrasIdLocked(db, accountId, key);
- if (extrasId < 0 ) {
- extrasId = insertExtraLocked(db, accountId, key, value);
- if (extrasId < 0) {
- return;
- }
- } else {
- ContentValues values = new ContentValues();
- values.put(EXTRAS_VALUE, value);
- if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
- return;
- }
-
- }
- writeUserDataIntoCacheLocked(accounts, db, account, key, value);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
- }
-
- private void onResult(IAccountManagerResponse response, Bundle result) {
- if (result == null) {
- Log.e(TAG, "the result is unexpectedly null", new Exception());
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- try {
- response.onResult(result);
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote
- // exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
-
- public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
- final String authTokenType)
- throws RemoteException {
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
-
- final int callingUid = getCallingUid();
- clearCallingIdentity();
- if (callingUid != android.os.Process.SYSTEM_UID) {
- throw new SecurityException("can only call from system");
- }
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, accountType, false,
- false /* stripAuthTokenFromResult */) {
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", getAuthTokenLabel"
- + ", " + accountType
- + ", authTokenType " + authTokenType;
- }
-
- public void run() throws RemoteException {
- mAuthenticator.getAuthTokenLabel(this, authTokenType);
- }
-
- public void onResult(Bundle result) {
- if (result != null) {
- String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
- Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label);
- super.onResult(bundle);
- return;
- } else {
- super.onResult(result);
- }
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void getAuthToken(IAccountManagerResponse response, final Account account,
- final String authTokenType, final boolean notifyOnAuthFailure,
- final boolean expectActivityLaunch, Bundle loginOptionsIn) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthToken: " + account
- + ", response " + response
- + ", authTokenType " + authTokenType
- + ", notifyOnAuthFailure " + notifyOnAuthFailure
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
- final UserAccounts accounts = getUserAccountsForCaller();
- final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
- authenticatorInfo = mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(account.type), accounts.userId);
- final boolean customTokens =
- authenticatorInfo != null && authenticatorInfo.type.customTokens;
-
- // skip the check if customTokens
- final int callerUid = Binder.getCallingUid();
- final boolean permissionGranted = customTokens ||
- permissionIsGranted(account, authTokenType, callerUid);
-
- final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() :
- loginOptionsIn;
- // let authenticator know the identity of the caller
- loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
- loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
- if (notifyOnAuthFailure) {
- loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
- }
-
- long identityToken = clearCallingIdentity();
- try {
- // if the caller has permission, do the peek. otherwise go the more expensive
- // route of starting a Session
- if (!customTokens && permissionGranted) {
- String authToken = readAuthTokenInternal(accounts, account, authTokenType);
- if (authToken != null) {
- Bundle result = new Bundle();
- result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
- result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
- result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
- onResult(response, result);
- return;
- }
- }
-
- new Session(accounts, response, account.type, expectActivityLaunch,
- false /* stripAuthTokenFromResult */) {
- protected String toDebugString(long now) {
- if (loginOptions != null) loginOptions.keySet();
- return super.toDebugString(now) + ", getAuthToken"
- + ", " + account
- + ", authTokenType " + authTokenType
- + ", loginOptions " + loginOptions
- + ", notifyOnAuthFailure " + notifyOnAuthFailure;
- }
-
- public void run() throws RemoteException {
- // If the caller doesn't have permission then create and return the
- // "grant permission" intent instead of the "getAuthToken" intent.
- if (!permissionGranted) {
- mAuthenticator.getAuthTokenLabel(this, authTokenType);
- } else {
- mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
- }
- }
-
- public void onResult(Bundle result) {
- if (result != null) {
- if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
- Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
- new AccountAuthenticatorResponse(this),
- authTokenType,
- result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL));
- Bundle bundle = new Bundle();
- bundle.putParcelable(AccountManager.KEY_INTENT, intent);
- onResult(bundle);
- return;
- }
- String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
- if (authToken != null) {
- String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
- String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
- if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
- onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
- "the type and name should not be empty");
- return;
- }
- if (!customTokens) {
- saveAuthTokenToDatabase(mAccounts, new Account(name, type),
- authTokenType, authToken);
- }
- }
-
- Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
- if (intent != null && notifyOnAuthFailure && !customTokens) {
- doNotification(mAccounts,
- account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
- intent, accounts.userId);
- }
- }
- super.onResult(result);
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private void createNoCredentialsPermissionNotification(Account account, Intent intent,
- int userId) {
- int uid = intent.getIntExtra(
- GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
- String authTokenType = intent.getStringExtra(
- GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
- String authTokenLabel = intent.getStringExtra(
- GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
-
- Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
- 0 /* when */);
- final String titleAndSubtitle =
- mContext.getString(R.string.permission_request_notification_with_subtitle,
- account.name);
- final int index = titleAndSubtitle.indexOf('\n');
- String title = titleAndSubtitle;
- String subtitle = "";
- if (index > 0) {
- title = titleAndSubtitle.substring(0, index);
- subtitle = titleAndSubtitle.substring(index + 1);
- }
- UserHandle user = new UserHandle(userId);
- n.setLatestEventInfo(mContext, title, subtitle,
- PendingIntent.getActivityAsUser(mContext, 0, intent,
- PendingIntent.FLAG_CANCEL_CURRENT, null, user));
- installNotification(getCredentialPermissionNotificationId(
- account, authTokenType, uid), n, user);
- }
-
- private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
- AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
-
- Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
- // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
- // Since it was set in Eclair+ we can't change it without breaking apps using
- // the intent from a non-Activity context.
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addCategory(
- String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));
-
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
- intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid);
-
- return intent;
- }
-
- private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
- int uid) {
- Integer id;
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.credentialsPermissionNotificationIds) {
- final Pair<Pair<Account, String>, Integer> key =
- new Pair<Pair<Account, String>, Integer>(
- new Pair<Account, String>(account, authTokenType), uid);
- id = accounts.credentialsPermissionNotificationIds.get(key);
- if (id == null) {
- id = mNotificationIds.incrementAndGet();
- accounts.credentialsPermissionNotificationIds.put(key, id);
- }
- }
- return id;
- }
-
- private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) {
- Integer id;
- synchronized (accounts.signinRequiredNotificationIds) {
- id = accounts.signinRequiredNotificationIds.get(account);
- if (id == null) {
- id = mNotificationIds.incrementAndGet();
- accounts.signinRequiredNotificationIds.put(account, id);
- }
- }
- return id;
- }
-
- public void addAcount(final IAccountManagerResponse response, final String accountType,
- final String authTokenType, final String[] requiredFeatures,
- final boolean expectActivityLaunch, final Bundle optionsIn) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccount: accountType " + accountType
- + ", response " + response
- + ", authTokenType " + authTokenType
- + ", requiredFeatures " + stringArrayToString(requiredFeatures)
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- checkManageAccountsPermission();
-
- UserAccounts accounts = getUserAccountsForCaller();
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
- options.putInt(AccountManager.KEY_CALLER_UID, uid);
- options.putInt(AccountManager.KEY_CALLER_PID, pid);
-
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
- options);
- }
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", addAccount"
- + ", accountType " + accountType
- + ", requiredFeatures "
- + (requiredFeatures != null
- ? TextUtils.join(",", requiredFeatures)
- : null);
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- @Override
- public void confirmCredentialsAsUser(IAccountManagerResponse response,
- final Account account, final Bundle options, final boolean expectActivityLaunch,
- int userId) {
- // Only allow the system process to read accounts of other users
- if (userId != UserHandle.getCallingUserId()
- && Binder.getCallingUid() != android.os.Process.myUid()) {
- throw new SecurityException("User " + UserHandle.getCallingUserId()
- + " trying to confirm account credentials for " + userId);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "confirmCredentials: " + account
- + ", response " + response
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccounts(userId);
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.confirmCredentials(this, account, options);
- }
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", confirmCredentials"
- + ", " + account;
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void updateCredentials(IAccountManagerResponse response, final Account account,
- final String authTokenType, final boolean expectActivityLaunch,
- final Bundle loginOptions) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "updateCredentials: " + account
- + ", response " + response
- + ", authTokenType " + authTokenType
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (account == null) throw new IllegalArgumentException("account is null");
- if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, account.type, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
- }
- protected String toDebugString(long now) {
- if (loginOptions != null) loginOptions.keySet();
- return super.toDebugString(now) + ", updateCredentials"
- + ", " + account
- + ", authTokenType " + authTokenType
- + ", loginOptions " + loginOptions;
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void editProperties(IAccountManagerResponse response, final String accountType,
- final boolean expectActivityLaunch) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "editProperties: accountType " + accountType
- + ", response " + response
- + ", expectActivityLaunch " + expectActivityLaunch
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- checkManageAccountsPermission();
- UserAccounts accounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- new Session(accounts, response, accountType, expectActivityLaunch,
- true /* stripAuthTokenFromResult */) {
- public void run() throws RemoteException {
- mAuthenticator.editProperties(this, mAccountType);
- }
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", editProperties"
- + ", accountType " + accountType;
- }
- }.bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private class GetAccountsByTypeAndFeatureSession extends Session {
- private final String[] mFeatures;
- private volatile Account[] mAccountsOfType = null;
- private volatile ArrayList<Account> mAccountsWithFeatures = null;
- private volatile int mCurrentAccount = 0;
-
- public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
- IAccountManagerResponse response, String type, String[] features) {
- super(accounts, response, type, false /* expectActivityLaunch */,
- true /* stripAuthTokenFromResult */);
- mFeatures = features;
- }
-
- public void run() throws RemoteException {
- synchronized (mAccounts.cacheLock) {
- mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType);
- }
- // check whether each account matches the requested features
- mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
- mCurrentAccount = 0;
-
- checkAccount();
- }
-
- public void checkAccount() {
- if (mCurrentAccount >= mAccountsOfType.length) {
- sendResult();
- return;
- }
-
- final IAccountAuthenticator accountAuthenticator = mAuthenticator;
- if (accountAuthenticator == null) {
- // It is possible that the authenticator has died, which is indicated by
- // mAuthenticator being set to null. If this happens then just abort.
- // There is no need to send back a result or error in this case since
- // that already happened when mAuthenticator was cleared.
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "checkAccount: aborting session since we are no longer"
- + " connected to the authenticator, " + toDebugString());
- }
- return;
- }
- try {
- accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
- } catch (RemoteException e) {
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
- }
- }
-
- public void onResult(Bundle result) {
- mNumResults++;
- if (result == null) {
- onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
- return;
- }
- if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
- mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
- }
- mCurrentAccount++;
- checkAccount();
- }
-
- public void sendResult() {
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- Account[] accounts = new Account[mAccountsWithFeatures.size()];
- for (int i = 0; i < accounts.length; i++) {
- accounts[i] = mAccountsWithFeatures.get(i);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
- + response);
- }
- Bundle result = new Bundle();
- result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
- response.onResult(result);
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
- }
-
-
- protected String toDebugString(long now) {
- return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
- + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
- }
- }
-
- /**
- * Returns the accounts for a specific user
- * @hide
- */
- public Account[] getAccounts(int userId) {
- checkReadAccountsPermission();
- UserAccounts accounts = getUserAccounts(userId);
- long identityToken = clearCallingIdentity();
- try {
- synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, null);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /**
- * Returns accounts for all running users.
- *
- * @hide
- */
- public AccountAndUser[] getRunningAccounts() {
- final int[] runningUserIds;
- try {
- runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds();
- } catch (RemoteException e) {
- // Running in system_server; should never happen
- throw new RuntimeException(e);
- }
- return getAccounts(runningUserIds);
- }
-
- /** {@hide} */
- public AccountAndUser[] getAllAccounts() {
- final List<UserInfo> users = getUserManager().getUsers();
- final int[] userIds = new int[users.size()];
- for (int i = 0; i < userIds.length; i++) {
- userIds[i] = users.get(i).id;
- }
- return getAccounts(userIds);
- }
-
- private AccountAndUser[] getAccounts(int[] userIds) {
- final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
- synchronized (mUsers) {
- for (int userId : userIds) {
- UserAccounts userAccounts = getUserAccounts(userId);
- if (userAccounts == null) continue;
- synchronized (userAccounts.cacheLock) {
- Account[] accounts = getAccountsFromCacheLocked(userAccounts, null);
- for (int a = 0; a < accounts.length; a++) {
- runningAccounts.add(new AccountAndUser(accounts[a], userId));
- }
- }
- }
- }
-
- AccountAndUser[] accountsArray = new AccountAndUser[runningAccounts.size()];
- return runningAccounts.toArray(accountsArray);
- }
-
- @Override
- public Account[] getAccountsAsUser(String type, int userId) {
- // Only allow the system process to read accounts of other users
- if (userId != UserHandle.getCallingUserId()
- && Binder.getCallingUid() != android.os.Process.myUid()) {
- throw new SecurityException("User " + UserHandle.getCallingUserId()
- + " trying to get account for " + userId);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAccounts: accountType " + type
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- checkReadAccountsPermission();
- UserAccounts accounts = getUserAccounts(userId);
- long identityToken = clearCallingIdentity();
- try {
- synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, type);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- @Override
- public Account[] getAccounts(String type) {
- return getAccountsAsUser(type, UserHandle.getCallingUserId());
- }
-
- public void getAccountsByFeatures(IAccountManagerResponse response,
- String type, String[] features) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAccounts: accountType " + type
- + ", response " + response
- + ", features " + stringArrayToString(features)
- + ", caller's uid " + Binder.getCallingUid()
- + ", pid " + Binder.getCallingPid());
- }
- if (response == null) throw new IllegalArgumentException("response is null");
- if (type == null) throw new IllegalArgumentException("accountType is null");
- checkReadAccountsPermission();
- UserAccounts userAccounts = getUserAccountsForCaller();
- long identityToken = clearCallingIdentity();
- try {
- if (features == null || features.length == 0) {
- Account[] accounts;
- synchronized (userAccounts.cacheLock) {
- accounts = getAccountsFromCacheLocked(userAccounts, type);
- }
- Bundle result = new Bundle();
- result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
- onResult(response, result);
- return;
- }
- new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind();
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- private long getAccountIdLocked(SQLiteDatabase db, Account account) {
- Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
- "name=? AND type=?", new String[]{account.name, account.type}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) {
- Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
- EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
- new String[]{key}, null, null, null);
- try {
- if (cursor.moveToNext()) {
- return cursor.getLong(0);
- }
- return -1;
- } finally {
- cursor.close();
- }
- }
-
- private abstract class Session extends IAccountAuthenticatorResponse.Stub
- implements IBinder.DeathRecipient, ServiceConnection {
- IAccountManagerResponse mResponse;
- final String mAccountType;
- final boolean mExpectActivityLaunch;
- final long mCreationTime;
-
- public int mNumResults = 0;
- private int mNumRequestContinued = 0;
- private int mNumErrors = 0;
-
-
- IAccountAuthenticator mAuthenticator = null;
-
- private final boolean mStripAuthTokenFromResult;
- protected final UserAccounts mAccounts;
-
- public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
- boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
- super();
- if (response == null) throw new IllegalArgumentException("response is null");
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- mAccounts = accounts;
- mStripAuthTokenFromResult = stripAuthTokenFromResult;
- mResponse = response;
- mAccountType = accountType;
- mExpectActivityLaunch = expectActivityLaunch;
- mCreationTime = SystemClock.elapsedRealtime();
- synchronized (mSessions) {
- mSessions.put(toString(), this);
- }
- try {
- response.asBinder().linkToDeath(this, 0 /* flags */);
- } catch (RemoteException e) {
- mResponse = null;
- binderDied();
- }
- }
-
- IAccountManagerResponse getResponseAndClose() {
- if (mResponse == null) {
- // this session has already been closed
- return null;
- }
- IAccountManagerResponse response = mResponse;
- close(); // this clears mResponse so we need to save the response before this call
- return response;
- }
-
- private void close() {
- synchronized (mSessions) {
- if (mSessions.remove(toString()) == null) {
- // the session was already closed, so bail out now
- return;
- }
- }
- if (mResponse != null) {
- // stop listening for response deaths
- mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
-
- // clear this so that we don't accidentally send any further results
- mResponse = null;
- }
- cancelTimeout();
- unbind();
- }
-
- public void binderDied() {
- mResponse = null;
- close();
- }
-
- protected String toDebugString() {
- return toDebugString(SystemClock.elapsedRealtime());
- }
-
- protected String toDebugString(long now) {
- return "Session: expectLaunch " + mExpectActivityLaunch
- + ", connected " + (mAuthenticator != null)
- + ", stats (" + mNumResults + "/" + mNumRequestContinued
- + "/" + mNumErrors + ")"
- + ", lifetime " + ((now - mCreationTime) / 1000.0);
- }
-
- void bind() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
- }
- if (!bindToAuthenticator(mAccountType)) {
- Log.d(TAG, "bind attempt failed for " + toDebugString());
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
- }
- }
-
- private void unbind() {
- if (mAuthenticator != null) {
- mAuthenticator = null;
- mContext.unbindService(this);
- }
- }
-
- public void scheduleTimeout() {
- mMessageHandler.sendMessageDelayed(
- mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
- }
-
- public void cancelTimeout() {
- mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
- try {
- run();
- } catch (RemoteException e) {
- onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
- "remote exception");
- }
- }
-
- public void onServiceDisconnected(ComponentName name) {
- mAuthenticator = null;
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
- "disconnected");
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onServiceDisconnected: "
- + "caught RemoteException while responding", e);
- }
- }
- }
- }
-
- public abstract void run() throws RemoteException;
-
- public void onTimedOut() {
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- try {
- response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
- "timeout");
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding",
- e);
- }
- }
- }
- }
-
- public void onResult(Bundle result) {
- mNumResults++;
- if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
- String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
- String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
- if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
- Account account = new Account(accountName, accountType);
- cancelNotification(getSigninRequiredNotificationId(mAccounts, account),
- new UserHandle(mAccounts.userId));
- }
- }
- IAccountManagerResponse response;
- if (mExpectActivityLaunch && result != null
- && result.containsKey(AccountManager.KEY_INTENT)) {
- response = mResponse;
- } else {
- response = getResponseAndClose();
- }
- if (response != null) {
- try {
- if (result == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName()
- + " calling onError() on response " + response);
- }
- response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
- "null bundle returned");
- } else {
- if (mStripAuthTokenFromResult) {
- result.remove(AccountManager.KEY_AUTHTOKEN);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName()
- + " calling onResult() on response " + response);
- }
- response.onResult(result);
- }
- } catch (RemoteException e) {
- // if the caller is dead then there is no one to care about remote exceptions
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "failure while notifying response", e);
- }
- }
- }
- }
-
- public void onRequestContinued() {
- mNumRequestContinued++;
- }
-
- public void onError(int errorCode, String errorMessage) {
- mNumErrors++;
- IAccountManagerResponse response = getResponseAndClose();
- if (response != null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, getClass().getSimpleName()
- + " calling onError() on response " + response);
- }
- try {
- response.onError(errorCode, errorMessage);
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
- }
- }
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Session.onError: already closed");
- }
- }
- }
-
- /**
- * find the component name for the authenticator and initiate a bind
- * if no authenticator or the bind fails then return false, otherwise return true
- */
- private boolean bindToAuthenticator(String authenticatorType) {
- final AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
- authenticatorInfo = mAuthenticatorCache.getServiceInfo(
- AuthenticatorDescription.newKey(authenticatorType), mAccounts.userId);
- if (authenticatorInfo == null) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "there is no authenticator for " + authenticatorType
- + ", bailing out");
- }
- return false;
- }
-
- Intent intent = new Intent();
- intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
- intent.setComponent(authenticatorInfo.componentName);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
- }
- if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE, mAccounts.userId)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
- }
- return false;
- }
-
-
- return true;
- }
- }
-
- private class MessageHandler extends Handler {
- MessageHandler(Looper looper) {
- super(looper);
- }
-
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_TIMED_OUT:
- Session session = (Session)msg.obj;
- session.onTimedOut();
- break;
-
- default:
- throw new IllegalStateException("unhandled message: " + msg.what);
- }
- }
- }
-
- private static String getDatabaseName(int userId) {
- File systemDir = Environment.getSystemSecureDirectory();
- File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME);
- if (userId == 0) {
- // Migrate old file, if it exists, to the new location.
- // Make sure the new file doesn't already exist. A dummy file could have been
- // accidentally created in the old location, causing the new one to become corrupted
- // as well.
- File oldFile = new File(systemDir, DATABASE_NAME);
- if (oldFile.exists() && !databaseFile.exists()) {
- // Check for use directory; create if it doesn't exist, else renameTo will fail
- File userDir = Environment.getUserSystemDirectory(userId);
- if (!userDir.exists()) {
- if (!userDir.mkdirs()) {
- throw new IllegalStateException("User dir cannot be created: " + userDir);
- }
- }
- if (!oldFile.renameTo(databaseFile)) {
- throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
- }
- }
- }
- return databaseFile.getPath();
- }
-
- static class DatabaseHelper extends SQLiteOpenHelper {
-
- public DatabaseHelper(Context context, int userId) {
- super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION);
- }
-
- /**
- * This call needs to be made while the mCacheLock is held. The way to
- * ensure this is to get the lock any time a method is called ont the DatabaseHelper
- * @param db The database.
- */
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
- + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + ACCOUNTS_NAME + " TEXT NOT NULL, "
- + ACCOUNTS_TYPE + " TEXT NOT NULL, "
- + ACCOUNTS_PASSWORD + " TEXT, "
- + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
-
- db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
- + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
- + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
- + AUTHTOKENS_AUTHTOKEN + " TEXT, "
- + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
-
- createGrantsTable(db);
-
- db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
- + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
- + EXTRAS_ACCOUNTS_ID + " INTEGER, "
- + EXTRAS_KEY + " TEXT NOT NULL, "
- + EXTRAS_VALUE + " TEXT, "
- + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
-
- db.execSQL("CREATE TABLE " + TABLE_META + " ( "
- + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
- + META_VALUE + " TEXT)");
-
- createAccountsDeletionTrigger(db);
- }
-
- private void createAccountsDeletionTrigger(SQLiteDatabase db) {
- db.execSQL(""
- + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
- + " BEGIN"
- + " DELETE FROM " + TABLE_AUTHTOKENS
- + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
- + " DELETE FROM " + TABLE_EXTRAS
- + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
- + " DELETE FROM " + TABLE_GRANTS
- + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
- + " END");
- }
-
- private void createGrantsTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
- + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
- + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
- + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
- + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
- + "," + GRANTS_GRANTEE_UID + "))");
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
-
- if (oldVersion == 1) {
- // no longer need to do anything since the work is done
- // when upgrading from version 2
- oldVersion++;
- }
-
- if (oldVersion == 2) {
- createGrantsTable(db);
- db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
- createAccountsDeletionTrigger(db);
- oldVersion++;
- }
-
- if (oldVersion == 3) {
- db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
- " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
- oldVersion++;
- }
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
- }
- }
-
- public IBinder onBind(Intent intent) {
- return asBinder();
- }
-
- /**
- * Searches array of arguments for the specified string
- * @param args array of argument strings
- * @param value value to search for
- * @return true if the value is contained in the array
- */
- private static boolean scanArgs(String[] args, String value) {
- if (args != null) {
- for (String arg : args) {
- if (value.equals(arg)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
- != PackageManager.PERMISSION_GRANTED) {
- fout.println("Permission Denial: can't dump AccountsManager from from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
- + " without permission " + android.Manifest.permission.DUMP);
- return;
- }
- final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c");
- final IndentingPrintWriter ipw = new IndentingPrintWriter(fout, " ");
-
- final List<UserInfo> users = getUserManager().getUsers();
- for (UserInfo user : users) {
- ipw.println("User " + user + ":");
- ipw.increaseIndent();
- dumpUser(getUserAccounts(user.id), fd, ipw, args, isCheckinRequest);
- ipw.println();
- ipw.decreaseIndent();
- }
- }
-
- private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
- String[] args, boolean isCheckinRequest) {
- synchronized (userAccounts.cacheLock) {
- final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase();
-
- if (isCheckinRequest) {
- // This is a checkin request. *Only* upload the account types and the count of each.
- Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
- null, null, ACCOUNTS_TYPE, null, null);
- try {
- while (cursor.moveToNext()) {
- // print type,count
- fout.println(cursor.getString(0) + "," + cursor.getString(1));
- }
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- } else {
- Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */);
- fout.println("Accounts: " + accounts.length);
- for (Account account : accounts) {
- fout.println(" " + account);
- }
-
- fout.println();
- synchronized (mSessions) {
- final long now = SystemClock.elapsedRealtime();
- fout.println("Active Sessions: " + mSessions.size());
- for (Session session : mSessions.values()) {
- fout.println(" " + session.toDebugString(now));
- }
- }
-
- fout.println();
- mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
- }
- }
- }
-
- private void doNotification(UserAccounts accounts, Account account, CharSequence message,
- Intent intent, int userId) {
- long identityToken = clearCallingIdentity();
- try {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "doNotification: " + message + " intent:" + intent);
- }
-
- if (intent.getComponent() != null &&
- GrantCredentialsPermissionActivity.class.getName().equals(
- intent.getComponent().getClassName())) {
- createNoCredentialsPermissionNotification(account, intent, userId);
- } else {
- final Integer notificationId = getSigninRequiredNotificationId(accounts, account);
- intent.addCategory(String.valueOf(notificationId));
- Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
- 0 /* when */);
- UserHandle user = new UserHandle(userId);
- final String notificationTitleFormat =
- mContext.getText(R.string.notification_title).toString();
- n.setLatestEventInfo(mContext,
- String.format(notificationTitleFormat, account.name),
- message, PendingIntent.getActivityAsUser(
- mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT,
- null, user));
- installNotification(notificationId, n, user);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- protected void installNotification(final int notificationId, final Notification n,
- UserHandle user) {
- ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
- .notifyAsUser(null, notificationId, n, user);
- }
-
- protected void cancelNotification(int id, UserHandle user) {
- long identityToken = clearCallingIdentity();
- try {
- ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
- .cancelAsUser(null, id, user);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /** Succeeds if any of the specified permissions are granted. */
- private void checkBinderPermission(String... permissions) {
- final int uid = Binder.getCallingUid();
-
- for (String perm : permissions) {
- if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, " caller uid " + uid + " has " + perm);
- }
- return;
- }
- }
-
- String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
- Log.w(TAG, " " + msg);
- throw new SecurityException(msg);
- }
-
- private boolean inSystemImage(int callingUid) {
- final int callingUserId = UserHandle.getUserId(callingUid);
-
- final PackageManager userPackageManager;
- try {
- userPackageManager = mContext.createPackageContextAsUser(
- "android", 0, new UserHandle(callingUserId)).getPackageManager();
- } catch (NameNotFoundException e) {
- return false;
- }
-
- String[] packages = userPackageManager.getPackagesForUid(callingUid);
- for (String name : packages) {
- try {
- PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */);
- if (packageInfo != null
- && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
- return false;
- }
-
- private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
- final boolean inSystemImage = inSystemImage(callerUid);
- final boolean fromAuthenticator = account != null
- && hasAuthenticatorUid(account.type, callerUid);
- final boolean hasExplicitGrants = account != null
- && hasExplicitlyGrantedPermission(account, authTokenType, callerUid);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
- + callerUid + ", " + account
- + ": is authenticator? " + fromAuthenticator
- + ", has explicit permission? " + hasExplicitGrants);
- }
- return fromAuthenticator || hasExplicitGrants || inSystemImage;
- }
-
- private boolean hasAuthenticatorUid(String accountType, int callingUid) {
- final int callingUserId = UserHandle.getUserId(callingUid);
- for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
- mAuthenticatorCache.getAllServices(callingUserId)) {
- if (serviceInfo.type.type.equals(accountType)) {
- return (serviceInfo.uid == callingUid) ||
- (mPackageManager.checkSignatures(serviceInfo.uid, callingUid)
- == PackageManager.SIGNATURE_MATCH);
- }
- }
- return false;
- }
-
- private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType,
- int callerUid) {
- if (callerUid == android.os.Process.SYSTEM_UID) {
- return true;
- }
- UserAccounts accounts = getUserAccountsForCaller();
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- String[] args = { String.valueOf(callerUid), authTokenType,
- account.name, account.type};
- final boolean permissionGranted =
- DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
- if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
- // TODO: Skip this check when running automated tests. Replace this
- // with a more general solution.
- Log.d(TAG, "no credentials permission for usage of " + account + ", "
- + authTokenType + " by uid " + callerUid
- + " but ignoring since device is in test harness.");
- return true;
- }
- return permissionGranted;
- }
- }
-
- private void checkCallingUidAgainstAuthenticator(Account account) {
- final int uid = Binder.getCallingUid();
- if (account == null || !hasAuthenticatorUid(account.type, uid)) {
- String msg = "caller uid " + uid + " is different than the authenticator's uid";
- Log.w(TAG, msg);
- throw new SecurityException(msg);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid");
- }
- }
-
- private void checkAuthenticateAccountsPermission(Account account) {
- checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS);
- checkCallingUidAgainstAuthenticator(account);
- }
-
- private void checkReadAccountsPermission() {
- checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
- }
-
- private void checkManageAccountsPermission() {
- checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
- }
-
- private void checkManageAccountsOrUseCredentialsPermissions() {
- checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS,
- Manifest.permission.USE_CREDENTIALS);
- }
-
- public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
- throws RemoteException {
- final int callingUid = getCallingUid();
-
- if (callingUid != android.os.Process.SYSTEM_UID) {
- throw new SecurityException();
- }
-
- if (value) {
- grantAppPermission(account, authTokenType, uid);
- } else {
- revokeAppPermission(account, authTokenType, uid);
- }
- }
-
- /**
- * Allow callers with the given uid permission to get credentials for account/authTokenType.
- * <p>
- * Although this is public it can only be accessed via the AccountManagerService object
- * which is in the system. This means we don't need to protect it with permissions.
- * @hide
- */
- private void grantAppPermission(Account account, String authTokenType, int uid) {
- if (account == null || authTokenType == null) {
- Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
- return;
- }
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId >= 0) {
- ContentValues values = new ContentValues();
- values.put(GRANTS_ACCOUNTS_ID, accountId);
- values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
- values.put(GRANTS_GRANTEE_UID, uid);
- db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
- db.setTransactionSuccessful();
- }
- } finally {
- db.endTransaction();
- }
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
- new UserHandle(accounts.userId));
- }
- }
-
- /**
- * Don't allow callers with the given uid permission to get credentials for
- * account/authTokenType.
- * <p>
- * Although this is public it can only be accessed via the AccountManagerService object
- * which is in the system. This means we don't need to protect it with permissions.
- * @hide
- */
- private void revokeAppPermission(Account account, String authTokenType, int uid) {
- if (account == null || authTokenType == null) {
- Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
- return;
- }
- UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
- synchronized (accounts.cacheLock) {
- final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- long accountId = getAccountIdLocked(db, account);
- if (accountId >= 0) {
- db.delete(TABLE_GRANTS,
- GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
- + GRANTS_GRANTEE_UID + "=?",
- new String[]{String.valueOf(accountId), authTokenType,
- String.valueOf(uid)});
- db.setTransactionSuccessful();
- }
- } finally {
- db.endTransaction();
- }
- cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
- new UserHandle(accounts.userId));
- }
- }
-
- static final private String stringArrayToString(String[] value) {
- return value != null ? ("[" + TextUtils.join(",", value) + "]") : null;
- }
-
- private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) {
- final Account[] oldAccountsForType = accounts.accountCache.get(account.type);
- if (oldAccountsForType != null) {
- ArrayList<Account> newAccountsList = new ArrayList<Account>();
- for (Account curAccount : oldAccountsForType) {
- if (!curAccount.equals(account)) {
- newAccountsList.add(curAccount);
- }
- }
- if (newAccountsList.isEmpty()) {
- accounts.accountCache.remove(account.type);
- } else {
- Account[] newAccountsForType = new Account[newAccountsList.size()];
- newAccountsForType = newAccountsList.toArray(newAccountsForType);
- accounts.accountCache.put(account.type, newAccountsForType);
- }
- }
- accounts.userDataCache.remove(account);
- accounts.authTokenCache.remove(account);
- }
-
- /**
- * This assumes that the caller has already checked that the account is not already present.
- */
- private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) {
- Account[] accountsForType = accounts.accountCache.get(account.type);
- int oldLength = (accountsForType != null) ? accountsForType.length : 0;
- Account[] newAccountsForType = new Account[oldLength + 1];
- if (accountsForType != null) {
- System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength);
- }
- newAccountsForType[oldLength] = account;
- accounts.accountCache.put(account.type, newAccountsForType);
- }
-
- protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) {
- if (accountType != null) {
- final Account[] accounts = userAccounts.accountCache.get(accountType);
- if (accounts == null) {
- return EMPTY_ACCOUNT_ARRAY;
- } else {
- return Arrays.copyOf(accounts, accounts.length);
- }
- } else {
- int totalLength = 0;
- for (Account[] accounts : userAccounts.accountCache.values()) {
- totalLength += accounts.length;
- }
- if (totalLength == 0) {
- return EMPTY_ACCOUNT_ARRAY;
- }
- Account[] accounts = new Account[totalLength];
- totalLength = 0;
- for (Account[] accountsOfType : userAccounts.accountCache.values()) {
- System.arraycopy(accountsOfType, 0, accounts, totalLength,
- accountsOfType.length);
- totalLength += accountsOfType.length;
- }
- return accounts;
- }
- }
-
- protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
- Account account, String key, String value) {
- HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
- if (userDataForAccount == null) {
- userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
- accounts.userDataCache.put(account, userDataForAccount);
- }
- if (value == null) {
- userDataForAccount.remove(key);
- } else {
- userDataForAccount.put(key, value);
- }
- }
-
- protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
- Account account, String key, String value) {
- HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
- if (authTokensForAccount == null) {
- authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
- accounts.authTokenCache.put(account, authTokensForAccount);
- }
- if (value == null) {
- authTokensForAccount.remove(key);
- } else {
- authTokensForAccount.put(key, value);
- }
- }
-
- protected String readAuthTokenInternal(UserAccounts accounts, Account account,
- String authTokenType) {
- synchronized (accounts.cacheLock) {
- HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
- if (authTokensForAccount == null) {
- // need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
- accounts.authTokenCache.put(account, authTokensForAccount);
- }
- return authTokensForAccount.get(authTokenType);
- }
- }
-
- protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
- synchronized (accounts.cacheLock) {
- HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
- if (userDataForAccount == null) {
- // need to populate the cache for this account
- final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
- userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
- accounts.userDataCache.put(account, userDataForAccount);
- }
- return userDataForAccount.get(key);
- }
- }
-
- protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
- final SQLiteDatabase db, Account account) {
- HashMap<String, String> userDataForAccount = new HashMap<String, String>();
- Cursor cursor = db.query(TABLE_EXTRAS,
- COLUMNS_EXTRAS_KEY_AND_VALUE,
- SELECTION_USERDATA_BY_ACCOUNT,
- new String[]{account.name, account.type},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- final String tmpkey = cursor.getString(0);
- final String value = cursor.getString(1);
- userDataForAccount.put(tmpkey, value);
- }
- } finally {
- cursor.close();
- }
- return userDataForAccount;
- }
-
- protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked(
- final SQLiteDatabase db, Account account) {
- HashMap<String, String> authTokensForAccount = new HashMap<String, String>();
- Cursor cursor = db.query(TABLE_AUTHTOKENS,
- COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
- SELECTION_AUTHTOKENS_BY_ACCOUNT,
- new String[]{account.name, account.type},
- null, null, null);
- try {
- while (cursor.moveToNext()) {
- final String type = cursor.getString(0);
- final String authToken = cursor.getString(1);
- authTokensForAccount.put(type, authToken);
- }
- } finally {
- cursor.close();
- }
- return authTokensForAccount;
- }
-}
diff --git a/core/java/android/accounts/IAccountAuthenticatorCache.java b/core/java/android/accounts/IAccountAuthenticatorCache.java
deleted file mode 100644
index 06c2106..0000000
--- a/core/java/android/accounts/IAccountAuthenticatorCache.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.accounts;
-
-import android.content.pm.RegisteredServicesCache;
-import android.content.pm.RegisteredServicesCacheListener;
-import android.os.Handler;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Collection;
-
-/**
- * An interface to the Authenticator specialization of RegisteredServicesCache. The use of
- * this interface by the AccountManagerService makes it easier to unit test it.
- * @hide
- */
-public interface IAccountAuthenticatorCache {
- /**
- * Accessor for the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
- * matched the specified {@link android.accounts.AuthenticatorDescription} or null
- * if none match.
- * @param type the authenticator type to return
- * @return the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
- * matches the account type or null if none is present
- */
- RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> getServiceInfo(
- AuthenticatorDescription type, int userId);
-
- /**
- * @return A copy of a Collection of all the current Authenticators.
- */
- Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> getAllServices(
- int userId);
-
- /**
- * Dumps the state of the cache. See
- * {@link android.os.Binder#dump(java.io.FileDescriptor, java.io.PrintWriter, String[])}
- */
- void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId);
-
- /**
- * Sets a listener that will be notified whenever the authenticator set changes
- * @param listener the listener to notify, or null
- * @param handler the {@link Handler} on which the notification will be posted. If null
- * the notification will be posted on the main thread.
- */
- void setListener(RegisteredServicesCacheListener<AuthenticatorDescription> listener,
- Handler handler);
-
- void invalidateCache(int userId);
-}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d6ddeb6..87c2d8c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1346,6 +1346,20 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * This is called when the user is requesting an assist, to build a full
+ * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
+ * application. You can override this method to place into the bundle anything
+ * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
+ * of the assist Intent. The default implementation does nothing.
+ *
+ * <p>This function will be called after any global assist callbacks that had
+ * been registered with {@link Application#registerOnProvideAssistData
+ * Application.registerOnProvideAssistData}.
+ */
+ public void onProvideAssistData(Bundle data) {
+ }
+
+ /**
* Called when you are no longer visible to the user. You will next
* receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
* depending on later user activity.
@@ -3726,7 +3740,7 @@ public class Activity extends ContextThemeWrapper
try {
intent.setAllowFds(false);
result = ActivityManagerNative.getDefault()
- .startActivity(mMainThread.getApplicationThread(),
+ .startActivity(mMainThread.getApplicationThread(), getBasePackageName(),
intent, intent.resolveTypeIfNeeded(getContentResolver()),
mToken, mEmbeddedID, requestCode,
ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, null,
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 594be68..944a533 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1915,7 +1915,30 @@ public class ActivityManager {
return PackageManager.PERMISSION_DENIED;
}
- /** @hide */
+ /**
+ * @hide
+ * Helper for dealing with incoming user arguments to system service calls.
+ * Takes care of checking permissions and converting USER_CURRENT to the
+ * actual current user.
+ *
+ * @param callingPid The pid of the incoming call, as per Binder.getCallingPid().
+ * @param callingUid The uid of the incoming call, as per Binder.getCallingUid().
+ * @param userId The user id argument supplied by the caller -- this is the user
+ * they want to run as.
+ * @param allowAll If true, we will allow USER_ALL. This means you must be prepared
+ * to get a USER_ALL returned and deal with it correctly. If false,
+ * an exception will be thrown if USER_ALL is supplied.
+ * @param requireFull If true, the caller must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} to be able to run as a
+ * different user than their current process; otherwise they must hold
+ * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
+ * @param name Optional textual name of the incoming call; only for generating error messages.
+ * @param callerPackage Optional package name of caller; only for error messages.
+ *
+ * @return Returns the user ID that the call should run as. Will always be a concrete
+ * user number, unless <var>allowAll</var> is true in which case it could also be
+ * USER_ALL.
+ */
public static int handleIncomingUser(int callingPid, int callingUid, int userId,
boolean allowAll, boolean requireFull, String name, String callerPackage) {
if (UserHandle.getUserId(callingUid) == userId) {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index fe7338b..aca4f9c 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -39,7 +39,6 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
-import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Singleton;
@@ -93,7 +92,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
try {
getDefault().broadcastIntent(
null, intent, null, null, Activity.RESULT_OK, null, null,
- null /*permission*/, false, true, userId);
+ null /*permission*/, AppOpsManager.OP_NONE, false, true, userId);
} catch (RemoteException ex) {
}
}
@@ -117,6 +116,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -128,7 +128,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
? data.readFileDescriptor() : null;
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
- int result = startActivity(app, intent, resolvedType,
+ int result = startActivity(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options);
reply.writeNoException();
@@ -141,6 +141,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -153,7 +154,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int userId = data.readInt();
- int result = startActivityAsUser(app, intent, resolvedType,
+ int result = startActivityAsUser(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options, userId);
reply.writeNoException();
@@ -166,6 +167,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -178,7 +180,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int userId = data.readInt();
- WaitResult result = startActivityAndWait(app, intent, resolvedType,
+ WaitResult result = startActivityAndWait(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags,
profileFile, profileFd, options, userId);
reply.writeNoException();
@@ -191,6 +193,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent intent = Intent.CREATOR.createFromParcel(data);
String resolvedType = data.readString();
IBinder resultTo = data.readStrongBinder();
@@ -201,7 +204,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int userId = data.readInt();
- int result = startActivityWithConfig(app, intent, resolvedType,
+ int result = startActivityWithConfig(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, config, options, userId);
reply.writeNoException();
reply.writeInt(result);
@@ -341,11 +344,12 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
String resultData = data.readString();
Bundle resultExtras = data.readBundle();
String perm = data.readString();
+ int appOp = data.readInt();
boolean serialized = data.readInt() != 0;
boolean sticky = data.readInt() != 0;
int userId = data.readInt();
int res = broadcastIntent(app, intent, resolvedType, resultTo,
- resultCode, resultData, resultExtras, perm,
+ resultCode, resultData, resultExtras, perm, appOp,
serialized, sticky, userId);
reply.writeNoException();
reply.writeInt(res);
@@ -836,8 +840,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
Bundle arguments = data.readBundle();
IBinder b = data.readStrongBinder();
IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
+ b = data.readStrongBinder();
+ IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
int userId = data.readInt();
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w, userId);
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -1526,13 +1532,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
data.enforceInterface(IActivityManager.descriptor);
IBinder b = data.readStrongBinder();
IApplicationThread app = ApplicationThreadNative.asInterface(b);
+ String callingPackage = data.readString();
Intent[] intents = data.createTypedArray(Intent.CREATOR);
String[] resolvedTypes = data.createStringArray();
IBinder resultTo = data.readStrongBinder();
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
int userId = data.readInt();
- int result = startActivities(app, intents, resolvedTypes, resultTo,
+ int result = startActivities(app, callingPackage, intents, resolvedTypes, resultTo,
options, userId);
reply.writeNoException();
reply.writeInt(result);
@@ -1784,6 +1791,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_LAUNCHED_FROM_PACKAGE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ String res = getLaunchedFromPackage(token);
+ reply.writeNoException();
+ reply.writeString(res);
+ return true;
+ }
+
case REGISTER_USER_SWITCH_OBSERVER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IUserSwitchObserver observer = IUserSwitchObserver.Stub.asInterface(
@@ -1819,6 +1835,24 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_TOP_ACTIVITY_EXTRAS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int requestType = data.readInt();
+ Bundle res = getTopActivityExtras(requestType);
+ reply.writeNoException();
+ reply.writeBundle(res);
+ return true;
+ }
+
+ case REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ Bundle extras = data.readBundle();
+ reportTopActivityExtras(token, extras);
+ reply.writeNoException();
+ return true;
+ }
+
}
return super.onTransact(code, data, reply, flags);
@@ -1855,7 +1889,7 @@ class ActivityManagerProxy implements IActivityManager
return mRemote;
}
- public int startActivity(IApplicationThread caller, Intent intent,
+ public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
@@ -1863,6 +1897,7 @@ class ActivityManagerProxy implements IActivityManager
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -1890,7 +1925,7 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
- public int startActivityAsUser(IApplicationThread caller, Intent intent,
+ public int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
@@ -1898,6 +1933,7 @@ class ActivityManagerProxy implements IActivityManager
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -1925,14 +1961,15 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result;
}
- public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent,
- String resolvedType, IBinder resultTo, String resultWho,
+ public WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -1960,14 +1997,15 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
return result;
}
- public int startActivityWithConfig(IApplicationThread caller, Intent intent,
- String resolvedType, IBinder resultTo, String resultWho,
+ public int startActivityWithConfig(IApplicationThread caller, String callingPackage,
+ Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, Configuration config,
Bundle options, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
@@ -2138,7 +2176,7 @@ class ActivityManagerProxy implements IActivityManager
public int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle map,
- String requiredPermission, boolean serialized,
+ String requiredPermission, int appOp, boolean serialized,
boolean sticky, int userId) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -2152,6 +2190,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeString(resultData);
data.writeBundle(map);
data.writeString(requiredPermission);
+ data.writeInt(appOp);
data.writeInt(serialized ? 1 : 0);
data.writeInt(sticky ? 1 : 0);
data.writeInt(userId);
@@ -2857,8 +2896,8 @@ class ActivityManagerProxy implements IActivityManager
}
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
- throws RemoteException {
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -2867,6 +2906,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
data.writeBundle(arguments);
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+ data.writeStrongBinder(connection != null ? connection.asBinder() : null);
data.writeInt(userId);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
@@ -3752,13 +3792,14 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
- public int startActivities(IApplicationThread caller,
+ public int startActivities(IApplicationThread caller, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
Bundle options, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
+ data.writeString(callingPackage);
data.writeTypedArray(intents, 0);
data.writeStringArray(resolvedTypes);
data.writeStrongBinder(resultTo);
@@ -4104,6 +4145,19 @@ class ActivityManagerProxy implements IActivityManager
return result;
}
+ public String getLaunchedFromPackage(IBinder activityToken) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(activityToken);
+ mRemote.transact(GET_LAUNCHED_FROM_PACKAGE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ String result = reply.readString();
+ data.recycle();
+ reply.recycle();
+ return result;
+ }
+
public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -4150,5 +4204,30 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public Bundle getTopActivityExtras(int requestType) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(requestType);
+ mRemote.transact(GET_TOP_ACTIVITY_EXTRAS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Bundle res = reply.readBundle();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
+ public void reportTopActivityExtras(IBinder token, Bundle extras) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ data.writeBundle(extras);
+ mRemote.transact(REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d880817..bb73cf4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,7 +43,6 @@ import android.database.sqlite.SQLiteDebug;
import android.database.sqlite.SQLiteDebug.DbStats;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.net.IConnectivityManager;
import android.net.Proxy;
@@ -102,7 +101,6 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
-import java.security.Security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -187,7 +185,8 @@ public final class ActivityThread {
= new ArrayList<Application>();
// set of instantiated backup agents, keyed by package name
final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>();
- static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>();
+ /** Reference to singleton {@link ActivityThread} */
+ private static ActivityThread sCurrentActivityThread;
Instrumentation mInstrumentation;
String mInstrumentationAppDir = null;
String mInstrumentationAppLibraryDir = null;
@@ -419,6 +418,7 @@ public final class ActivityThread {
ComponentName instrumentationName;
Bundle instrumentationArgs;
IInstrumentationWatcher instrumentationWatcher;
+ IUiAutomationConnection instrumentationUiAutomationConnection;
int debugMode;
boolean enableOpenGlTrace;
boolean restrictedBackupMode;
@@ -532,6 +532,12 @@ public final class ActivityThread {
String pkg;
CompatibilityInfo info;
}
+
+ static final class RequestActivityExtras {
+ IBinder activityToken;
+ IBinder requestToken;
+ int requestType;
+ }
private native void dumpGraphicsInfo(FileDescriptor fd);
@@ -723,9 +729,10 @@ public final class ActivityThread {
ComponentName instrumentationName, String profileFile,
ParcelFileDescriptor profileFd, boolean autoStopProfiler,
Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher,
- int debugMode, boolean enableOpenGlTrace, boolean isRestrictedBackupMode,
- boolean persistent, Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) {
+ IUiAutomationConnection instrumentationUiConnection, int debugMode,
+ boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) {
if (services != null) {
// Setup the service cache in the ServiceManager
@@ -741,6 +748,7 @@ public final class ActivityThread {
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
+ data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableOpenGlTrace = enableOpenGlTrace;
data.restrictedBackupMode = isRestrictedBackupMode;
@@ -1107,6 +1115,16 @@ public final class ActivityThread {
queueOrSendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
}
+ @Override
+ public void requestActivityExtras(IBinder activityToken, IBinder requestToken,
+ int requestType) {
+ RequestActivityExtras cmd = new RequestActivityExtras();
+ cmd.activityToken = activityToken;
+ cmd.requestToken = requestToken;
+ cmd.requestType = requestType;
+ queueOrSendMessage(H.REQUEST_ACTIVITY_EXTRAS, cmd);
+ }
+
private void printRow(PrintWriter pw, String format, Object...objs) {
pw.println(String.format(format, objs));
}
@@ -1172,6 +1190,7 @@ public final class ActivityThread {
public static final int TRIM_MEMORY = 140;
public static final int DUMP_PROVIDER = 141;
public static final int UNSTABLE_PROVIDER_DIED = 142;
+ public static final int REQUEST_ACTIVITY_EXTRAS = 143;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
@@ -1218,6 +1237,7 @@ public final class ActivityThread {
case TRIM_MEMORY: return "TRIM_MEMORY";
case DUMP_PROVIDER: return "DUMP_PROVIDER";
case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
+ case REQUEST_ACTIVITY_EXTRAS: return "REQUEST_ACTIVITY_EXTRAS";
}
}
return Integer.toString(code);
@@ -1429,6 +1449,9 @@ public final class ActivityThread {
case UNSTABLE_PROVIDER_DIED:
handleUnstableProviderDied((IBinder)msg.obj, false);
break;
+ case REQUEST_ACTIVITY_EXTRAS:
+ handleRequestActivityExtras((RequestActivityExtras)msg.obj);
+ break;
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
@@ -1564,7 +1587,7 @@ public final class ActivityThread {
}
public static ActivityThread currentActivityThread() {
- return sThreadLocal.get();
+ return sCurrentActivityThread;
}
public static String currentPackageName() {
@@ -2321,6 +2344,23 @@ public final class ActivityThread {
performNewIntents(data.token, data.intents);
}
+ public void handleRequestActivityExtras(RequestActivityExtras cmd) {
+ Bundle data = new Bundle();
+ ActivityClientRecord r = mActivities.get(cmd.activityToken);
+ if (r != null) {
+ r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
+ r.activity.onProvideAssistData(data);
+ }
+ if (data.isEmpty()) {
+ data = null;
+ }
+ IActivityManager mgr = ActivityManagerNative.getDefault();
+ try {
+ mgr.reportTopActivityExtras(cmd.requestToken, data);
+ } catch (RemoteException e) {
+ }
+ }
+
private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
/**
@@ -4277,7 +4317,7 @@ public final class ActivityThread {
// Enable OpenGL tracing if required
if (data.enableOpenGlTrace) {
- GLUtils.enableTracing();
+ GLUtils.setTracingLevel(1);
}
/**
@@ -4336,7 +4376,8 @@ public final class ActivityThread {
}
mInstrumentation.init(this, instrContext, appContext,
- new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher);
+ new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher,
+ data.instrumentationUiAutomationConnection);
if (mProfiler.profileFile != null && !ii.handleProfiling
&& mProfiler.profileFd == null) {
@@ -4894,7 +4935,7 @@ public final class ActivityThread {
}
private void attach(boolean system) {
- sThreadLocal.set(this);
+ sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
diff --git a/core/java/android/util/Pool.java b/core/java/android/app/AppOpsManager.aidl
index 8cd4f3e..4b97a15 100644
--- a/core/java/android/util/Pool.java
+++ b/core/java/android/app/AppOpsManager.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
+/**
+ * Copyright (c) 2013, 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
+ * 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,
@@ -14,12 +14,7 @@
* limitations under the License.
*/
-package android.util;
+package android.app;
-/**
- * @hide
- */
-public interface Pool<T extends Poolable<T>> {
- public abstract T acquire();
- public abstract void release(T element);
-}
+parcelable AppOpsManager.PackageOps;
+parcelable AppOpsManager.OpEntry;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
new file mode 100644
index 0000000..c9776f1
--- /dev/null
+++ b/core/java/android/app/AppOpsManager.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2012 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.Manifest;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAppOpsCallback;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+
+/**
+ * API for interacting with "application operation" tracking. Allows you to:
+ *
+ * - Note when operations are happening, and find out if they are allowed for the current caller.
+ * - Disallow specific apps from doing specific operations.
+ * - Collect all of the current information about operations that have been executed or are not
+ * being allowed.
+ * - Monitor for changes in whether an operation is allowed.
+ *
+ * Each operation is identified by a single integer; these integers are a fixed set of
+ * operations, enumerated by the OP_* constants.
+ *
+ * When checking operations, the result is a "mode" integer indicating the current setting
+ * for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute the operation but
+ * fake its behavior enough so that the caller doesn't crash), MODE_ERRORED (through a
+ * SecurityException back to the caller; the normal operation calls will do this for you).
+ *
+ * @hide
+ */
+public class AppOpsManager {
+ final Context mContext;
+ final IAppOpsService mService;
+ final HashMap<Callback, IAppOpsCallback> mModeWatchers
+ = new HashMap<Callback, IAppOpsCallback>();
+
+ public static final int MODE_ALLOWED = 0;
+ public static final int MODE_IGNORED = 1;
+ public static final int MODE_ERRORED = 2;
+
+ // when adding one of these:
+ // - increment _NUM_OP
+ // - add rows to sOpToSwitch, sOpNames, sOpPerms
+ // - add descriptive strings to Settings/res/values/arrays.xml
+ public static final int OP_NONE = -1;
+ public static final int OP_COARSE_LOCATION = 0;
+ public static final int OP_FINE_LOCATION = 1;
+ public static final int OP_GPS = 2;
+ public static final int OP_VIBRATE = 3;
+ public static final int OP_READ_CONTACTS = 4;
+ public static final int OP_WRITE_CONTACTS = 5;
+ public static final int OP_READ_CALL_LOG = 6;
+ public static final int OP_WRITE_CALL_LOG = 7;
+ public static final int OP_READ_CALENDAR = 8;
+ public static final int OP_WRITE_CALENDAR = 9;
+ public static final int OP_WIFI_SCAN = 10;
+ public static final int OP_POST_NOTIFICATION = 11;
+ public static final int OP_NEIGHBORING_CELLS = 12;
+ public static final int OP_CALL_PHONE = 13;
+ public static final int OP_READ_SMS = 14;
+ public static final int OP_WRITE_SMS = 15;
+ public static final int OP_RECEIVE_SMS = 16;
+ public static final int OP_RECEIVE_EMERGECY_SMS = 17;
+ public static final int OP_RECEIVE_MMS = 18;
+ public static final int OP_RECEIVE_WAP_PUSH = 19;
+ public static final int OP_SEND_SMS = 20;
+ public static final int OP_READ_ICC_SMS = 21;
+ public static final int OP_WRITE_ICC_SMS = 22;
+ public static final int OP_WRITE_SETTINGS = 23;
+ public static final int OP_SYSTEM_ALERT_WINDOW = 24;
+ public static final int OP_ACCESS_NOTIFICATIONS = 25;
+ public static final int OP_CAMERA = 26;
+ public static final int OP_RECORD_AUDIO = 27;
+ public static final int OP_PLAY_AUDIO = 28;
+ public static final int OP_READ_CLIPBOARD = 29;
+ public static final int OP_WRITE_CLIPBOARD = 30;
+ /** @hide */
+ public static final int _NUM_OP = 31;
+
+ /**
+ * This maps each operation to the operation that serves as the
+ * switch to determine whether it is allowed. Generally this is
+ * a 1:1 mapping, but for some things (like location) that have
+ * multiple low-level operations being tracked that should be
+ * presented to hte user as one switch then this can be used to
+ * make them all controlled by the same single operation.
+ */
+ private static int[] sOpToSwitch = new int[] {
+ OP_COARSE_LOCATION,
+ OP_COARSE_LOCATION,
+ OP_COARSE_LOCATION,
+ OP_VIBRATE,
+ OP_READ_CONTACTS,
+ OP_WRITE_CONTACTS,
+ OP_READ_CALL_LOG,
+ OP_WRITE_CALL_LOG,
+ OP_READ_CALENDAR,
+ OP_WRITE_CALENDAR,
+ OP_COARSE_LOCATION,
+ OP_POST_NOTIFICATION,
+ OP_COARSE_LOCATION,
+ OP_CALL_PHONE,
+ OP_READ_SMS,
+ OP_WRITE_SMS,
+ OP_READ_SMS,
+ OP_READ_SMS,
+ OP_READ_SMS,
+ OP_READ_SMS,
+ OP_WRITE_SMS,
+ OP_READ_SMS,
+ OP_WRITE_SMS,
+ OP_WRITE_SETTINGS,
+ OP_SYSTEM_ALERT_WINDOW,
+ OP_ACCESS_NOTIFICATIONS,
+ OP_CAMERA,
+ OP_RECORD_AUDIO,
+ OP_PLAY_AUDIO,
+ OP_READ_CLIPBOARD,
+ OP_WRITE_CLIPBOARD,
+ };
+
+ /**
+ * This provides a simple name for each operation to be used
+ * in debug output.
+ */
+ private static String[] sOpNames = new String[] {
+ "COARSE_LOCATION",
+ "FINE_LOCATION",
+ "GPS",
+ "VIBRATE",
+ "READ_CONTACTS",
+ "WRITE_CONTACTS",
+ "READ_CALL_LOG",
+ "WRITE_CALL_LOG",
+ "READ_CALENDAR",
+ "WRITE_CALENDAR",
+ "WIFI_SCAN",
+ "POST_NOTIFICATION",
+ "NEIGHBORING_CELLS",
+ "CALL_PHONE",
+ "READ_SMS",
+ "WRITE_SMS",
+ "RECEIVE_SMS",
+ "RECEIVE_EMERGECY_SMS",
+ "RECEIVE_MMS",
+ "RECEIVE_WAP_PUSH",
+ "SEND_SMS",
+ "READ_ICC_SMS",
+ "WRITE_ICC_SMS",
+ "WRITE_SETTINGS",
+ "SYSTEM_ALERT_WINDOW",
+ "ACCESS_NOTIFICATIONS",
+ "CAMERA",
+ "RECORD_AUDIO",
+ "PLAY_AUDIO",
+ "READ_CLIPBOARD",
+ "WRITE_CLIPBOARD",
+ };
+
+ /**
+ * This optionally maps a permission to an operation. If there
+ * is no permission associated with an operation, it is null.
+ */
+ private static String[] sOpPerms = new String[] {
+ android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.ACCESS_FINE_LOCATION,
+ null,
+ android.Manifest.permission.VIBRATE,
+ android.Manifest.permission.READ_CONTACTS,
+ android.Manifest.permission.WRITE_CONTACTS,
+ android.Manifest.permission.READ_CALL_LOG,
+ android.Manifest.permission.WRITE_CALL_LOG,
+ android.Manifest.permission.READ_CALENDAR,
+ android.Manifest.permission.WRITE_CALENDAR,
+ null, // no permission required for notifications
+ android.Manifest.permission.ACCESS_WIFI_STATE,
+ null, // neighboring cells shares the coarse location perm
+ android.Manifest.permission.CALL_PHONE,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.WRITE_SMS,
+ android.Manifest.permission.RECEIVE_SMS,
+ android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
+ android.Manifest.permission.RECEIVE_MMS,
+ android.Manifest.permission.RECEIVE_WAP_PUSH,
+ android.Manifest.permission.SEND_SMS,
+ android.Manifest.permission.READ_SMS,
+ android.Manifest.permission.WRITE_SMS,
+ android.Manifest.permission.WRITE_SETTINGS,
+ android.Manifest.permission.SYSTEM_ALERT_WINDOW,
+ android.Manifest.permission.ACCESS_NOTIFICATIONS,
+ android.Manifest.permission.CAMERA,
+ android.Manifest.permission.RECORD_AUDIO,
+ null, // no permission for playing audio
+ null, // no permission for reading clipboard
+ null, // no permission for writing clipboard
+ };
+
+ /**
+ * Retrieve the op switch that controls the given operation.
+ */
+ public static int opToSwitch(int op) {
+ return sOpToSwitch[op];
+ }
+
+ /**
+ * Retrieve a non-localized name for the operation, for debugging output.
+ */
+ public static String opToName(int op) {
+ if (op == OP_NONE) return "NONE";
+ return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")");
+ }
+
+ /**
+ * Retrieve the permission associated with an operation, or null if there is not one.
+ */
+ public static String opToPermission(int op) {
+ return sOpPerms[op];
+ }
+
+ /**
+ * Class holding all of the operation information associated with an app.
+ */
+ public static class PackageOps implements Parcelable {
+ private final String mPackageName;
+ private final int mUid;
+ private final List<OpEntry> mEntries;
+
+ public PackageOps(String packageName, int uid, List<OpEntry> entries) {
+ mPackageName = packageName;
+ mUid = uid;
+ mEntries = entries;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public List<OpEntry> getOps() {
+ return mEntries;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mUid);
+ dest.writeInt(mEntries.size());
+ for (int i=0; i<mEntries.size(); i++) {
+ mEntries.get(i).writeToParcel(dest, flags);
+ }
+ }
+
+ PackageOps(Parcel source) {
+ mPackageName = source.readString();
+ mUid = source.readInt();
+ mEntries = new ArrayList<OpEntry>();
+ final int N = source.readInt();
+ for (int i=0; i<N; i++) {
+ mEntries.add(OpEntry.CREATOR.createFromParcel(source));
+ }
+ }
+
+ public static final Creator<PackageOps> CREATOR = new Creator<PackageOps>() {
+ @Override public PackageOps createFromParcel(Parcel source) {
+ return new PackageOps(source);
+ }
+
+ @Override public PackageOps[] newArray(int size) {
+ return new PackageOps[size];
+ }
+ };
+ }
+
+ /**
+ * Class holding the information about one unique operation of an application.
+ */
+ public static class OpEntry implements Parcelable {
+ private final int mOp;
+ private final int mMode;
+ private final long mTime;
+ private final long mRejectTime;
+ private final int mDuration;
+
+ public OpEntry(int op, int mode, long time, long rejectTime, int duration) {
+ mOp = op;
+ mMode = mode;
+ mTime = time;
+ mRejectTime = rejectTime;
+ mDuration = duration;
+ }
+
+ public int getOp() {
+ return mOp;
+ }
+
+ public int getMode() {
+ return mMode;
+ }
+
+ public long getTime() {
+ return mTime;
+ }
+
+ public long getRejectTime() {
+ return mRejectTime;
+ }
+
+ public boolean isRunning() {
+ return mDuration == -1;
+ }
+
+ public int getDuration() {
+ return mDuration == -1 ? (int)(System.currentTimeMillis()-mTime) : mDuration;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mOp);
+ dest.writeInt(mMode);
+ dest.writeLong(mTime);
+ dest.writeLong(mRejectTime);
+ dest.writeInt(mDuration);
+ }
+
+ OpEntry(Parcel source) {
+ mOp = source.readInt();
+ mMode = source.readInt();
+ mTime = source.readLong();
+ mRejectTime = source.readLong();
+ mDuration = source.readInt();
+ }
+
+ public static final Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
+ @Override public OpEntry createFromParcel(Parcel source) {
+ return new OpEntry(source);
+ }
+
+ @Override public OpEntry[] newArray(int size) {
+ return new OpEntry[size];
+ }
+ };
+ }
+
+ /**
+ * Callback for notification of changes to operation state.
+ */
+ public interface Callback {
+ public void opChanged(int op, String packageName);
+ }
+
+ public AppOpsManager(Context context, IAppOpsService service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Retrieve current operation state for all applications.
+ *
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ */
+ public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+ try {
+ return mService.getPackagesForOps(ops);
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve current operation state for one application.
+ *
+ * @param uid The uid of the application of interest.
+ * @param packageName The name of the application of interest.
+ * @param ops The set of operations you are interested in, or null if you want all of them.
+ */
+ public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, int[] ops) {
+ try {
+ return mService.getOpsForPackage(uid, packageName, ops);
+ } catch (RemoteException e) {
+ }
+ return null;
+ }
+
+ public void setMode(int code, int uid, String packageName, int mode) {
+ try {
+ mService.setMode(code, uid, packageName, mode);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void startWatchingMode(int op, String packageName, final Callback callback) {
+ synchronized (mModeWatchers) {
+ IAppOpsCallback cb = mModeWatchers.get(callback);
+ if (cb == null) {
+ cb = new IAppOpsCallback.Stub() {
+ public void opChanged(int op, String packageName) {
+ callback.opChanged(op, packageName);
+ }
+ };
+ mModeWatchers.put(callback, cb);
+ }
+ try {
+ mService.startWatchingMode(op, packageName, cb);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public void stopWatchingMode(Callback callback) {
+ synchronized (mModeWatchers) {
+ IAppOpsCallback cb = mModeWatchers.get(callback);
+ if (cb != null) {
+ try {
+ mService.stopWatchingMode(cb);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ public int checkOp(int op, int uid, String packageName) {
+ try {
+ int mode = mService.checkOperation(op, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Operation not allowed");
+ }
+ return mode;
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int checkOpNoThrow(int op, int uid, String packageName) {
+ try {
+ return mService.checkOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int noteOp(int op, int uid, String packageName) {
+ try {
+ int mode = mService.noteOperation(op, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Operation not allowed");
+ }
+ return mode;
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int noteOpNoThrow(int op, int uid, String packageName) {
+ try {
+ return mService.noteOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int noteOp(int op) {
+ return noteOp(op, Process.myUid(), mContext.getBasePackageName());
+ }
+
+ public int startOp(int op, int uid, String packageName) {
+ try {
+ int mode = mService.startOperation(op, uid, packageName);
+ if (mode == MODE_ERRORED) {
+ throw new SecurityException("Operation not allowed");
+ }
+ return mode;
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int startOpNoThrow(int op, int uid, String packageName) {
+ try {
+ return mService.startOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ return MODE_IGNORED;
+ }
+
+ public int startOp(int op) {
+ return startOp(op, Process.myUid(), mContext.getBasePackageName());
+ }
+
+ public void finishOp(int op, int uid, String packageName) {
+ try {
+ mService.finishOperation(op, uid, packageName);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void finishOp(int op) {
+ finishOp(op, Process.myUid(), mContext.getBasePackageName());
+ }
+}
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 3a67cec..132388e 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -22,6 +22,7 @@ import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
@@ -45,6 +46,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
new ArrayList<ComponentCallbacks>();
private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<ActivityLifecycleCallbacks>();
+ private ArrayList<OnProvideAssistData> mAssistCallbacks = null;
/** @hide */
public LoadedApk mLoadedApk;
@@ -59,6 +61,21 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
void onActivityDestroyed(Activity activity);
}
+ /**
+ * Callback interface for use with {@link Application#registerOnProvideAssistData}
+ * and {@link Application#unregisterOnProvideAssistData}.
+ */
+ public interface OnProvideAssistData {
+ /**
+ * This is called when the user is requesting an assist, to build a full
+ * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
+ * application. You can override this method to place into the bundle anything
+ * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
+ * of the assist Intent.
+ */
+ public void onProvideAssistData(Activity activity, Bundle data);
+ }
+
public Application() {
super(null);
}
@@ -137,7 +154,24 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
mActivityLifecycleCallbacks.remove(callback);
}
}
-
+
+ public void registerOnProvideAssistData(OnProvideAssistData callback) {
+ synchronized (this) {
+ if (mAssistCallbacks == null) {
+ mAssistCallbacks = new ArrayList<OnProvideAssistData>();
+ }
+ mAssistCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterOnProvideAssistData(OnProvideAssistData callback) {
+ synchronized (this) {
+ if (mAssistCallbacks != null) {
+ mAssistCallbacks.remove(callback);
+ }
+ }
+ }
+
// ------------------ Internal API ------------------
/**
@@ -232,4 +266,19 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
return callbacks;
}
+
+ /* package */ void dispatchOnProvideAssistData(Activity activity, Bundle data) {
+ Object[] callbacks;
+ synchronized (this) {
+ if (mAssistCallbacks == null) {
+ return;
+ }
+ callbacks = mAssistCallbacks.toArray();
+ }
+ if (callbacks != null) {
+ for (int i=0; i<callbacks.length; i++) {
+ ((OnProvideAssistData)callbacks[i]).onProvideAssistData(activity, data);
+ }
+ }
+ }
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7431765..f09c2fe 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -142,6 +142,21 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
+ public int getPackageUid(String packageName, int userHandle)
+ throws NameNotFoundException {
+ try {
+ int uid = mPM.getPackageUid(packageName, userHandle);
+ if (uid >= 0) {
+ return uid;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+
+ throw new NameNotFoundException(packageName);
+ }
+
+ @Override
public PermissionInfo getPermissionInfo(String name, int flags)
throws NameNotFoundException {
try {
@@ -411,17 +426,22 @@ final class ApplicationPackageManager extends PackageManager {
@Override
public List<PackageInfo> getInstalledPackages(int flags, int userId) {
try {
- final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>();
- PackageInfo lastItem = null;
- ParceledListSlice<PackageInfo> slice;
-
- do {
- final String lastKey = lastItem != null ? lastItem.packageName : null;
- slice = mPM.getInstalledPackages(flags, lastKey, userId);
- lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR);
- } while (!slice.isLastSlice());
+ ParceledListSlice<PackageInfo> slice = mPM.getInstalledPackages(flags, userId);
+ return slice.getList();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
- return packageInfos;
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<PackageInfo> getPackagesHoldingPermissions(
+ String[] permissions, int flags) {
+ final int userId = mContext.getUserId();
+ try {
+ ParceledListSlice<PackageInfo> slice = mPM.getPackagesHoldingPermissions(
+ permissions, flags, userId);
+ return slice.getList();
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
@@ -432,17 +452,8 @@ final class ApplicationPackageManager extends PackageManager {
public List<ApplicationInfo> getInstalledApplications(int flags) {
final int userId = mContext.getUserId();
try {
- final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>();
- ApplicationInfo lastItem = null;
- ParceledListSlice<ApplicationInfo> slice;
-
- do {
- final String lastKey = lastItem != null ? lastItem.packageName : null;
- slice = mPM.getInstalledApplications(flags, lastKey, userId);
- lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR);
- } while (!slice.isLastSlice());
-
- return applicationInfos;
+ ParceledListSlice<ApplicationInfo> slice = mPM.getInstalledApplications(flags, userId);
+ return slice.getList();
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
}
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 63aa5f9..b1c58f2 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -267,6 +267,9 @@ public abstract class ApplicationThreadNative extends Binder
Bundle testArgs = data.readBundle();
IBinder binder = data.readStrongBinder();
IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder);
+ binder = data.readStrongBinder();
+ IUiAutomationConnection uiAutomationConnection =
+ IUiAutomationConnection.Stub.asInterface(binder);
int testMode = data.readInt();
boolean openGlTrace = data.readInt() != 0;
boolean restrictedBackupMode = (data.readInt() != 0);
@@ -277,8 +280,9 @@ public abstract class ApplicationThreadNative extends Binder
Bundle coreSettings = data.readBundle();
bindApplication(packageName, info,
providers, testName, profileName, profileFd, autoStopProfiler,
- testArgs, testWatcher, testMode, openGlTrace, restrictedBackupMode,
- persistent, config, compatInfo, services, coreSettings);
+ testArgs, testWatcher, uiAutomationConnection, testMode,
+ openGlTrace, restrictedBackupMode, persistent, config, compatInfo,
+ services, coreSettings);
return true;
}
@@ -587,6 +591,17 @@ public abstract class ApplicationThreadNative extends Binder
reply.writeNoException();
return true;
}
+
+ case REQUEST_ACTIVITY_EXTRAS_TRANSACTION:
+ {
+ data.enforceInterface(IApplicationThread.descriptor);
+ IBinder activityToken = data.readStrongBinder();
+ IBinder requestToken = data.readStrongBinder();
+ int requestType = data.readInt();
+ requestActivityExtras(activityToken, requestToken, requestType);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -863,10 +878,11 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void bindApplication(String packageName, ApplicationInfo info,
List<ProviderInfo> providers, ComponentName testName, String profileName,
ParcelFileDescriptor profileFd, boolean autoStopProfiler, Bundle testArgs,
- IInstrumentationWatcher testWatcher, int debugMode, boolean openGlTrace,
- boolean restrictedBackupMode, boolean persistent,
- Configuration config, CompatibilityInfo compatInfo,
- Map<String, IBinder> services, Bundle coreSettings) throws RemoteException {
+ IInstrumentationWatcher testWatcher,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
+ Bundle coreSettings) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeString(packageName);
@@ -888,6 +904,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(autoStopProfiler ? 1 : 0);
data.writeBundle(testArgs);
data.writeStrongInterface(testWatcher);
+ data.writeStrongInterface(uiAutomationConnection);
data.writeInt(debugMode);
data.writeInt(openGlTrace ? 1 : 0);
data.writeInt(restrictedBackupMode ? 1 : 0);
@@ -1185,4 +1202,15 @@ class ApplicationThreadProxy implements IApplicationThread {
mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
data.recycle();
}
+
+ public void requestActivityExtras(IBinder activityToken, IBinder requestToken, int requestType)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ data.writeInterfaceToken(IApplicationThread.descriptor);
+ data.writeStrongBinder(activityToken);
+ data.writeStrongBinder(requestToken);
+ data.writeInt(requestType);
+ mRemote.transact(REQUEST_ACTIVITY_EXTRAS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+ data.recycle();
+ }
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f895ccc..734d435 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -47,11 +47,9 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.hardware.ISerialManager;
-import android.hardware.SensorManager;
import android.hardware.SerialManager;
import android.hardware.SystemSensorManager;
import android.hardware.display.DisplayManager;
-import android.hardware.input.IInputManager;
import android.hardware.input.InputManager;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbManager;
@@ -65,8 +63,6 @@ import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.INetworkPolicyManager;
import android.net.NetworkPolicyManager;
-import android.net.ThrottleManager;
-import android.net.IThrottleManager;
import android.net.Uri;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
@@ -109,6 +105,8 @@ import android.view.textservice.TextServicesManager;
import android.accounts.AccountManager;
import android.accounts.IAccountManager;
import android.app.admin.DevicePolicyManager;
+
+import com.android.internal.app.IAppOpsService;
import com.android.internal.os.IDropBoxManagerService;
import java.io.File;
@@ -462,7 +460,8 @@ class ContextImpl extends Context {
registerService(STORAGE_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
try {
- return new StorageManager(ctx.mMainThread.getHandler().getLooper());
+ return new StorageManager(
+ ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
} catch (RemoteException rex) {
Log.e(TAG, "Failed to create StorageManager", rex);
return null;
@@ -474,12 +473,6 @@ class ContextImpl extends Context {
return new TelephonyManager(ctx.getOuterContext());
}});
- registerService(THROTTLE_SERVICE, new StaticServiceFetcher() {
- public Object createStaticService() {
- IBinder b = ServiceManager.getService(THROTTLE_SERVICE);
- return new ThrottleManager(IThrottleManager.Stub.asInterface(b));
- }});
-
registerService(UI_MODE_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new UiModeManager();
@@ -499,7 +492,7 @@ class ContextImpl extends Context {
registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new SystemVibrator();
+ return new SystemVibrator(ctx);
}});
registerService(WALLPAPER_SERVICE, WALLPAPER_FETCHER);
@@ -530,11 +523,18 @@ class ContextImpl extends Context {
}});
registerService(USER_SERVICE, new ServiceFetcher() {
- public Object getService(ContextImpl ctx) {
+ public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(USER_SERVICE);
IUserManager service = IUserManager.Stub.asInterface(b);
return new UserManager(ctx, service);
}});
+
+ registerService(APP_OPS_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(APP_OPS_SERVICE);
+ IAppOpsService service = IAppOpsService.Stub.asInterface(b);
+ return new AppOpsManager(ctx, service);
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -624,7 +624,15 @@ class ContextImpl extends Context {
if (mPackageInfo != null) {
return mPackageInfo.getPackageName();
}
- throw new RuntimeException("Not supported in system context");
+ // No mPackageInfo means this is a Context for the system itself,
+ // and this here is its name.
+ return "android";
+ }
+
+ /** @hide */
+ @Override
+ public String getBasePackageName() {
+ return mBasePackageName != null ? mBasePackageName : getPackageName();
}
@Override
@@ -956,7 +964,7 @@ class ContextImpl extends Context {
public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
try {
ActivityManagerNative.getDefault().startActivityAsUser(
- mMainThread.getApplicationThread(), intent,
+ mMainThread.getApplicationThread(), getBasePackageName(), intent,
intent.resolveTypeIfNeeded(getContentResolver()),
null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, null, options,
user.getIdentifier());
@@ -1035,7 +1043,7 @@ class ContextImpl extends Context {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, null, false, false,
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, false,
getUserId());
} catch (RemoteException e) {
}
@@ -1049,7 +1057,21 @@ class ContextImpl extends Context {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, false, false,
+ Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE,
+ false, false, getUserId());
+ } catch (RemoteException e) {
+ }
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ warnIfCallingFromSystemProcess();
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.setAllowFds(false);
+ ActivityManagerNative.getDefault().broadcastIntent(
+ mMainThread.getApplicationThread(), intent, resolvedType, null,
+ Activity.RESULT_OK, null, null, receiverPermission, appOp, false, false,
getUserId());
} catch (RemoteException e) {
}
@@ -1064,7 +1086,7 @@ class ContextImpl extends Context {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, true, false,
+ Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, true, false,
getUserId());
} catch (RemoteException e) {
}
@@ -1075,6 +1097,15 @@ class ContextImpl extends Context {
String receiverPermission, BroadcastReceiver resultReceiver,
Handler scheduler, int initialCode, String initialData,
Bundle initialExtras) {
+ sendOrderedBroadcast(intent, receiverPermission, AppOpsManager.OP_NONE,
+ resultReceiver, scheduler, initialCode, initialData, initialExtras);
+ }
+
+ @Override
+ public void sendOrderedBroadcast(Intent intent,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
warnIfCallingFromSystemProcess();
IIntentReceiver rd = null;
if (resultReceiver != null) {
@@ -1098,8 +1129,8 @@ class ContextImpl extends Context {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
- initialCode, initialData, initialExtras, receiverPermission,
- true, false, getUserId());
+ initialCode, initialData, initialExtras, receiverPermission, appOp,
+ true, false, getUserId());
} catch (RemoteException e) {
}
}
@@ -1110,8 +1141,8 @@ class ContextImpl extends Context {
try {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(),
- intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false,
- user.getIdentifier());
+ intent, resolvedType, null, Activity.RESULT_OK, null, null, null,
+ AppOpsManager.OP_NONE, false, false, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1124,7 +1155,7 @@ class ContextImpl extends Context {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, receiverPermission, false, false,
+ Activity.RESULT_OK, null, null, receiverPermission, AppOpsManager.OP_NONE, false, false,
user.getIdentifier());
} catch (RemoteException e) {
}
@@ -1157,7 +1188,7 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, receiverPermission,
- true, false, user.getIdentifier());
+ AppOpsManager.OP_NONE, true, false, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1170,7 +1201,7 @@ class ContextImpl extends Context {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, null, false, true,
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, true,
getUserId());
} catch (RemoteException e) {
}
@@ -1205,7 +1236,7 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, null,
- true, true, getUserId());
+ AppOpsManager.OP_NONE, true, true, getUserId());
} catch (RemoteException e) {
}
}
@@ -1232,7 +1263,7 @@ class ContextImpl extends Context {
intent.setAllowFds(false);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
- Activity.RESULT_OK, null, null, null, false, true, user.getIdentifier());
+ Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, false, true, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1265,7 +1296,7 @@ class ContextImpl extends Context {
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, rd,
initialCode, initialData, initialExtras, null,
- true, true, user.getIdentifier());
+ AppOpsManager.OP_NONE, true, true, user.getIdentifier());
} catch (RemoteException e) {
}
}
@@ -1404,12 +1435,13 @@ class ContextImpl extends Context {
public boolean bindService(Intent service, ServiceConnection conn,
int flags) {
warnIfCallingFromSystemProcess();
- return bindService(service, conn, flags, UserHandle.getUserId(Process.myUid()));
+ return bindServiceAsUser(service, conn, flags, Process.myUserHandle());
}
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ UserHandle user) {
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
@@ -1431,7 +1463,7 @@ class ContextImpl extends Context {
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
- sd, flags, userHandle);
+ sd, flags, user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
@@ -1467,7 +1499,7 @@ class ContextImpl extends Context {
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null, getUserId());
+ className, profileFile, 0, arguments, null, null, getUserId());
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 32e40ee..26dc60d 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -1085,6 +1085,7 @@ public class DownloadManager {
values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
values.putNull(Downloads.Impl._DATA);
values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
+ values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0);
mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8af17a4..cf4c729 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -51,19 +51,19 @@ import java.util.List;
* {@hide}
*/
public interface IActivityManager extends IInterface {
- public int startActivity(IApplicationThread caller,
+ public int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int flags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
- public int startActivityAsUser(IApplicationThread caller,
+ public int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int flags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
- public WaitResult startActivityAndWait(IApplicationThread caller,
+ public WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int flags, String profileFile,
ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
- public int startActivityWithConfig(IApplicationThread caller,
+ public int startActivityWithConfig(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, Configuration newConfig,
Bundle options, int userId) throws RemoteException;
@@ -85,7 +85,7 @@ public interface IActivityManager extends IInterface {
public int broadcastIntent(IApplicationThread caller, Intent intent,
String resolvedType, IIntentReceiver resultTo, int resultCode,
String resultData, Bundle map, String requiredPermission,
- boolean serialized, boolean sticky, int userId) throws RemoteException;
+ int appOp, boolean serialized, boolean sticky, int userId) throws RemoteException;
public void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) throws RemoteException;
public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException;
public void attachApplication(IApplicationThread app) throws RemoteException;
@@ -158,8 +158,8 @@ public interface IActivityManager extends IInterface {
public void killApplicationProcess(String processName, int uid) throws RemoteException;
public boolean startInstrumentation(ComponentName className, String profileFile,
- int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
- throws RemoteException;
+ int flags, Bundle arguments, IInstrumentationWatcher watcher,
+ IUiAutomationConnection connection, int userId) throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
@@ -310,7 +310,7 @@ public interface IActivityManager extends IInterface {
public boolean dumpHeap(String process, int userId, boolean managed, String path,
ParcelFileDescriptor fd) throws RemoteException;
- public int startActivities(IApplicationThread caller,
+ public int startActivities(IApplicationThread caller, String callingPackage,
Intent[] intents, String[] resolvedTypes, IBinder resultTo,
Bundle options, int userId) throws RemoteException;
@@ -357,9 +357,10 @@ public interface IActivityManager extends IInterface {
public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData)
throws RemoteException;
- // This is not public because you need to be very careful in how you
+ // These are not public because you need to be very careful in how you
// manage your activity to make sure it is always the uid you expect.
public int getLaunchedFromUid(IBinder activityToken) throws RemoteException;
+ public String getLaunchedFromPackage(IBinder activityToken) throws RemoteException;
public void registerUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
public void unregisterUserSwitchObserver(IUserSwitchObserver observer) throws RemoteException;
@@ -368,6 +369,10 @@ public interface IActivityManager extends IInterface {
public long inputDispatchingTimedOut(int pid, boolean aboveSystem) throws RemoteException;
+ public Bundle getTopActivityExtras(int requestType) throws RemoteException;
+
+ public void reportTopActivityExtras(IBinder token, Bundle extras) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -624,4 +629,7 @@ public interface IActivityManager extends IInterface {
int INPUT_DISPATCHING_TIMED_OUT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+158;
int CLEAR_PENDING_BACKUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+159;
int GET_INTENT_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+160;
+ int GET_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+161;
+ int REPORT_TOP_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+162;
+ int GET_LAUNCHED_FROM_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+163;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 03a26d4..3189b31 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -90,7 +90,8 @@ public interface IApplicationThread extends IInterface {
void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers,
ComponentName testName, String profileName, ParcelFileDescriptor profileFd,
boolean autoStopProfiler, Bundle testArguments, IInstrumentationWatcher testWatcher,
- int debugMode, boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
+ IUiAutomationConnection uiAutomationConnection, int debugMode,
+ boolean openGlTrace, boolean restrictedBackupMode, boolean persistent,
Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
Bundle coreSettings) throws RemoteException;
void scheduleExit() throws RemoteException;
@@ -130,6 +131,8 @@ public interface IApplicationThread extends IInterface {
void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException;
void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException;
void unstableProviderDied(IBinder provider) throws RemoteException;
+ void requestActivityExtras(IBinder activityToken, IBinder requestToken, int requestType)
+ throws RemoteException;
String descriptor = "android.app.IApplicationThread";
@@ -179,4 +182,5 @@ public interface IApplicationThread extends IInterface {
int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44;
int DUMP_DB_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45;
int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46;
+ int REQUEST_ACTIVITY_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47;
}
diff --git a/core/java/android/net/IThrottleManager.aidl b/core/java/android/app/INotificationListener.aidl
index a12469d..f010a2a 100644
--- a/core/java/android/net/IThrottleManager.aidl
+++ b/core/java/android/app/INotificationListener.aidl
@@ -1,5 +1,5 @@
/**
- * Copyright (c) 2010, The Android Open Source Project
+ * Copyright (c) 2013, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,27 +14,13 @@
* limitations under the License.
*/
-package android.net;
+package android.app;
-import android.os.IBinder;
+import com.android.internal.statusbar.StatusBarNotification;
-/**
- * Interface that answers queries about data transfer amounts and throttling
- */
-/** {@hide} */
-interface IThrottleManager
+/** @hide */
+oneway interface INotificationListener
{
- long getByteCount(String iface, int dir, int period, int ago);
-
- int getThrottle(String iface);
-
- long getResetTime(String iface);
-
- long getPeriodStartTime(String iface);
-
- long getCliffThreshold(String iface, int cliff);
-
- int getCliffLevel(String iface, int cliff);
-
- String getHelpUri();
-}
+ void onNotificationPosted(in StatusBarNotification notification);
+ void onNotificationRemoved(in StatusBarNotification notification);
+} \ No newline at end of file
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 62d4962..14bcc0d 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -17,10 +17,13 @@
package android.app;
+import android.app.INotificationListener;
import android.app.ITransientNotification;
import android.app.Notification;
import android.content.Intent;
+import com.android.internal.statusbar.StatusBarNotification;
+
/** {@hide} */
interface INotificationManager
{
@@ -28,11 +31,17 @@ interface INotificationManager
void enqueueToast(String pkg, ITransientNotification callback, int duration);
void cancelToast(String pkg, ITransientNotification callback);
- void enqueueNotificationWithTag(String pkg, String tag, int id,
+ void enqueueNotificationWithTag(String pkg, String basePkg, String tag, int id,
in Notification notification, inout int[] idReceived, int userId);
void cancelNotificationWithTag(String pkg, String tag, int id, int userId);
- void setNotificationsEnabledForPackage(String pkg, boolean enabled);
- boolean areNotificationsEnabledForPackage(String pkg);
+ void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled);
+ boolean areNotificationsEnabledForPackage(String pkg, int uid);
+
+ StatusBarNotification[] getActiveNotifications(String callingPkg);
+ StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
+
+ void registerListener(in INotificationListener listener, int userid);
+ void unregisterListener(in INotificationListener listener, int userid);
}
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
new file mode 100644
index 0000000..09bf829
--- /dev/null
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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.accessibilityservice.IAccessibilityServiceClient;
+import android.graphics.Bitmap;
+import android.view.InputEvent;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * This interface contains privileged operations a shell program can perform
+ * on behalf of an instrumentation that it runs. These operations require
+ * special permissions which the shell user has but the instrumentation does
+ * not. Running privileged operations by the shell user on behalf of an
+ * instrumentation is needed for running UiTestCases.
+ *
+ * {@hide}
+ */
+interface IUiAutomationConnection {
+ void connect(IAccessibilityServiceClient client);
+ void disconnect();
+ boolean injectInputEvent(in InputEvent event, boolean sync);
+ boolean setRotation(int rotation);
+ Bitmap takeScreenshot(int width, int height);
+ void shutdown();
+}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e0856ae..e7bf305 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -27,6 +27,7 @@ import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Looper;
import android.os.MessageQueue;
import android.os.PerformanceCollector;
import android.os.Process;
@@ -48,7 +49,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
-
/**
* Base class for implementing application instrumentation code. When running
* with instrumentation turned on, this class will be instantiated for you
@@ -58,6 +58,7 @@ import java.util.List;
* &lt;instrumentation&gt; tag.
*/
public class Instrumentation {
+
/**
* If included in the status or final bundle sent to an IInstrumentationWatcher, this key
* identifies the class that is writing the report. This can be used to provide more structured
@@ -72,7 +73,7 @@ public class Instrumentation {
* instrumentation can also be launched, and results collected, by an automated system.
*/
public static final String REPORT_KEY_STREAMRESULT = "stream";
-
+
private static final String TAG = "Instrumentation";
private final Object mSync = new Object();
@@ -85,9 +86,11 @@ public class Instrumentation {
private List<ActivityWaiter> mWaitingActivities;
private List<ActivityMonitor> mActivityMonitors;
private IInstrumentationWatcher mWatcher;
+ private IUiAutomationConnection mUiAutomationConnection;
private boolean mAutomaticPerformanceSnapshots = false;
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
+ private UiAutomation mUiAutomation;
public Instrumentation() {
}
@@ -1410,7 +1413,7 @@ public class Instrumentation {
intent.setAllowFds(false);
intent.migrateExtraStreamToClipData();
int result = ActivityManagerNative.getDefault()
- .startActivity(whoThread, intent,
+ .startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
@@ -1468,15 +1471,16 @@ public class Instrumentation {
resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
}
int result = ActivityManagerNative.getDefault()
- .startActivities(whoThread, intents, resolvedTypes, token, options,
- userId);
+ .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
+ token, options, userId);
checkStartActivityResult(result, intents[0]);
} catch (RemoteException e) {
}
}
/**
- * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)},
+ * Like {@link #execStartActivity(android.content.Context, android.os.IBinder,
+ * android.os.IBinder, Fragment, android.content.Intent, int, android.os.Bundle)},
* but for calls from a {#link Fragment}.
*
* @param who The Context from which the activity is being started.
@@ -1525,7 +1529,7 @@ public class Instrumentation {
intent.setAllowFds(false);
intent.migrateExtraStreamToClipData();
int result = ActivityManagerNative.getDefault()
- .startActivity(whoThread, intent,
+ .startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mWho : null,
requestCode, 0, null, null, options);
@@ -1585,7 +1589,7 @@ public class Instrumentation {
intent.setAllowFds(false);
intent.migrateExtraStreamToClipData();
int result = ActivityManagerNative.getDefault()
- .startActivityAsUser(whoThread, intent,
+ .startActivityAsUser(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options, user.getIdentifier());
@@ -1597,13 +1601,14 @@ public class Instrumentation {
/*package*/ final void init(ActivityThread thread,
Context instrContext, Context appContext, ComponentName component,
- IInstrumentationWatcher watcher) {
+ IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
mThread = thread;
mMessageQueue = mThread.getLooper().myQueue();
mInstrContext = instrContext;
mAppContext = appContext;
mComponent = component;
mWatcher = watcher;
+ mUiAutomationConnection = uiAutomationConnection;
}
/*package*/ static void checkStartActivityResult(int res, Object intent) {
@@ -1637,18 +1642,48 @@ public class Instrumentation {
}
private final void validateNotAppThread() {
- if (ActivityThread.currentActivityThread() != null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
throw new RuntimeException(
"This method can not be called from the main application thread");
}
}
+ /**
+ * Gets the {@link UiAutomation} instance.
+ * <p>
+ * <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}
+ * work across application boundaries while the APIs exposed by the instrumentation
+ * do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will
+ * not allow you to inject the event in an app different from the instrumentation
+ * target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}
+ * will work regardless of the current application.
+ * </p>
+ * <p>
+ * A typical test case should be using either the {@link UiAutomation} or
+ * {@link Instrumentation} APIs. Using both APIs at the same time is not
+ * a mistake by itself but a client has to be aware of the APIs limitations.
+ * </p>
+ * @return The UI automation instance.
+ *
+ * @see UiAutomation
+ */
+ public UiAutomation getUiAutomation() {
+ if (mUiAutomationConnection != null) {
+ if (mUiAutomation == null) {
+ mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
+ mUiAutomationConnection);
+ mUiAutomation.connect();
+ }
+ return mUiAutomation;
+ }
+ return null;
+ }
+
private final class InstrumentationThread extends Thread {
public InstrumentationThread(String name) {
super(name);
}
public void run() {
- IActivityManager am = ActivityManagerNative.getDefault();
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
} catch (RuntimeException e) {
@@ -1659,9 +1694,13 @@ public class Instrumentation {
startPerformanceSnapshot();
}
onStart();
+ if (mUiAutomation != null) {
+ mUiAutomation.disconnect();
+ mUiAutomation = null;
+ }
}
}
-
+
private static final class EmptyRunnable implements Runnable {
public void run() {
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 0a9ed58..2224490 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -362,7 +362,12 @@ public final class LoadedApk {
try {
pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());
} catch (RemoteException e) {
- throw new AssertionError(e);
+ throw new IllegalStateException("Unable to get package info for "
+ + mPackageName + "; is system dying?", e);
+ }
+ if (pi == null) {
+ throw new IllegalStateException("Unable to get package info for "
+ + mPackageName + "; is package not installed?");
}
/*
* Two possible indications that this package could be
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 3f8e16c..ebca041 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -25,15 +25,11 @@ import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.util.IntProperty;
-import android.util.Log;
-import android.util.Slog;
import android.util.TypedValue;
import android.view.View;
import android.widget.ProgressBar;
@@ -436,14 +432,23 @@ public class Notification implements Parcelable
* @hide
*/
public static final String EXTRA_PEOPLE = "android.people";
-
- private Bundle extras;
+ /** @hide */
+ public static final String EXTRA_TITLE = "android.title";
+ /** @hide */
+ public static final String EXTRA_TEXT = "android.text";
+ /** @hide */
+ public static final String EXTRA_SUBTEXT = "android.subtext";
+ /** @hide */
+ public static final String EXTRA_SMALL_ICON = "android.icon";
+
+ /** @hide */
+ public Bundle extras = new Bundle();
/**
* Structure to encapsulate an "action", including title and icon, that can be attached to a Notification.
* @hide
*/
- private static class Action implements Parcelable {
+ public static class Action implements Parcelable {
public int icon;
public CharSequence title;
public PendingIntent actionIntent;
@@ -495,7 +500,10 @@ public class Notification implements Parcelable
};
}
- private Action[] actions;
+ /**
+ * @hide
+ */
+ public Action[] actions;
/**
* Constructs a Notification object with default values.
@@ -589,11 +597,10 @@ public class Notification implements Parcelable
kind = parcel.createStringArray(); // may set kind to null
- if (parcel.readInt() != 0) {
- extras = parcel.readBundle();
- }
+ extras = parcel.readBundle(); // may be null
+
+ actions = parcel.createTypedArray(Action.CREATOR); // may be null
- actions = parcel.createTypedArray(Action.CREATOR);
if (parcel.readInt() != 0) {
bigContentView = RemoteViews.CREATOR.createFromParcel(parcel);
}
@@ -602,7 +609,11 @@ public class Notification implements Parcelable
@Override
public Notification clone() {
Notification that = new Notification();
+ cloneInto(that);
+ return that;
+ }
+ private void cloneInto(Notification that) {
that.when = this.when;
that.icon = this.icon;
that.number = this.number;
@@ -656,15 +667,16 @@ public class Notification implements Parcelable
}
- that.actions = new Action[this.actions.length];
- for(int i=0; i<this.actions.length; i++) {
- that.actions[i] = this.actions[i].clone();
+ if (this.actions != null) {
+ that.actions = new Action[this.actions.length];
+ for(int i=0; i<this.actions.length; i++) {
+ that.actions[i] = this.actions[i].clone();
+ }
}
+
if (this.bigContentView != null) {
that.bigContentView = this.bigContentView.clone();
}
-
- return that;
}
public int describeContents() {
@@ -745,14 +757,9 @@ public class Notification implements Parcelable
parcel.writeStringArray(kind); // ok for null
- if (extras != null) {
- parcel.writeInt(1);
- extras.writeToParcel(parcel, 0);
- } else {
- parcel.writeInt(0);
- }
+ parcel.writeBundle(extras); // null ok
- parcel.writeTypedArray(actions, 0);
+ parcel.writeTypedArray(actions, 0); // null ok
if (bigContentView != null) {
parcel.writeInt(1);
@@ -800,35 +807,29 @@ public class Notification implements Parcelable
@Deprecated
public void setLatestEventInfo(Context context,
CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
- // TODO: rewrite this to use Builder
- RemoteViews contentView = new RemoteViews(context.getPackageName(),
- R.layout.notification_template_base);
- if (this.icon != 0) {
- contentView.setImageViewResource(R.id.icon, this.icon);
- }
- if (priority < PRIORITY_LOW) {
- contentView.setInt(R.id.icon,
- "setBackgroundResource", R.drawable.notification_template_icon_low_bg);
- contentView.setInt(R.id.status_bar_latest_event_content,
- "setBackgroundResource", R.drawable.notification_bg_low);
- }
+ Notification.Builder builder = new Notification.Builder(context);
+
+ // First, ensure that key pieces of information that may have been set directly
+ // are preserved
+ builder.setWhen(this.when);
+ builder.setSmallIcon(this.icon);
+ builder.setPriority(this.priority);
+ builder.setTicker(this.tickerText);
+ builder.setNumber(this.number);
+ builder.mFlags = this.flags;
+ builder.setSound(this.sound, this.audioStreamType);
+ builder.setDefaults(this.defaults);
+ builder.setVibrate(this.vibrate);
+
+ // now apply the latestEventInfo fields
if (contentTitle != null) {
- contentView.setTextViewText(R.id.title, contentTitle);
+ builder.setContentTitle(contentTitle);
}
if (contentText != null) {
- contentView.setTextViewText(R.id.text, contentText);
+ builder.setContentText(contentText);
}
- if (this.when != 0) {
- contentView.setViewVisibility(R.id.time, View.VISIBLE);
- contentView.setLong(R.id.time, "setTime", when);
- }
- if (this.number != 0) {
- NumberFormat f = NumberFormat.getIntegerInstance();
- contentView.setTextViewText(R.id.info, f.format(this.number));
- }
-
- this.contentView = contentView;
- this.contentIntent = contentIntent;
+ builder.setContentIntent(contentIntent);
+ builder.buildInto(this);
}
@Override
@@ -1615,11 +1616,20 @@ public class Notification implements Parcelable
n.kind = null;
}
n.priority = mPriority;
- n.extras = mExtras != null ? new Bundle(mExtras) : null;
if (mActions.size() > 0) {
n.actions = new Action[mActions.size()];
mActions.toArray(n.actions);
}
+
+ n.extras = mExtras != null ? new Bundle(mExtras) : new Bundle();
+
+ // Store original information used in the construction of this object
+ n.extras.putCharSequence(EXTRA_TITLE, mContentTitle);
+ n.extras.putCharSequence(EXTRA_TEXT, mContentText);
+ n.extras.putCharSequence(EXTRA_SUBTEXT, mSubText);
+ n.extras.putInt(EXTRA_SMALL_ICON, mSmallIcon);
+ //n.extras.putByteArray(EXTRA_LARGE_ICON, ...
+
return n;
}
@@ -1642,6 +1652,16 @@ public class Notification implements Parcelable
return buildUnstyled();
}
}
+
+ /**
+ * Apply this Builder to an existing {@link Notification} object.
+ *
+ * @hide
+ */
+ public Notification buildInto(Notification n) {
+ build().cloneInto(n);
+ return n;
+ }
}
@@ -1882,6 +1902,9 @@ public class Notification implements Parcelable
checkBuilder();
Notification wip = mBuilder.buildUnstyled();
wip.bigContentView = makeBigContentView();
+
+ wip.extras.putCharSequence(EXTRA_TEXT, mBigText);
+
return wip;
}
}
@@ -1981,6 +2004,14 @@ public class Notification implements Parcelable
checkBuilder();
Notification wip = mBuilder.buildUnstyled();
wip.bigContentView = makeBigContentView();
+
+ StringBuilder builder = new StringBuilder();
+ for (CharSequence str : mTexts) {
+ builder.append(str);
+ builder.append("\n");
+ }
+ wip.extras.putCharSequence(EXTRA_TEXT, builder);
+
return wip;
}
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 0acad75..5e69128 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -129,8 +129,8 @@ public class NotificationManager
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
- service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
- UserHandle.myUserId());
+ service.enqueueNotificationWithTag(pkg, mContext.getBasePackageName(), tag, id,
+ notification, idOut, UserHandle.myUserId());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
@@ -151,8 +151,8 @@ public class NotificationManager
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
try {
- service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
- user.getIdentifier());
+ service.enqueueNotificationWithTag(pkg, mContext.getBasePackageName(), tag, id,
+ notification, idOut, user.getIdentifier());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 2897ee0..37804e9 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -41,7 +41,7 @@ import android.util.AndroidException;
* 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 PendingIntent:
- * often, for example, the base Intent you supply will have the component
+ * almost always, for example, the base Intent you supply should have the component
* name explicitly set to one of your own components, to ensure it is ultimately
* sent there and nowhere else.
*
@@ -200,6 +200,11 @@ public final class PendingIntent implements Parcelable {
* existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK
* Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -227,6 +232,11 @@ public final class PendingIntent implements Parcelable {
* existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK
* Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -313,6 +323,11 @@ public final class PendingIntent implements Parcelable {
* UI the user actually sees when the intents are started.
* </p>
*
+ * <p class="note">For security reasons, the {@link android.content.Intent} objects
+ * you supply here should almost always be <em>explicit intents</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -359,6 +374,11 @@ public final class PendingIntent implements Parcelable {
* UI the user actually sees when the intents are started.
* </p>
*
+ * <p class="note">For security reasons, the {@link android.content.Intent} objects
+ * you supply here should almost always be <em>explicit intents</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the activity.
* @param requestCode Private request code for the sender (currently
@@ -423,6 +443,11 @@ public final class PendingIntent implements Parcelable {
* Retrieve a PendingIntent that will perform a broadcast, like calling
* {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should perform
* the broadcast.
* @param requestCode Private request code for the sender (currently
@@ -473,6 +498,11 @@ public final class PendingIntent implements Parcelable {
* {@link Context#startService Context.startService()}. The start
* arguments given to the service will come from the extras of the Intent.
*
+ * <p class="note">For security reasons, the {@link android.content.Intent}
+ * you supply here should almost always be an <em>explicit intent</em>,
+ * that is specify an explicit component to be delivered to through
+ * {@link Intent#setClass(android.content.Context, Class)} Intent.setClass</p>
+ *
* @param context The Context in which this PendingIntent should start
* the service.
* @param requestCode Private request code for the sender (currently
@@ -707,6 +737,15 @@ public final class PendingIntent implements Parcelable {
* sending the Intent. The returned string is supplied by the system, so
* that an application can not spoof its package.
*
+ * <p class="note">Be careful about how you use this. All this tells you is
+ * who created the PendingIntent. It does <strong>not</strong> tell you who
+ * handed the PendingIntent to you: that is, PendingIntent objects are intended to be
+ * passed between applications, so the PendingIntent you receive from an application
+ * could actually be one it received from another application, meaning the result
+ * you get here will identify the original application. Because of this, you should
+ * only use this information to identify who you expect to be interacting with
+ * through a {@link #send} call, not who gave you the PendingIntent.</p>
+ *
* @return The package name of the PendingIntent, or null if there is
* none associated with it.
*/
@@ -726,6 +765,15 @@ public final class PendingIntent implements Parcelable {
* sending the Intent. The returned integer is supplied by the system, so
* that an application can not spoof its uid.
*
+ * <p class="note">Be careful about how you use this. All this tells you is
+ * who created the PendingIntent. It does <strong>not</strong> tell you who
+ * handed the PendingIntent to you: that is, PendingIntent objects are intended to be
+ * passed between applications, so the PendingIntent you receive from an application
+ * could actually be one it received from another application, meaning the result
+ * you get here will identify the original application. Because of this, you should
+ * only use this information to identify who you expect to be interacting with
+ * through a {@link #send} call, not who gave you the PendingIntent.</p>
+ *
* @return The uid of the PendingIntent, or -1 if there is
* none associated with it.
*/
@@ -747,6 +795,15 @@ public final class PendingIntent implements Parcelable {
* {@link android.os.Process#myUserHandle() Process.myUserHandle()} for
* more explanation of user handles.
*
+ * <p class="note">Be careful about how you use this. All this tells you is
+ * who created the PendingIntent. It does <strong>not</strong> tell you who
+ * handed the PendingIntent to you: that is, PendingIntent objects are intended to be
+ * passed between applications, so the PendingIntent you receive from an application
+ * could actually be one it received from another application, meaning the result
+ * you get here will identify the original application. Because of this, you should
+ * only use this information to identify who you expect to be interacting with
+ * through a {@link #send} call, not who gave you the PendingIntent.</p>
+ *
* @return The user handle of the PendingIntent, or null if there is
* none associated with it.
*/
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 6382cee..7dfc589 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -846,8 +846,8 @@ public class SearchManager
*
* @hide
*/
- public Intent getAssistIntent(Context context) {
- return getAssistIntent(context, UserHandle.myUserId());
+ public Intent getAssistIntent(Context context, boolean inclContext) {
+ return getAssistIntent(context, inclContext, UserHandle.myUserId());
}
/**
@@ -856,7 +856,7 @@ public class SearchManager
*
* @hide
*/
- public Intent getAssistIntent(Context context, int userHandle) {
+ public Intent getAssistIntent(Context context, boolean inclContext, int userHandle) {
try {
if (mService == null) {
return null;
@@ -867,6 +867,13 @@ public class SearchManager
}
Intent intent = new Intent(Intent.ACTION_ASSIST);
intent.setComponent(comp);
+ if (inclContext) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ Bundle extras = am.getTopActivityExtras(0);
+ if (extras != null) {
+ intent.replaceExtras(extras);
+ }
+ }
return intent;
} catch (RemoteException re) {
Log.e(TAG, "getAssistIntent() failed: " + re);
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
new file mode 100644
index 0000000..7d02342
--- /dev/null
+++ b/core/java/android/app/UiAutomation.java
@@ -0,0 +1,699 @@
+/*
+ * Copyright (C) 2013 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.accessibilityservice.AccessibilityService.Callbacks;
+import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.hardware.display.DisplayManagerGlobal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.Display;
+import android.view.InputEvent;
+import android.view.Surface;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Class for interacting with the device's UI by simulation user actions and
+ * introspection of the screen content. It relies on the platform accessibility
+ * APIs to introspect the screen and to perform some actions on the remote view
+ * tree. It also allows injecting of arbitrary raw input events simulating user
+ * interaction with keyboards and touch devices. One can think of a UiAutomation
+ * as a special type of {@link android.accessibilityservice.AccessibilityService}
+ * which does not provide hooks for the service life cycle and exposes other
+ * APIs that are useful for UI test automation.
+ * <p>
+ * The APIs exposed by this class are low-level to maximize flexibility when
+ * developing UI test automation tools and libraries. Generally, a UiAutomation
+ * client should be using a higher-level library or implement high-level functions.
+ * For example, performing a tap on the screen requires construction and injecting
+ * of a touch down and up events which have to be delivered to the system by a
+ * call to {@link #injectInputEvent(InputEvent, boolean)}.
+ * </p>
+ * <p>
+ * The APIs exposed by this class operate across applications enabling a client
+ * to write tests that cover use cases spanning over multiple applications. For
+ * example, going to the settings application to change a setting and then
+ * interacting with another application whose behavior depends on that setting.
+ * </p>
+ */
+public final class UiAutomation {
+
+ private static final String LOG_TAG = UiAutomation.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ private static final int CONNECTION_ID_UNDEFINED = -1;
+
+ private static final long CONNECT_TIMEOUT_MILLIS = 5000;
+
+ /** Rotation constant: Unfreeze rotation (rotating the device changes its rotation state). */
+ public static final int ROTATION_UNFREEZE = -2;
+
+ /** Rotation constant: Freeze rotation to its current state. */
+ public static final int ROTATION_FREEZE_CURRENT = -1;
+
+ /** Rotation constant: Freeze rotation to 0 degrees (natural orientation) */
+ public static final int ROTATION_FREEZE_0 = Surface.ROTATION_0;
+
+ /** Rotation constant: Freeze rotation to 90 degrees . */
+ public static final int ROTATION_FREEZE_90 = Surface.ROTATION_90;
+
+ /** Rotation constant: Freeze rotation to 180 degrees . */
+ public static final int ROTATION_FREEZE_180 = Surface.ROTATION_180;
+
+ /** Rotation constant: Freeze rotation to 270 degrees . */
+ public static final int ROTATION_FREEZE_270 = Surface.ROTATION_270;
+
+ private final Object mLock = new Object();
+
+ private final ArrayList<AccessibilityEvent> mEventQueue = new ArrayList<AccessibilityEvent>();
+
+ private final IAccessibilityServiceClient mClient;
+
+ private final IUiAutomationConnection mUiAutomationConnection;
+
+ private int mConnectionId = CONNECTION_ID_UNDEFINED;
+
+ private OnAccessibilityEventListener mOnAccessibilityEventListener;
+
+ private boolean mWaitingForEventDelivery;
+
+ private long mLastEventTimeMillis;
+
+ private boolean mIsConnecting;
+
+ /**
+ * Listener for observing the {@link AccessibilityEvent} stream.
+ */
+ public static interface OnAccessibilityEventListener {
+
+ /**
+ * Callback for receiving an {@link AccessibilityEvent}.
+ * <p>
+ * <strong>Note:</strong> This method is <strong>NOT</strong> executed
+ * on the main test thread. The client is responsible for proper
+ * synchronization.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> It is responsibility of the client
+ * to recycle the received events to minimize object creation.
+ * </p>
+ *
+ * @param event The received event.
+ */
+ public void onAccessibilityEvent(AccessibilityEvent event);
+ }
+
+ /**
+ * Listener for filtering accessibility events.
+ */
+ public static interface AccessibilityEventFilter {
+
+ /**
+ * Callback for determining whether an event is accepted or
+ * it is filtered out.
+ *
+ * @param event The event to process.
+ * @return True if the event is accepted, false to filter it out.
+ */
+ public boolean accept(AccessibilityEvent event);
+ }
+
+ /**
+ * Creates a new instance that will handle callbacks from the accessibility
+ * layer on the thread of the provided looper and perform requests for privileged
+ * operations on the provided connection.
+ *
+ * @param looper The looper on which to execute accessibility callbacks.
+ * @param connection The connection for performing privileged operations.
+ *
+ * @hide
+ */
+ public UiAutomation(Looper looper, IUiAutomationConnection connection) {
+ if (looper == null) {
+ throw new IllegalArgumentException("Looper cannot be null!");
+ }
+ if (connection == null) {
+ throw new IllegalArgumentException("Connection cannot be null!");
+ }
+ mUiAutomationConnection = connection;
+ mClient = new IAccessibilityServiceClientImpl(looper);
+ }
+
+ /**
+ * Connects this UiAutomation to the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void connect() {
+ synchronized (mLock) {
+ throwIfConnectedLocked();
+ if (mIsConnecting) {
+ return;
+ }
+ mIsConnecting = true;
+ }
+
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.connect(mClient);
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while connecting UiAutomation", re);
+ }
+
+ synchronized (mLock) {
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ try {
+ while (true) {
+ if (isConnectedLocked()) {
+ break;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = CONNECT_TIMEOUT_MILLIS - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new RuntimeException("Error while connecting UiAutomation");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mIsConnecting = false;
+ }
+ }
+ }
+
+ /**
+ * Disconnects this UiAutomation from the accessibility introspection APIs.
+ *
+ * @hide
+ */
+ public void disconnect() {
+ synchronized (mLock) {
+ if (mIsConnecting) {
+ throw new IllegalStateException(
+ "Cannot call disconnect() while connecting!");
+ }
+ throwIfNotConnectedLocked();
+ mConnectionId = CONNECTION_ID_UNDEFINED;
+ }
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.disconnect();
+ } catch (RemoteException re) {
+ throw new RuntimeException("Error while disconnecting UiAutomation", re);
+ }
+ }
+
+ /**
+ * The id of the {@link IAccessibilityInteractionConnection} for querying
+ * the screen content. This is here for legacy purposes since some tools use
+ * hidden APIs to introspect the screen.
+ *
+ * @hide
+ */
+ public int getConnectionId() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ return mConnectionId;
+ }
+ }
+
+ /**
+ * Sets a callback for observing the stream of {@link AccessibilityEvent}s.
+ *
+ * @param listener The callback.
+ */
+ public void setOnAccessibilityEventListener(OnAccessibilityEventListener listener) {
+ synchronized (mLock) {
+ mOnAccessibilityEventListener = listener;
+ }
+ }
+
+ /**
+ * Performs a global action. Such an action can be performed at any moment
+ * regardless of the current application or user location in that application.
+ * For example going back, going home, opening recents, etc.
+ *
+ * @param action The action to perform.
+ * @return Whether the action was successfully performed.
+ *
+ * @see AccessibilityService#GLOBAL_ACTION_BACK
+ * @see AccessibilityService#GLOBAL_ACTION_HOME
+ * @see AccessibilityService#GLOBAL_ACTION_NOTIFICATIONS
+ * @see AccessibilityService#GLOBAL_ACTION_RECENTS
+ */
+ public final boolean performGlobalAction(int action) {
+ final IAccessibilityServiceConnection connection;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connection = AccessibilityInteractionClient.getInstance()
+ .getConnection(mConnectionId);
+ }
+ // Calling out without a lock held.
+ if (connection != null) {
+ try {
+ return connection.performGlobalAction(action);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the an {@link AccessibilityServiceInfo} describing this UiAutomation.
+ * This method is useful if one wants to change some of the dynamically
+ * configurable properties at runtime.
+ *
+ * @return The accessibility service info.
+ *
+ * @see AccessibilityServiceInfo
+ */
+ public final AccessibilityServiceInfo getServiceInfo() {
+ final IAccessibilityServiceConnection connection;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connection = AccessibilityInteractionClient.getInstance()
+ .getConnection(mConnectionId);
+ }
+ // Calling out without a lock held.
+ if (connection != null) {
+ try {
+ return connection.getServiceInfo();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the {@link AccessibilityServiceInfo} that describes how this
+ * UiAutomation will be handled by the platform accessibility layer.
+ *
+ * @param info The info.
+ *
+ * @see AccessibilityServiceInfo
+ */
+ public final void setServiceInfo(AccessibilityServiceInfo info) {
+ final IAccessibilityServiceConnection connection;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ AccessibilityInteractionClient.getInstance().clearCache();
+ connection = AccessibilityInteractionClient.getInstance()
+ .getConnection(mConnectionId);
+ }
+ // Calling out without a lock held.
+ if (connection != null) {
+ try {
+ connection.setServiceInfo(info);
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
+ }
+ }
+ }
+
+ /**
+ * Gets the root {@link AccessibilityNodeInfo} in the active window.
+ *
+ * @return The root info.
+ */
+ public AccessibilityNodeInfo getRootInActiveWindow() {
+ final int connectionId;
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
+ }
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .getRootInActiveWindow(connectionId);
+ }
+
+ /**
+ * A method for injecting an arbitrary input event.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the event.
+ * </p>
+ * @param event The event to inject.
+ * @param sync Whether to inject the event synchronously.
+ * @return Whether event injection succeeded.
+ */
+ public boolean injectInputEvent(InputEvent event, boolean sync) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ try {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
+ }
+ // Calling out without a lock held.
+ return mUiAutomationConnection.injectInputEvent(event, sync);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while injecting input event!", re);
+ }
+ return false;
+ }
+
+ /**
+ * Sets the device rotation. A client can freeze the rotation in
+ * desired state or freeze the rotation to its current state or
+ * unfreeze the rotation (rotating the device changes its rotation
+ * state).
+ *
+ * @param rotation The desired rotation.
+ * @return Whether the rotation was set successfully.
+ *
+ * @see #ROTATION_FREEZE_0
+ * @see #ROTATION_FREEZE_90
+ * @see #ROTATION_FREEZE_180
+ * @see #ROTATION_FREEZE_270
+ * @see #ROTATION_FREEZE_CURRENT
+ * @see #ROTATION_UNFREEZE
+ */
+ public boolean setRotation(int rotation) {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ switch (rotation) {
+ case ROTATION_FREEZE_0:
+ case ROTATION_FREEZE_90:
+ case ROTATION_FREEZE_180:
+ case ROTATION_FREEZE_270:
+ case ROTATION_UNFREEZE:
+ case ROTATION_FREEZE_CURRENT: {
+ try {
+ // Calling out without a lock held.
+ mUiAutomationConnection.setRotation(rotation);
+ return true;
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while setting rotation!", re);
+ }
+ } return false;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation.");
+ }
+ }
+ }
+
+ /**
+ * Executes a command and waits for a specific accessibility event up to a
+ * given wait timeout. To detect a sequence of events one can implement a
+ * filter that keeps track of seen events of the expected sequence and
+ * returns true after the last event of that sequence is received.
+ * <p>
+ * <strong>Note:</strong> It is caller's responsibility to recycle the returned event.
+ * </p>
+ * @param command The command to execute.
+ * @param filter Filter that recognizes the expected event.
+ * @param timeoutMillis The wait timeout in milliseconds.
+ *
+ * @throws TimeoutException If the expected event is not received within the timeout.
+ */
+ public AccessibilityEvent executeAndWaitForEvent(Runnable command,
+ AccessibilityEventFilter filter, long timeoutMillis) throws TimeoutException {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+
+ mEventQueue.clear();
+ // Prepare to wait for an event.
+ mWaitingForEventDelivery = true;
+
+ // We will ignore events from previous interactions.
+ final long executionStartTimeMillis = SystemClock.uptimeMillis();
+
+ // Execute the command.
+ command.run();
+ try {
+ // Wait for the event.
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ // Drain the event queue
+ while (!mEventQueue.isEmpty()) {
+ AccessibilityEvent event = mEventQueue.remove(0);
+ // Ignore events from previous interactions.
+ if (event.getEventTime() <= executionStartTimeMillis) {
+ continue;
+ }
+ if (filter.accept(event)) {
+ return event;
+ }
+ event.recycle();
+ }
+ // Check if timed out and if not wait.
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
+ if (remainingTimeMillis <= 0) {
+ throw new TimeoutException("Expected event not received within: "
+ + timeoutMillis + " ms.");
+ }
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ } finally {
+ mWaitingForEventDelivery = false;
+ mEventQueue.clear();
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Waits for the accessibility event stream to become idle, which is not to
+ * have received an accessibility event within <code>idleTimeoutMillis</code>.
+ * The total time spent to wait for an idle accessibility event stream is bounded
+ * by the <code>globalTimeoutMillis</code>.
+ *
+ * @param idleTimeoutMillis The timeout in milliseconds between two events
+ * to consider the device idle.
+ * @param globalTimeoutMillis The maximal global timeout in milliseconds in
+ * which to wait for an idle state.
+ *
+ * @throws TimeoutException If no idle state was detected within
+ * <code>globalTimeoutMillis.</code>
+ */
+ public void waitForIdle(long idleTimeoutMillis, long globalTimeoutMillis)
+ throws TimeoutException {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ if (mLastEventTimeMillis <= 0) {
+ mLastEventTimeMillis = startTimeMillis;
+ }
+
+ while (true) {
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+ // Did we get idle state within the global timeout?
+ final long elapsedGlobalTimeMillis = currentTimeMillis - startTimeMillis;
+ final long remainingGlobalTimeMillis =
+ globalTimeoutMillis - elapsedGlobalTimeMillis;
+ if (remainingGlobalTimeMillis <= 0) {
+ throw new TimeoutException("No idle state with idle timeout: "
+ + idleTimeoutMillis + " within global timeout: "
+ + globalTimeoutMillis);
+ }
+ // Did we get an idle state within the idle timeout?
+ final long elapsedIdleTimeMillis = currentTimeMillis - mLastEventTimeMillis;
+ final long remainingIdleTimeMillis = idleTimeoutMillis - elapsedIdleTimeMillis;
+ if (remainingIdleTimeMillis <= 0) {
+ return;
+ }
+ try {
+ mLock.wait(remainingIdleTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ }
+
+ /**
+ * Takes a screenshot.
+ *
+ * @return The screenshot bitmap on success, null otherwise.
+ */
+ public Bitmap takeScreenshot() {
+ synchronized (mLock) {
+ throwIfNotConnectedLocked();
+ }
+ Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(Display.DEFAULT_DISPLAY);
+ Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ final int displayWidth = displaySize.x;
+ final int displayHeight = displaySize.y;
+
+ final float screenshotWidth;
+ final float screenshotHeight;
+
+ final int rotation = display.getRotation();
+ switch (rotation) {
+ case ROTATION_FREEZE_0: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_90: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ case ROTATION_FREEZE_180: {
+ screenshotWidth = displayWidth;
+ screenshotHeight = displayHeight;
+ } break;
+ case ROTATION_FREEZE_270: {
+ screenshotWidth = displayHeight;
+ screenshotHeight = displayWidth;
+ } break;
+ default: {
+ throw new IllegalArgumentException("Invalid rotation: "
+ + rotation);
+ }
+ }
+
+ // Take the screenshot
+ Bitmap screenShot = null;
+ try {
+ // Calling out without a lock held.
+ screenShot = mUiAutomationConnection.takeScreenshot((int) screenshotWidth,
+ (int) screenshotHeight);
+ if (screenShot == null) {
+ return null;
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while taking screnshot!", re);
+ return null;
+ }
+
+ // Rotate the screenshot to the current orientation
+ if (rotation != ROTATION_FREEZE_0) {
+ Bitmap unrotatedScreenShot = Bitmap.createBitmap(displayWidth, displayHeight,
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(unrotatedScreenShot);
+ canvas.translate(unrotatedScreenShot.getWidth() / 2,
+ unrotatedScreenShot.getHeight() / 2);
+ canvas.rotate(getDegreesForRotation(rotation));
+ canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
+ canvas.drawBitmap(screenShot, 0, 0, null);
+ canvas.setBitmap(null);
+ screenShot = unrotatedScreenShot;
+ }
+
+ // Optimization
+ screenShot.setHasAlpha(false);
+
+ return screenShot;
+ }
+
+ private static float getDegreesForRotation(int value) {
+ switch (value) {
+ case Surface.ROTATION_90: {
+ return 360f - 90f;
+ }
+ case Surface.ROTATION_180: {
+ return 360f - 180f;
+ }
+ case Surface.ROTATION_270: {
+ return 360f - 270f;
+ } default: {
+ return 0;
+ }
+ }
+ }
+
+ private boolean isConnectedLocked() {
+ return mConnectionId != CONNECTION_ID_UNDEFINED;
+ }
+
+ private void throwIfConnectedLocked() {
+ if (mConnectionId != CONNECTION_ID_UNDEFINED) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private void throwIfNotConnectedLocked() {
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("UiAutomation not connected!");
+ }
+ }
+
+ private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
+
+ public IAccessibilityServiceClientImpl(Looper looper) {
+ super(null, looper, new Callbacks() {
+ @Override
+ public void onSetConnectionId(int connectionId) {
+ synchronized (mLock) {
+ mConnectionId = connectionId;
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceConnected() {
+ /* do nothing */
+ }
+
+ @Override
+ public void onInterrupt() {
+ /* do nothing */
+ }
+
+ @Override
+ public boolean onGesture(int gestureId) {
+ /* do nothing */
+ return false;
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ synchronized (mLock) {
+ mLastEventTimeMillis = event.getEventTime();
+ if (mWaitingForEventDelivery) {
+ mEventQueue.add(AccessibilityEvent.obtain(event));
+ }
+ mLock.notifyAll();
+ }
+ // Calling out only without a lock held.
+ final OnAccessibilityEventListener listener = mOnAccessibilityEventListener;
+ if (listener != null) {
+ listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
new file mode 100644
index 0000000..06ef472
--- /dev/null
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2013 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.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.view.IWindowManager;
+import android.view.InputEvent;
+import android.view.SurfaceControl;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.IAccessibilityManager;
+
+/**
+ * This is a remote object that is passed from the shell to an instrumentation
+ * for enabling access to privileged operations which the shell can do and the
+ * instrumentation cannot. These privileged operations are needed for implementing
+ * a {@link UiAutomation} that enables across application testing by simulating
+ * user actions and performing screen introspection.
+ *
+ * @hide
+ */
+public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
+
+ private static final int INITIAL_FROZEN_ROTATION_UNSPECIFIED = -1;
+
+ private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService(Service.WINDOW_SERVICE));
+
+ private final Object mLock = new Object();
+
+ private int mInitialFrozenRotation = INITIAL_FROZEN_ROTATION_UNSPECIFIED;
+
+ private IAccessibilityServiceClient mClient;
+
+ private boolean mIsShutdown;
+
+ private int mOwningUid;
+
+ public void connect(IAccessibilityServiceClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("Client cannot be null!");
+ }
+ synchronized (mLock) {
+ throwIfShutdownLocked();
+ if (isConnectedLocked()) {
+ throw new IllegalStateException("Already connected.");
+ }
+ mOwningUid = Binder.getCallingUid();
+ registerUiTestAutomationServiceLocked(client);
+ storeRotationStateLocked();
+ }
+ }
+
+ @Override
+ public void disconnect() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("Already disconnected.");
+ }
+ mOwningUid = -1;
+ unregisterUiTestAutomationServiceLocked();
+ restoreRotationStateLocked();
+ }
+ }
+
+ @Override
+ public boolean injectInputEvent(InputEvent event, boolean sync) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
+ : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return InputManager.getInstance().injectInputEvent(event, mode);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean setRotation(int rotation) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (rotation == UiAutomation.ROTATION_UNFREEZE) {
+ mWindowManager.thawRotation();
+ } else {
+ mWindowManager.freezeRotation(rotation);
+ }
+ return true;
+ } catch (RemoteException re) {
+ /* ignore */
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
+ @Override
+ public Bitmap takeScreenshot(int width, int height) {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ throwIfNotConnectedLocked();
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return SurfaceControl.screenshot(width, height);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ synchronized (mLock) {
+ throwIfCalledByNotTrustedUidLocked();
+ throwIfShutdownLocked();
+ mIsShutdown = true;
+ if (isConnectedLocked()) {
+ disconnect();
+ }
+ }
+ }
+
+ private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client) {
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+ AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+ info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
+ info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
+ info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
+ try {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ manager.registerUiTestAutomationService(client, info);
+ mClient = client;
+ } catch (RemoteException re) {
+ throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+ }
+ }
+
+ private void unregisterUiTestAutomationServiceLocked() {
+ IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
+ ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
+ try {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ manager.unregisterUiTestAutomationService(mClient);
+ mClient = null;
+ } catch (RemoteException re) {
+ throw new IllegalStateException("Error while unregistering UiTestAutomationService",
+ re);
+ }
+ }
+
+ private void storeRotationStateLocked() {
+ try {
+ if (mWindowManager.isRotationFrozen()) {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mInitialFrozenRotation = mWindowManager.getRotation();
+ }
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ private void restoreRotationStateLocked() {
+ try {
+ if (mInitialFrozenRotation != INITIAL_FROZEN_ROTATION_UNSPECIFIED) {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mWindowManager.freezeRotation(mInitialFrozenRotation);
+ } else {
+ // Calling out with a lock held is fine since if the system
+ // process is gone the client calling in will be killed.
+ mWindowManager.thawRotation();
+ }
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+
+ private boolean isConnectedLocked() {
+ return mClient != null;
+ }
+
+ private void throwIfShutdownLocked() {
+ if (mIsShutdown) {
+ throw new IllegalStateException("Connection shutdown!");
+ }
+ }
+
+ private void throwIfNotConnectedLocked() {
+ if (!isConnectedLocked()) {
+ throw new IllegalStateException("Not connected!");
+ }
+ }
+
+ private void throwIfCalledByNotTrustedUidLocked() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != mOwningUid && mOwningUid != Process.SYSTEM_UID
+ && callingUid != 0 /*root*/) {
+ throw new SecurityException("Calling from not trusted UID!");
+ }
+ }
+}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 9ad33a5..44aa06f 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
@@ -254,6 +255,21 @@ public abstract class BackupAgent extends ContextWrapper {
filterSet.add(databaseDir);
filterSet.remove(sharedPrefsDir);
fullBackupFileTree(packageName, FullBackup.SHAREDPREFS_TREE_TOKEN, sharedPrefsDir, filterSet, data);
+
+ // getExternalFilesDir() location associated with this app. Technically there should
+ // not be any files here if the app does not properly have permission to access
+ // external storage, but edge cases happen. fullBackupFileTree() catches
+ // IOExceptions and similar, and treats them as non-fatal, so we rely on that; and
+ // we know a priori that processes running as the system UID are not permitted to
+ // access external storage, so we check for that as well to avoid nastygrams in
+ // the log.
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ File efLocation = getExternalFilesDir(null);
+ if (efLocation != null) {
+ fullBackupFileTree(packageName, FullBackup.MANAGED_EXTERNAL_TREE_TOKEN,
+ efLocation.getCanonicalPath(), null, data);
+ }
+ }
}
/**
@@ -274,6 +290,7 @@ public abstract class BackupAgent extends ContextWrapper {
String spDir;
String cacheDir;
String libDir;
+ String efDir = null;
String filePath;
ApplicationInfo appInfo = getApplicationInfo();
@@ -288,6 +305,14 @@ public abstract class BackupAgent extends ContextWrapper {
? null
: new File(appInfo.nativeLibraryDir).getCanonicalPath();
+ // may or may not have external files access to attempt backup/restore there
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ File efLocation = getExternalFilesDir(null);
+ if (efLocation != null) {
+ efDir = efLocation.getCanonicalPath();
+ }
+ }
+
// Now figure out which well-defined tree the file is placed in, working from
// most to least specific. We also specifically exclude the lib and cache dirs.
filePath = file.getCanonicalPath();
@@ -315,6 +340,9 @@ public abstract class BackupAgent extends ContextWrapper {
} else if (filePath.startsWith(mainDir)) {
domain = FullBackup.ROOT_TREE_TOKEN;
rootpath = mainDir;
+ } else if ((efDir != null) && filePath.startsWith(efDir)) {
+ domain = FullBackup.MANAGED_EXTERNAL_TREE_TOKEN;
+ rootpath = efDir;
} else {
Log.w(TAG, "File " + filePath + " is in an unsupported location; skipping");
return;
@@ -438,6 +466,14 @@ public abstract class BackupAgent extends ContextWrapper {
basePath = getSharedPrefsFile("foo").getParentFile().getCanonicalPath();
} else if (domain.equals(FullBackup.CACHE_TREE_TOKEN)) {
basePath = getCacheDir().getCanonicalPath();
+ } else if (domain.equals(FullBackup.MANAGED_EXTERNAL_TREE_TOKEN)) {
+ // make sure we can try to restore here before proceeding
+ if (Process.myUid() != Process.SYSTEM_UID) {
+ File efLocation = getExternalFilesDir(null);
+ if (efLocation != null) {
+ basePath = getExternalFilesDir(null).getCanonicalPath();
+ }
+ }
} else {
// Not a supported location
Log.i(TAG, "Data restored from non-app domain " + domain + ", ignoring");
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index f859599..2fe08f3 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -46,6 +46,7 @@ public class FullBackup {
public static final String DATA_TREE_TOKEN = "f";
public static final String DATABASE_TREE_TOKEN = "db";
public static final String SHAREDPREFS_TREE_TOKEN = "sp";
+ public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef";
public static final String CACHE_TREE_TOKEN = "c";
public static final String SHARED_STORAGE_TOKEN = "shared";
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index fa3bf4d..a470e70 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.DisplayMetrics;
+import android.util.Log;
import android.util.TypedValue;
import android.widget.RemoteViews;
import android.widget.RemoteViews.OnClickHandler;
@@ -55,38 +56,39 @@ public class AppWidgetHost {
Context mContext;
String mPackageName;
+ Handler mHandler;
+ int mHostId;
+ Callbacks mCallbacks = new Callbacks();
+ final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
+ private OnClickHandler mOnClickHandler;
class Callbacks extends IAppWidgetHost.Stub {
- public void updateAppWidget(int appWidgetId, RemoteViews views) {
+ public void updateAppWidget(int appWidgetId, RemoteViews views, int userId) {
if (isLocalBinder() && views != null) {
views = views.clone();
- views.setUser(mUser);
+ views.setUser(new UserHandle(userId));
}
- Message msg = mHandler.obtainMessage(HANDLE_UPDATE);
- msg.arg1 = appWidgetId;
- msg.obj = views;
+ Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, userId, views);
msg.sendToTarget();
}
- public void providerChanged(int appWidgetId, AppWidgetProviderInfo info) {
+ public void providerChanged(int appWidgetId, AppWidgetProviderInfo info, int userId) {
if (isLocalBinder() && info != null) {
info = info.clone();
}
- Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED);
- msg.arg1 = appWidgetId;
- msg.obj = info;
+ Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED,
+ appWidgetId, userId, info);
msg.sendToTarget();
}
- public void providersChanged() {
- Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED);
+ public void providersChanged(int userId) {
+ Message msg = mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED, userId, 0);
msg.sendToTarget();
}
- public void viewDataChanged(int appWidgetId, int viewId) {
- Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED);
- msg.arg1 = appWidgetId;
- msg.arg2 = viewId;
+ public void viewDataChanged(int appWidgetId, int viewId, int userId) {
+ Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
+ appWidgetId, viewId, userId);
msg.sendToTarget();
}
}
@@ -99,7 +101,7 @@ public class AppWidgetHost {
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_UPDATE: {
- updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
+ updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj, msg.arg2);
break;
}
case HANDLE_PROVIDER_CHANGED: {
@@ -107,26 +109,17 @@ public class AppWidgetHost {
break;
}
case HANDLE_PROVIDERS_CHANGED: {
- onProvidersChanged();
+ onProvidersChanged(msg.arg1);
break;
}
case HANDLE_VIEW_DATA_CHANGED: {
- viewDataChanged(msg.arg1, msg.arg2);
+ viewDataChanged(msg.arg1, msg.arg2, (Integer) msg.obj);
break;
}
}
}
}
- Handler mHandler;
-
- int mHostId;
- Callbacks mCallbacks = new Callbacks();
- final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
- private OnClickHandler mOnClickHandler;
- // Optionally set by lockscreen
- private UserHandle mUser;
-
public AppWidgetHost(Context context, int hostId) {
this(context, hostId, null, context.getMainLooper());
}
@@ -140,14 +133,9 @@ public class AppWidgetHost {
mOnClickHandler = handler;
mHandler = new UpdateHandler(looper);
mDisplayMetrics = context.getResources().getDisplayMetrics();
- mUser = Process.myUserHandle();
bindService();
}
- /** @hide */
- public void setUserId(int userId) {
- mUser = new UserHandle(userId);
- }
private static void bindService() {
synchronized (sServiceLock) {
@@ -163,23 +151,15 @@ public class AppWidgetHost {
* becomes visible, i.e. from onStart() in your Activity.
*/
public void startListening() {
- startListeningAsUser(UserHandle.myUserId());
- }
-
- /**
- * Start receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity
- * becomes visible, i.e. from onStart() in your Activity.
- * @hide
- */
- public void startListeningAsUser(int userId) {
int[] updatedIds;
ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();
+ final int userId = mContext.getUserId();
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
- updatedIds = sService.startListeningAsUser(
+ updatedIds = sService.startListening(
mCallbacks, mPackageName, mHostId, updatedViews, userId);
}
catch (RemoteException e) {
@@ -191,7 +171,7 @@ public class AppWidgetHost {
if (updatedViews.get(i) != null) {
updatedViews.get(i).setUser(new UserHandle(userId));
}
- updateAppWidgetView(updatedIds[i], updatedViews.get(i));
+ updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId);
}
}
@@ -201,26 +181,14 @@ public class AppWidgetHost {
*/
public void stopListening() {
try {
- sService.stopListeningAsUser(mHostId, UserHandle.myUserId());
+ sService.stopListening(mHostId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
- }
- /**
- * Stop receiving onAppWidgetChanged calls for your AppWidgets. Call this when your activity is
- * no longer visible, i.e. from onStop() in your Activity.
- * @hide
- */
- public void stopListeningAsUser(int userId) {
- try {
- sService.stopListeningAsUser(mHostId, userId);
- }
- catch (RemoteException e) {
- throw new RuntimeException("system server dead?", e);
- }
- // Also clear the views
+ // This is here because keyguard needs it since it'll be switching users after this call.
+ // If it turns out other apps need to call this often, we should re-think how this works.
clearViews();
}
@@ -230,11 +198,12 @@ public class AppWidgetHost {
* @return a appWidgetId
*/
public int allocateAppWidgetId() {
+
try {
if (mPackageName == null) {
mPackageName = mContext.getPackageName();
}
- return sService.allocateAppWidgetId(mPackageName, mHostId);
+ return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -247,7 +216,7 @@ public class AppWidgetHost {
* @return a appWidgetId
* @hide
*/
- public static int allocateAppWidgetIdForSystem(int hostId) {
+ public static int allocateAppWidgetIdForSystem(int hostId, int userId) {
checkCallerIsSystem();
try {
if (sService == null) {
@@ -256,7 +225,7 @@ public class AppWidgetHost {
Context systemContext =
(Context) ActivityThread.currentActivityThread().getSystemContext();
String packageName = systemContext.getPackageName();
- return sService.allocateAppWidgetId(packageName, hostId);
+ return sService.allocateAppWidgetId(packageName, hostId, userId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -272,7 +241,7 @@ public class AppWidgetHost {
if (sService == null) {
bindService();
}
- return sService.getAppWidgetIdsForHost(mHostId);
+ return sService.getAppWidgetIdsForHost(mHostId, mContext.getUserId());
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -297,7 +266,7 @@ public class AppWidgetHost {
synchronized (mViews) {
mViews.remove(appWidgetId);
try {
- sService.deleteAppWidgetId(appWidgetId);
+ sService.deleteAppWidgetId(appWidgetId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -309,13 +278,13 @@ public class AppWidgetHost {
* Stop listening to changes for this AppWidget.
* @hide
*/
- public static void deleteAppWidgetIdForSystem(int appWidgetId) {
+ public static void deleteAppWidgetIdForSystem(int appWidgetId, int userId) {
checkCallerIsSystem();
try {
if (sService == null) {
bindService();
}
- sService.deleteAppWidgetId(appWidgetId);
+ sService.deleteAppWidgetId(appWidgetId, userId);
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -331,7 +300,7 @@ public class AppWidgetHost {
*/
public void deleteHost() {
try {
- sService.deleteHost(mHostId);
+ sService.deleteHost(mHostId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -347,8 +316,16 @@ public class AppWidgetHost {
* </ul>
*/
public static void deleteAllHosts() {
+ deleteAllHosts(UserHandle.myUserId());
+ }
+
+ /**
+ * Private method containing a userId
+ * @hide
+ */
+ public static void deleteAllHosts(int userId) {
try {
- sService.deleteAllHosts();
+ sService.deleteAllHosts(userId);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -361,8 +338,9 @@ public class AppWidgetHost {
*/
public final AppWidgetHostView createView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
+ final int userId = context.getUserId();
AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
- view.setUserId(mUser.getIdentifier());
+ view.setUserId(userId);
view.setOnClickHandler(mOnClickHandler);
view.setAppWidget(appWidgetId, appWidget);
synchronized (mViews) {
@@ -370,9 +348,9 @@ public class AppWidgetHost {
}
RemoteViews views;
try {
- views = sService.getAppWidgetViews(appWidgetId);
+ views = sService.getAppWidgetViews(appWidgetId, userId);
if (views != null) {
- views.setUser(mUser);
+ views.setUser(new UserHandle(mContext.getUserId()));
}
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -422,10 +400,20 @@ public class AppWidgetHost {
* are added, updated or removed, or widget components are enabled or disabled.)
*/
protected void onProvidersChanged() {
- // Do nothing
+ onProvidersChanged(mContext.getUserId());
}
- void updateAppWidgetView(int appWidgetId, RemoteViews views) {
+ /**
+ * Private method containing a userId
+ * @hide
+ */
+ protected void onProvidersChanged(int userId) {
+ checkUserMatch(userId);
+ // Does nothing
+ }
+
+ void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) {
+ checkUserMatch(userId);
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
@@ -435,7 +423,8 @@ public class AppWidgetHost {
}
}
- void viewDataChanged(int appWidgetId, int viewId) {
+ void viewDataChanged(int appWidgetId, int viewId, int userId) {
+ checkUserMatch(userId);
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
@@ -445,6 +434,16 @@ public class AppWidgetHost {
}
}
+ // Ensure that the userId passed to us agrees with the one associated with this instance
+ // of AppWidgetHost.
+ // TODO: This should be removed in production code.
+ private void checkUserMatch(int userId) {
+ if (userId != mContext.getUserId()) {
+ throw new IllegalStateException(
+ "User ids don't match, userId=" + userId + ", mUserId=" + mContext.getUserId());
+ }
+ }
+
/**
* Clear the list of Views that have been created by this AppWidgetHost.
*/
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 6b1c3e2..e68d23a 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -16,6 +16,7 @@
package android.appwidget;
+import android.app.ActivityManagerNative;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -268,8 +269,8 @@ public class AppWidgetManager {
/**
* Sent when the custom extras for an AppWidget change.
*
- * @see AppWidgetProvider#onAppWidgetOptionsChanged
- * AppWidgetProvider.onAppWidgetOptionsChanged(Context context,
+ * @see AppWidgetProvider#onAppWidgetOptionsChanged
+ * AppWidgetProvider.onAppWidgetOptionsChanged(Context context,
* AppWidgetManager appWidgetManager, int appWidgetId, Bundle newExtras)
*/
public static final String ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS";
@@ -352,7 +353,7 @@ public class AppWidgetManager {
* It is okay to call this method both inside an {@link #ACTION_APPWIDGET_UPDATE} broadcast,
* and outside of the handler.
* This method will only work when called from the uid that owns the AppWidget provider.
- *
+ *
* <p>
* The total Bitmap memory used by the RemoteViews object cannot exceed that required to
* fill the screen 1.5 times, ie. (screen width x screen height x 4 x 1.5) bytes.
@@ -362,7 +363,7 @@ public class AppWidgetManager {
*/
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
- sService.updateAppWidgetIds(appWidgetIds, views);
+ sService.updateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -382,7 +383,7 @@ public class AppWidgetManager {
*/
public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
try {
- sService.updateAppWidgetOptions(appWidgetId, options);
+ sService.updateAppWidgetOptions(appWidgetId, options, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -402,7 +403,7 @@ public class AppWidgetManager {
*/
public Bundle getAppWidgetOptions(int appWidgetId) {
try {
- return sService.getAppWidgetOptions(appWidgetId);
+ return sService.getAppWidgetOptions(appWidgetId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -436,7 +437,7 @@ public class AppWidgetManager {
* Perform an incremental update or command on the widget(s) specified by appWidgetIds.
*
* This update differs from {@link #updateAppWidget(int[], RemoteViews)} in that the
- * RemoteViews object which is passed is understood to be an incomplete representation of the
+ * RemoteViews object which is passed is understood to be an incomplete representation of the
* widget, and hence does not replace the cached representation of the widget. As of API
* level 17, the new properties set within the views objects will be appended to the cached
* representation of the widget, and hence will persist.
@@ -458,7 +459,7 @@ public class AppWidgetManager {
*/
public void partiallyUpdateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
- sService.partiallyUpdateAppWidgetIds(appWidgetIds, views);
+ sService.partiallyUpdateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
} catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
@@ -507,7 +508,7 @@ public class AppWidgetManager {
*/
public void updateAppWidget(ComponentName provider, RemoteViews views) {
try {
- sService.updateAppWidgetProvider(provider, views);
+ sService.updateAppWidgetProvider(provider, views, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -523,7 +524,7 @@ public class AppWidgetManager {
*/
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
try {
- sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId);
+ sService.notifyAppWidgetViewDataChanged(appWidgetIds, viewId, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -557,7 +558,8 @@ public class AppWidgetManager {
*/
public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter) {
try {
- List<AppWidgetProviderInfo> providers = sService.getInstalledProviders(categoryFilter);
+ List<AppWidgetProviderInfo> providers = sService.getInstalledProviders(categoryFilter,
+ mContext.getUserId());
for (AppWidgetProviderInfo info : providers) {
// Converting complex to dp.
info.minWidth =
@@ -584,7 +586,8 @@ public class AppWidgetManager {
*/
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
try {
- AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId);
+ AppWidgetProviderInfo info = sService.getAppWidgetInfo(appWidgetId,
+ mContext.getUserId());
if (info != null) {
// Converting complex to dp.
info.minWidth =
@@ -617,7 +620,7 @@ public class AppWidgetManager {
*/
public void bindAppWidgetId(int appWidgetId, ComponentName provider) {
try {
- sService.bindAppWidgetId(appWidgetId, provider, null);
+ sService.bindAppWidgetId(appWidgetId, provider, null, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -641,7 +644,7 @@ public class AppWidgetManager {
*/
public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
try {
- sService.bindAppWidgetId(appWidgetId, provider, options);
+ sService.bindAppWidgetId(appWidgetId, provider, options, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -667,7 +670,7 @@ public class AppWidgetManager {
}
try {
return sService.bindAppWidgetIdIfAllowed(
- mContext.getPackageName(), appWidgetId, provider, null);
+ mContext.getPackageName(), appWidgetId, provider, null, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -696,8 +699,8 @@ public class AppWidgetManager {
return false;
}
try {
- return sService.bindAppWidgetIdIfAllowed(
- mContext.getPackageName(), appWidgetId, provider, options);
+ return sService.bindAppWidgetIdIfAllowed(mContext.getPackageName(), appWidgetId,
+ provider, options, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -715,7 +718,7 @@ public class AppWidgetManager {
*/
public boolean hasBindAppWidgetPermission(String packageName) {
try {
- return sService.hasBindAppWidgetPermission(packageName);
+ return sService.hasBindAppWidgetPermission(packageName, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -733,7 +736,7 @@ public class AppWidgetManager {
*/
public void setBindAppWidgetPermission(String packageName, boolean permission) {
try {
- sService.setBindAppWidgetPermission(packageName, permission);
+ sService.setBindAppWidgetPermission(packageName, permission, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
@@ -794,7 +797,7 @@ public class AppWidgetManager {
*/
public int[] getAppWidgetIds(ComponentName provider) {
try {
- return sService.getAppWidgetIds(provider);
+ return sService.getAppWidgetIds(provider, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index 063e5a8..3ba4f26 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -21,7 +21,6 @@ import android.os.ServiceManager;
import android.os.INetworkManagementService;
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.DhcpInfoInternal;
import android.net.LinkCapabilities;
import android.net.LinkProperties;
import android.net.NetworkInfo;
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index f9025d9..188c786 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -78,7 +78,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
// So we treat this case as an unhandled exception.
throw ex;
}
- if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)");
+ if (DEBUG) Slog.v(TAG, this + " <<< doInBackground (was canceled)", ex);
return null;
}
}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index dfd1820..88a4229 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -118,7 +118,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public void setPrimaryClip(ClipData clip) {
try {
- getService().setPrimaryClip(clip);
+ getService().setPrimaryClip(clip, mContext.getBasePackageName());
} catch (RemoteException e) {
}
}
@@ -128,7 +128,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public ClipData getPrimaryClip() {
try {
- return getService().getPrimaryClip(mContext.getPackageName());
+ return getService().getPrimaryClip(mContext.getBasePackageName());
} catch (RemoteException e) {
return null;
}
@@ -140,7 +140,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public ClipDescription getPrimaryClipDescription() {
try {
- return getService().getPrimaryClipDescription();
+ return getService().getPrimaryClipDescription(mContext.getBasePackageName());
} catch (RemoteException e) {
return null;
}
@@ -151,7 +151,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public boolean hasPrimaryClip() {
try {
- return getService().hasPrimaryClip();
+ return getService().hasPrimaryClip(mContext.getBasePackageName());
} catch (RemoteException e) {
return false;
}
@@ -162,7 +162,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
if (mPrimaryClipChangedListeners.size() == 0) {
try {
getService().addPrimaryClipChangedListener(
- mPrimaryClipChangedServiceListener);
+ mPrimaryClipChangedServiceListener, mContext.getBasePackageName());
} catch (RemoteException e) {
}
}
@@ -209,7 +209,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public boolean hasText() {
try {
- return getService().hasClipboardText();
+ return getService().hasClipboardText(mContext.getBasePackageName());
} catch (RemoteException e) {
return false;
}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 23d8f46..612f1af 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -16,8 +16,10 @@
package android.content;
+import static android.content.pm.PackageManager.GET_PROVIDERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.app.AppOpsManager;
import android.content.pm.PackageManager;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
@@ -100,6 +102,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
private String mWritePermission;
private PathPermission[] mPathPermissions;
private boolean mExported;
+ private boolean mNoPerms;
private Transport mTransport = new Transport();
@@ -154,8 +157,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @param abstractInterface The ContentProvider interface that is to be
* coerced.
- * @return If the IContentProvider is non-null and local, returns its actual
- * ContentProvider instance. Otherwise returns null.
+ * @return If the IContentProvider is non-{@code null} and local, returns its actual
+ * ContentProvider instance. Otherwise returns {@code null}.
* @hide
*/
public static ContentProvider coerceToLocalContentProvider(
@@ -172,6 +175,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* @hide
*/
class Transport extends ContentProviderNative {
+ AppOpsManager mAppOpsManager = null;
+ int mReadOp = AppOpsManager.OP_NONE;
+ int mWriteOp = AppOpsManager.OP_NONE;
+
ContentProvider getContentProvider() {
return ContentProvider.this;
}
@@ -182,10 +189,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public Cursor query(Uri uri, String[] projection,
+ public Cursor query(String callingPkg, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
ICancellationSignal cancellationSignal) {
- enforceReadPermission(uri);
+ if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return rejectQuery(uri, projection, selection, selectionArgs, sortOrder,
+ CancellationSignal.fromTransport(cancellationSignal));
+ }
return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder,
CancellationSignal.fromTransport(cancellationSignal));
}
@@ -196,64 +206,77 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public Uri insert(Uri uri, ContentValues initialValues) {
- enforceWritePermission(uri);
+ public Uri insert(String callingPkg, Uri uri, ContentValues initialValues) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return rejectInsert(uri, initialValues);
+ }
return ContentProvider.this.insert(uri, initialValues);
}
@Override
- public int bulkInsert(Uri uri, ContentValues[] initialValues) {
- enforceWritePermission(uri);
+ public int bulkInsert(String callingPkg, Uri uri, ContentValues[] initialValues) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
return ContentProvider.this.bulkInsert(uri, initialValues);
}
@Override
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
for (ContentProviderOperation operation : operations) {
if (operation.isReadOperation()) {
- enforceReadPermission(operation.getUri());
+ if (enforceReadPermission(callingPkg, operation.getUri())
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
}
if (operation.isWriteOperation()) {
- enforceWritePermission(operation.getUri());
+ if (enforceWritePermission(callingPkg, operation.getUri())
+ != AppOpsManager.MODE_ALLOWED) {
+ throw new OperationApplicationException("App op not allowed", 0);
+ }
}
}
return ContentProvider.this.applyBatch(operations);
}
@Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- enforceWritePermission(uri);
+ public int delete(String callingPkg, Uri uri, String selection, String[] selectionArgs) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
return ContentProvider.this.delete(uri, selection, selectionArgs);
}
@Override
- public int update(Uri uri, ContentValues values, String selection,
+ public int update(String callingPkg, Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
- enforceWritePermission(uri);
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return 0;
+ }
return ContentProvider.this.update(uri, values, selection, selectionArgs);
}
@Override
- public ParcelFileDescriptor openFile(Uri uri, String mode)
+ public ParcelFileDescriptor openFile(String callingPkg, Uri uri, String mode)
throws FileNotFoundException {
- if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
- else enforceReadPermission(uri);
+ enforceFilePermission(callingPkg, uri, mode);
return ContentProvider.this.openFile(uri, mode);
}
@Override
- public AssetFileDescriptor openAssetFile(Uri uri, String mode)
+ public AssetFileDescriptor openAssetFile(String callingPkg, Uri uri, String mode)
throws FileNotFoundException {
- if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
- else enforceReadPermission(uri);
+ enforceFilePermission(callingPkg, uri, mode);
return ContentProvider.this.openAssetFile(uri, mode);
}
@Override
- public Bundle call(String method, String arg, Bundle extras) {
- return ContentProvider.this.call(method, arg, extras);
+ public Bundle call(String callingPkg, String method, String arg, Bundle extras) {
+ return ContentProvider.this.callFromPackage(callingPkg, method, arg, extras);
}
@Override
@@ -262,9 +285,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeType, Bundle opts)
- throws FileNotFoundException {
- enforceReadPermission(uri);
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri uri, String mimeType,
+ Bundle opts) throws FileNotFoundException {
+ enforceFilePermission(callingPkg, uri, "r");
return ContentProvider.this.openTypedAssetFile(uri, mimeType, opts);
}
@@ -273,7 +296,28 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
return CancellationSignal.createTransport();
}
- private void enforceReadPermission(Uri uri) throws SecurityException {
+ private void enforceFilePermission(String callingPkg, Uri uri, String mode)
+ throws FileNotFoundException, SecurityException {
+ if (mode != null && mode.startsWith("rw")) {
+ if (enforceWritePermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ } else {
+ if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ throw new FileNotFoundException("App op not allowed");
+ }
+ }
+ }
+
+ private int enforceReadPermission(String callingPkg, Uri uri) throws SecurityException {
+ enforceReadPermissionInner(uri);
+ if (mReadOp != AppOpsManager.OP_NONE) {
+ return mAppOpsManager.noteOp(mReadOp, Binder.getCallingUid(), callingPkg);
+ }
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void enforceReadPermissionInner(Uri uri) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -334,7 +378,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
+ ", uid=" + uid + failReason);
}
- private void enforceWritePermission(Uri uri) throws SecurityException {
+ private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException {
+ enforceWritePermissionInner(uri);
+ if (mWriteOp != AppOpsManager.OP_NONE) {
+ return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg);
+ }
+ return AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void enforceWritePermissionInner(Uri uri) throws SecurityException {
final Context context = getContext();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -398,7 +450,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
/**
* Retrieves the Context this provider is running in. Only available once
- * {@link #onCreate} has been called -- this will return null in the
+ * {@link #onCreate} has been called -- this will return {@code null} in the
* constructor.
*/
public final Context getContext() {
@@ -471,6 +523,21 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
return mPathPermissions;
}
+ /** @hide */
+ public final void setAppOps(int readOp, int writeOp) {
+ if (!mNoPerms) {
+ mTransport.mAppOpsManager = (AppOpsManager)mContext.getSystemService(
+ Context.APP_OPS_SERVICE);
+ mTransport.mReadOp = readOp;
+ mTransport.mWriteOp = writeOp;
+ }
+ }
+
+ /** @hide */
+ public AppOpsManager getAppOpsManager() {
+ return mTransport.mAppOpsManager;
+ }
+
/**
* Implement this to initialize your content provider on startup.
* This method is called for all registered content providers on the
@@ -526,6 +593,31 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * @hide
+ * Implementation when a caller has performed a query on the content
+ * provider, but that call has been rejected for the operation given
+ * to {@link #setAppOps(int, int)}. The default implementation
+ * rewrites the <var>selection</var> argument to include a condition
+ * that is never true (so will always result in an empty cursor)
+ * and calls through to {@link #query(android.net.Uri, String[], String, String[],
+ * String, android.os.CancellationSignal)} with that.
+ */
+ public Cursor rejectQuery(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder,
+ CancellationSignal cancellationSignal) {
+ // The read is not allowed... to fake it out, we replace the given
+ // selection statement with a dummy one that will always be false.
+ // This way we will get a cursor back that has the correct structure
+ // but contains no rows.
+ if (selection == null) {
+ selection = "'A' = 'B'";
+ } else {
+ selection = "'A' = 'B' AND (" + selection + ")";
+ }
+ return query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
+ }
+
+ /**
* Implement this to handle query requests from clients.
* This method can be called from multiple threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
@@ -570,15 +662,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* that the implementation should parse and add to a WHERE or HAVING clause, specifying
* that _id value.
* @param projection The list of columns to put into the cursor. If
- * null all columns are included.
+ * {@code null} all columns are included.
* @param selection A selection criteria to apply when filtering rows.
- * If null then all rows are included.
+ * If {@code null} then all rows are included.
* @param selectionArgs You may include ?s in selection, which will be replaced by
* the values from selectionArgs, in order that they appear in the selection.
* The values will be bound as Strings.
* @param sortOrder How the rows in the cursor should be sorted.
- * If null then the provider is free to define the sort order.
- * @return a Cursor or null.
+ * If {@code null} then the provider is free to define the sort order.
+ * @return a Cursor or {@code null}.
*/
public abstract Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder);
@@ -633,18 +725,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* that the implementation should parse and add to a WHERE or HAVING clause, specifying
* that _id value.
* @param projection The list of columns to put into the cursor. If
- * null all columns are included.
+ * {@code null} all columns are included.
* @param selection A selection criteria to apply when filtering rows.
- * If null then all rows are included.
+ * If {@code null} then all rows are included.
* @param selectionArgs You may include ?s in selection, which will be replaced by
* the values from selectionArgs, in order that they appear in the selection.
* The values will be bound as Strings.
* @param sortOrder How the rows in the cursor should be sorted.
- * If null then the provider is free to define the sort order.
- * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+ * If {@code null} then the provider is free to define the sort order.
+ * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
- * @return a Cursor or null.
+ * @return a Cursor or {@code null}.
*/
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder,
@@ -668,19 +760,37 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* to retrieve the MIME type for a URI when dispatching intents.
*
* @param uri the URI to query.
- * @return a MIME type string, or null if there is no type.
+ * @return a MIME type string, or {@code null} if there is no type.
*/
public abstract String getType(Uri uri);
/**
+ * @hide
+ * Implementation when a caller has performed an insert on the content
+ * provider, but that call has been rejected for the operation given
+ * to {@link #setAppOps(int, int)}. The default implementation simply
+ * returns a dummy URI that is the base URI with a 0 path element
+ * appended.
+ */
+ public Uri rejectInsert(Uri uri, ContentValues values) {
+ // If not allowed, we need to return some reasonable URI. Maybe the
+ // content provider should be responsible for this, but for now we
+ // will just return the base URI with a dummy '0' tagged on to it.
+ // You shouldn't be able to read if you can't write, anyway, so it
+ // shouldn't matter much what is returned.
+ return uri.buildUpon().appendPath("0").build();
+ }
+
+ /**
* Implement this to handle requests to insert a new row.
* As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
* after inserting.
* This method can be called from multiple threads, as described in
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
* and Threads</a>.
- * @param uri The content:// URI of the insertion request.
+ * @param uri The content:// URI of the insertion request. This must not be {@code null}.
* @param values A set of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
* @return The URI for the newly inserted item.
*/
public abstract Uri insert(Uri uri, ContentValues values);
@@ -697,6 +807,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @param uri The content:// URI of the insertion request.
* @param values An array of sets of column_name/value pairs to add to the database.
+ * This must not be {@code null}.
* @return The number of values that were inserted.
*/
public int bulkInsert(Uri uri, ContentValues[] values) {
@@ -741,8 +852,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @param uri The URI to query. This can potentially have a record ID if this
* is an update request for a specific record.
- * @param values A Bundle mapping from column names to new column values (NULL is a
- * valid value).
+ * @param values A set of column_name/value pairs to update in the database.
+ * This must not be {@code null}.
* @param selection An optional filter to match rows to update.
* @return the number of rows affected.
*/
@@ -764,6 +875,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* their responsibility to close it when done. That is, the implementation
* of this method should create a new ParcelFileDescriptor for each call.
*
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
* @param uri The URI whose file is to be opened.
* @param mode Access mode for the file. May be "r" for read-only access,
* "rw" for read and write access, or "rwt" for read and write access
@@ -779,6 +902,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @see #openAssetFile(Uri, String)
* @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
*/
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
@@ -806,6 +930,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
* applications that can not handle sub-sections of files.</p>
*
+ * <p class="note">For use in Intents, you will want to implement {@link #getType}
+ * to return the appropriate MIME type for the data returned here with
+ * the same URI. This will allow intent resolution to automatically determine the data MIME
+ * type and select the appropriate matching targets as part of its operation.</p>
+ *
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.</p>
+ *
* @param uri The URI whose file is to be opened.
* @param mode Access mode for the file. May be "r" for read-only access,
* "w" for write-only access (erasing whatever data is currently in
@@ -823,6 +956,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
*
* @see #openFile(Uri, String)
* @see #openFileHelper(Uri, String)
+ * @see #getType(android.net.Uri)
*/
public AssetFileDescriptor openAssetFile(Uri uri, String mode)
throws FileNotFoundException {
@@ -875,7 +1009,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
/**
* Called by a client to determine the types of data streams that this
* content provider supports for the given URI. The default implementation
- * returns null, meaning no types. If your content provider stores data
+ * returns {@code null}, meaning no types. If your content provider stores data
* of a particular type, return that MIME type if it matches the given
* mimeTypeFilter. If it can perform type conversions, return an array
* of all supported MIME types that match mimeTypeFilter.
@@ -883,7 +1017,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* @param uri The data in the content provider being queried.
* @param mimeTypeFilter The type of data the client desires. May be
* a pattern, such as *\/* to retrieve all possible data types.
- * @return Returns null if there are no possible data streams for the
+ * @return Returns {@code null} if there are no possible data streams for the
* given mimeTypeFilter. Otherwise returns an array of all available
* concrete MIME types.
*
@@ -902,12 +1036,19 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* perform data conversions to generate data of the desired type.
*
* <p>The default implementation compares the given mimeType against the
- * result of {@link #getType(Uri)} and, if the match, simple calls
+ * result of {@link #getType(Uri)} and, if they match, simply calls
* {@link #openAssetFile(Uri, String)}.
*
* <p>See {@link ClipData} for examples of the use and implementation
* of this method.
*
+ * <p class="note">For better interoperability with other applications, it is recommended
+ * that for any URIs that can be opened, you also support queries on them
+ * containing at least the columns specified by {@link android.provider.OpenableColumns}.
+ * You may also want to support other common columns if you have additional meta-data
+ * to supply, such as {@link android.provider.MediaStore.MediaColumns#DATE_ADDED}
+ * in {@link android.provider.MediaStore.MediaColumns}.</p>
+ *
* @param uri The data in the content provider being queried.
* @param mimeTypeFilter The type of data the client desires. May be
* a pattern, such as *\/*, if the caller does not have specific type
@@ -1029,6 +1170,15 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * Like {@link #attachInfo(Context, android.content.pm.ProviderInfo)}, but for use
+ * when directly instantiating the provider for testing.
+ * @hide
+ */
+ public void attachInfoForTesting(Context context, ProviderInfo info) {
+ attachInfo(context, info, true);
+ }
+
+ /**
* After being instantiated, this is called to tell the content provider
* about itself.
*
@@ -1036,12 +1186,18 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* @param info Registered information about this content provider
*/
public void attachInfo(Context context, ProviderInfo info) {
+ attachInfo(context, info, false);
+ }
+
+ private void attachInfo(Context context, ProviderInfo info, boolean testing) {
/*
* We may be using AsyncTask from binder threads. Make it init here
* so its static handler is on the main thread.
*/
AsyncTask.init();
+ mNoPerms = testing;
+
/*
* Only allow it to be set once, so after the content service gives
* this to us clients can't change it.
@@ -1087,14 +1243,23 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
/**
+ * @hide
+ * Front-end to {@link #call(String, String, android.os.Bundle)} that provides the name
+ * of the calling package.
+ */
+ public Bundle callFromPackage(String callingPackag, String method, String arg, Bundle extras) {
+ return call(method, arg, extras);
+ }
+
+ /**
* Call a provider-defined method. This can be used to implement
* interfaces that are cheaper and/or unnatural for a table-like
* model.
*
- * @param method method name to call. Opaque to framework, but should not be null.
- * @param arg provider-defined String argument. May be null.
- * @param extras provider-defined Bundle argument. May be null.
- * @return provider-defined return value. May be null. Null is also
+ * @param method method name to call. Opaque to framework, but should not be {@code null}.
+ * @param arg provider-defined String argument. May be {@code null}.
+ * @param extras provider-defined Bundle argument. May be {@code null}.
+ * @return provider-defined return value. May be {@code null}, which is also
* the default for providers which don't implement any call methods.
*/
public Bundle call(String method, String arg, Bundle extras) {
@@ -1132,12 +1297,10 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
* Print the Provider's state into the given stream. This gets invoked if
* you run "adb shell dumpsys activity provider &lt;provider_component_name&gt;".
*
- * @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
* @param writer The PrintWriter to which you should dump your state. This will be
* closed for you after you return.
* @param args additional arguments to the dump request.
- * @hide
*/
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
writer.println("nothing to dump");
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 204f963..8dffac7 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -45,6 +45,7 @@ import java.util.ArrayList;
public class ContentProviderClient {
private final IContentProvider mContentProvider;
private final ContentResolver mContentResolver;
+ private final String mPackageName;
private final boolean mStable;
private boolean mReleased;
@@ -55,6 +56,7 @@ public class ContentProviderClient {
IContentProvider contentProvider, boolean stable) {
mContentProvider = contentProvider;
mContentResolver = contentResolver;
+ mPackageName = contentResolver.mPackageName;
mStable = stable;
}
@@ -81,8 +83,8 @@ public class ContentProviderClient {
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
- return mContentProvider.query(url, projection, selection, selectionArgs, sortOrder,
- remoteCancellationSignal);
+ return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs,
+ sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -119,7 +121,7 @@ public class ContentProviderClient {
public Uri insert(Uri url, ContentValues initialValues)
throws RemoteException {
try {
- return mContentProvider.insert(url, initialValues);
+ return mContentProvider.insert(mPackageName, url, initialValues);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -131,7 +133,7 @@ public class ContentProviderClient {
/** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
try {
- return mContentProvider.bulkInsert(url, initialValues);
+ return mContentProvider.bulkInsert(mPackageName, url, initialValues);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -144,7 +146,7 @@ public class ContentProviderClient {
public int delete(Uri url, String selection, String[] selectionArgs)
throws RemoteException {
try {
- return mContentProvider.delete(url, selection, selectionArgs);
+ return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -157,7 +159,7 @@ public class ContentProviderClient {
public int update(Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
try {
- return mContentProvider.update(url, values, selection, selectionArgs);
+ return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -176,7 +178,7 @@ public class ContentProviderClient {
public ParcelFileDescriptor openFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
try {
- return mContentProvider.openFile(url, mode);
+ return mContentProvider.openFile(mPackageName, url, mode);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -195,7 +197,7 @@ public class ContentProviderClient {
public AssetFileDescriptor openAssetFile(Uri url, String mode)
throws RemoteException, FileNotFoundException {
try {
- return mContentProvider.openAssetFile(url, mode);
+ return mContentProvider.openAssetFile(mPackageName, url, mode);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -209,7 +211,7 @@ public class ContentProviderClient {
String mimeType, Bundle opts)
throws RemoteException, FileNotFoundException {
try {
- return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+ return mContentProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -222,7 +224,7 @@ public class ContentProviderClient {
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws RemoteException, OperationApplicationException {
try {
- return mContentProvider.applyBatch(operations);
+ return mContentProvider.applyBatch(mPackageName, operations);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
@@ -235,7 +237,7 @@ public class ContentProviderClient {
public Bundle call(String method, String arg, Bundle extras)
throws RemoteException {
try {
- return mContentProvider.call(method, arg, extras);
+ return mContentProvider.call(mPackageName, method, arg, extras);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 550a1c9..6f822c1 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -81,6 +81,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
// String[] projection
@@ -110,16 +111,24 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
data.readStrongBinder());
- Cursor cursor = query(url, projection, selection, selectionArgs, sortOrder,
- cancellationSignal);
+ Cursor cursor = query(callingPkg, url, projection, selection, selectionArgs,
+ sortOrder, cancellationSignal);
if (cursor != null) {
- CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
- cursor, observer, getProviderName());
- BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
-
- reply.writeNoException();
- reply.writeInt(1);
- d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ try {
+ CursorToBulkCursorAdaptor adaptor = new CursorToBulkCursorAdaptor(
+ cursor, observer, getProviderName());
+ BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
+ cursor = null;
+
+ reply.writeNoException();
+ reply.writeInt(1);
+ d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ } finally {
+ // Close cursor if an exception was thrown while constructing the adaptor.
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
} else {
reply.writeNoException();
reply.writeInt(0);
@@ -142,10 +151,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case INSERT_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
- Uri out = insert(url, values);
+ Uri out = insert(callingPkg, url, values);
reply.writeNoException();
Uri.writeToParcel(reply, out);
return true;
@@ -154,10 +164,11 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case BULK_INSERT_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues[] values = data.createTypedArray(ContentValues.CREATOR);
- int count = bulkInsert(url, values);
+ int count = bulkInsert(callingPkg, url, values);
reply.writeNoException();
reply.writeInt(count);
return true;
@@ -166,13 +177,14 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case APPLY_BATCH_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
final int numOperations = data.readInt();
final ArrayList<ContentProviderOperation> operations =
new ArrayList<ContentProviderOperation>(numOperations);
for (int i = 0; i < numOperations; i++) {
operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data));
}
- final ContentProviderResult[] results = applyBatch(operations);
+ final ContentProviderResult[] results = applyBatch(callingPkg, operations);
reply.writeNoException();
reply.writeTypedArray(results, 0);
return true;
@@ -181,11 +193,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case DELETE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String selection = data.readString();
String[] selectionArgs = data.readStringArray();
- int count = delete(url, selection, selectionArgs);
+ int count = delete(callingPkg, url, selection, selectionArgs);
reply.writeNoException();
reply.writeInt(count);
@@ -195,12 +208,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case UPDATE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
ContentValues values = ContentValues.CREATOR.createFromParcel(data);
String selection = data.readString();
String[] selectionArgs = data.readStringArray();
- int count = update(url, values, selection, selectionArgs);
+ int count = update(callingPkg, url, values, selection, selectionArgs);
reply.writeNoException();
reply.writeInt(count);
@@ -210,11 +224,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case OPEN_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
ParcelFileDescriptor fd;
- fd = openFile(url, mode);
+ fd = openFile(callingPkg, url, mode);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -229,11 +244,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case OPEN_ASSET_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mode = data.readString();
AssetFileDescriptor fd;
- fd = openAssetFile(url, mode);
+ fd = openAssetFile(callingPkg, url, mode);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -249,11 +265,12 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
String method = data.readString();
String stringArg = data.readString();
Bundle args = data.readBundle();
- Bundle responseBundle = call(method, stringArg, args);
+ Bundle responseBundle = call(callingPkg, method, stringArg, args);
reply.writeNoException();
reply.writeBundle(responseBundle);
@@ -275,12 +292,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
case OPEN_TYPED_ASSET_FILE_TRANSACTION:
{
data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
Uri url = Uri.CREATOR.createFromParcel(data);
String mimeType = data.readString();
Bundle opts = data.readBundle();
AssetFileDescriptor fd;
- fd = openTypedAssetFile(url, mimeType, opts);
+ fd = openTypedAssetFile(callingPkg, url, mimeType, opts);
reply.writeNoException();
if (fd != null) {
reply.writeInt(1);
@@ -329,7 +347,7 @@ final class ContentProviderProxy implements IContentProvider
return mRemote;
}
- public Cursor query(Uri url, String[] projection, String selection,
+ public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
throws RemoteException {
BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
@@ -338,6 +356,7 @@ final class ContentProviderProxy implements IContentProvider
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
int length = 0;
if (projection != null) {
@@ -405,13 +424,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public Uri insert(Uri url, ContentValues values) throws RemoteException
+ public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
@@ -426,12 +446,13 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public int bulkInsert(Uri url, ContentValues[] values) throws RemoteException {
+ public int bulkInsert(String callingPkg, Uri url, ContentValues[] values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeTypedArray(values, 0);
@@ -446,12 +467,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
- throws RemoteException, OperationApplicationException {
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
data.writeInt(operations.size());
for (ContentProviderOperation operation : operations) {
operation.writeToParcel(data, 0);
@@ -468,13 +491,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public int delete(Uri url, String selection, String[] selectionArgs)
+ public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(selection);
data.writeStringArray(selectionArgs);
@@ -490,13 +514,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public int update(Uri url, ContentValues values, String selection,
+ public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
values.writeToParcel(data, 0);
data.writeString(selection);
@@ -513,13 +538,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public ParcelFileDescriptor openFile(Uri url, String mode)
+ public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mode);
@@ -535,13 +561,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mode);
@@ -558,13 +585,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public Bundle call(String method, String request, Bundle args)
+ public Bundle call(String callingPkg, String method, String request, Bundle args)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
data.writeString(method);
data.writeString(request);
data.writeBundle(args);
@@ -601,13 +629,14 @@ final class ContentProviderProxy implements IContentProvider
}
}
- public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
- throws RemoteException, FileNotFoundException {
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
+ Bundle opts) throws RemoteException, FileNotFoundException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(IContentProvider.descriptor);
+ data.writeString(callingPkg);
url.writeToParcel(data, 0);
data.writeString(mimeType);
data.writeBundle(opts);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index bde4d2b..fefd343 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -20,6 +20,7 @@ import dalvik.system.CloseGuard;
import android.accounts.Account;
import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
import android.app.AppGlobals;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
@@ -116,6 +117,10 @@ public abstract class ContentResolver {
*/
public static final String SYNC_EXTRAS_INITIALIZE = "initialize";
+ /** @hide */
+ public static final Intent ACTION_SYNC_CONN_STATUS_CHANGED =
+ new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
+
public static final String SCHEME_CONTENT = "content";
public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
public static final String SCHEME_FILE = "file";
@@ -169,6 +174,42 @@ public abstract class ContentResolver {
/** @hide */
public static final int SYNC_ERROR_INTERNAL = 8;
+ private static final String[] SYNC_ERROR_NAMES = new String[] {
+ "already-in-progress",
+ "authentication-error",
+ "io-error",
+ "parse-error",
+ "conflict",
+ "too-many-deletions",
+ "too-many-retries",
+ "internal-error",
+ };
+
+ /** @hide */
+ public static String syncErrorToString(int error) {
+ if (error < 1 || error > SYNC_ERROR_NAMES.length) {
+ return String.valueOf(error);
+ }
+ return SYNC_ERROR_NAMES[error - 1];
+ }
+
+ /** @hide */
+ public static int syncErrorStringToInt(String error) {
+ for (int i = 0, n = SYNC_ERROR_NAMES.length; i < n; i++) {
+ if (SYNC_ERROR_NAMES[i].equals(error)) {
+ return i + 1;
+ }
+ }
+ if (error != null) {
+ try {
+ return Integer.parseInt(error);
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "error parsing sync error: " + error);
+ }
+ }
+ return 0;
+ }
+
public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1<<0;
public static final int SYNC_OBSERVER_TYPE_PENDING = 1<<1;
public static final int SYNC_OBSERVER_TYPE_ACTIVE = 1<<2;
@@ -183,7 +224,8 @@ public abstract class ContentResolver {
private final Random mRandom = new Random(); // guarded by itself
public ContentResolver(Context context) {
- mContext = context;
+ mContext = context != null ? context : ActivityThread.currentApplication();
+ mPackageName = mContext.getBasePackageName();
}
/** @hide */
@@ -358,6 +400,7 @@ public abstract class ContentResolver {
return null;
}
IContentProvider stableProvider = null;
+ Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();
@@ -367,9 +410,8 @@ public abstract class ContentResolver {
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
- Cursor qCursor;
try {
- qCursor = unstableProvider.query(uri, projection,
+ qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
@@ -380,26 +422,32 @@ public abstract class ContentResolver {
if (stableProvider == null) {
return null;
}
- qCursor = stableProvider.query(uri, projection,
+ qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
- // force query execution
+
+ // Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
- // Wrap the cursor object into CursorWrapperInner object
+
+ // Wrap the cursor object into CursorWrapperInner object.
CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
stableProvider != null ? stableProvider : acquireProvider(uri));
stableProvider = null;
+ qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
+ if (qCursor != null) {
+ qCursor.close();
+ }
if (unstableProvider != null) {
releaseUnstableProvider(unstableProvider);
}
@@ -622,7 +670,7 @@ public abstract class ContentResolver {
try {
try {
- fd = unstableProvider.openAssetFile(uri, mode);
+ fd = unstableProvider.openAssetFile(mPackageName, uri, mode);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -636,7 +684,7 @@ public abstract class ContentResolver {
if (stableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
}
- fd = stableProvider.openAssetFile(uri, mode);
+ fd = stableProvider.openAssetFile(mPackageName, uri, mode);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -714,7 +762,7 @@ public abstract class ContentResolver {
try {
try {
- fd = unstableProvider.openTypedAssetFile(uri, mimeType, opts);
+ fd = unstableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -728,7 +776,7 @@ public abstract class ContentResolver {
if (stableProvider == null) {
throw new FileNotFoundException("No content provider: " + uri);
}
- fd = stableProvider.openTypedAssetFile(uri, mimeType, opts);
+ fd = stableProvider.openTypedAssetFile(mPackageName, uri, mimeType, opts);
if (fd == null) {
// The provider will be released by the finally{} clause
return null;
@@ -863,7 +911,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- Uri createdRow = provider.insert(url, values);
+ Uri createdRow = provider.insert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "insert", null /* where */);
return createdRow;
@@ -924,7 +972,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsCreated = provider.bulkInsert(url, values);
+ int rowsCreated = provider.bulkInsert(mPackageName, url, values);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "bulkinsert", null /* where */);
return rowsCreated;
@@ -955,7 +1003,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsDeleted = provider.delete(url, where, selectionArgs);
+ int rowsDeleted = provider.delete(mPackageName, url, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, url, "delete", where);
return rowsDeleted;
@@ -989,7 +1037,7 @@ public abstract class ContentResolver {
}
try {
long startTime = SystemClock.uptimeMillis();
- int rowsUpdated = provider.update(uri, values, where, selectionArgs);
+ int rowsUpdated = provider.update(mPackageName, uri, values, where, selectionArgs);
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogUpdateToEventLog(durationMillis, uri, "update", where);
return rowsUpdated;
@@ -1028,7 +1076,7 @@ public abstract class ContentResolver {
throw new IllegalArgumentException("Unknown URI " + uri);
}
try {
- return provider.call(method, arg, extras);
+ return provider.call(mPackageName, method, arg, extras);
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
@@ -1926,7 +1974,13 @@ public abstract class ContentResolver {
return sContentService;
}
+ /** @hide */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
private static IContentService sContentService;
private final Context mContext;
+ final String mPackageName;
private static final String TAG = "ContentResolver";
}
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
deleted file mode 100644
index 4512e82..0000000
--- a/core/java/android/content/ContentService.java
+++ /dev/null
@@ -1,838 +0,0 @@
-/*
- * 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.accounts.Account;
-import android.app.ActivityManager;
-import android.database.IContentObserver;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.util.Log;
-import android.util.SparseIntArray;
-import android.Manifest;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.security.InvalidParameterException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * {@hide}
- */
-public final class ContentService extends IContentService.Stub {
- private static final String TAG = "ContentService";
- private Context mContext;
- private boolean mFactoryTest;
- private final ObserverNode mRootNode = new ObserverNode("");
- private SyncManager mSyncManager = null;
- private final Object mSyncManagerLock = new Object();
-
- private SyncManager getSyncManager() {
- synchronized(mSyncManagerLock) {
- try {
- // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
- if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
- } catch (SQLiteException e) {
- Log.e(TAG, "Can't create SyncManager", e);
- }
- return mSyncManager;
- }
- }
-
- @Override
- protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
- "caller doesn't have the DUMP permission");
-
- // This makes it so that future permission checks will be in the context of this
- // process rather than the caller's process. We will restore this before returning.
- long identityToken = clearCallingIdentity();
- try {
- if (mSyncManager == null) {
- pw.println("No SyncManager created! (Disk full?)");
- } else {
- mSyncManager.dump(fd, pw);
- }
- pw.println();
- pw.println("Observer tree:");
- synchronized (mRootNode) {
- int[] counts = new int[2];
- final SparseIntArray pidCounts = new SparseIntArray();
- mRootNode.dumpLocked(fd, pw, args, "", " ", counts, pidCounts);
- pw.println();
- ArrayList<Integer> sorted = new ArrayList<Integer>();
- for (int i=0; i<pidCounts.size(); i++) {
- sorted.add(pidCounts.keyAt(i));
- }
- Collections.sort(sorted, new Comparator<Integer>() {
- @Override
- public int compare(Integer lhs, Integer rhs) {
- int lc = pidCounts.get(lhs);
- int rc = pidCounts.get(rhs);
- if (lc < rc) {
- return 1;
- } else if (lc > rc) {
- return -1;
- }
- return 0;
- }
-
- });
- for (int i=0; i<sorted.size(); i++) {
- int pid = sorted.get(i);
- pw.print(" pid "); pw.print(pid); pw.print(": ");
- pw.print(pidCounts.get(pid)); pw.println(" observers");
- }
- pw.println();
- pw.print(" Total number of nodes: "); pw.println(counts[0]);
- pw.print(" Total number of observers: "); pw.println(counts[1]);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- try {
- return super.onTransact(code, data, reply, flags);
- } catch (RuntimeException e) {
- // The content service only throws security exceptions, so let's
- // log all others.
- if (!(e instanceof SecurityException)) {
- Log.e(TAG, "Content Service Crash", e);
- }
- throw e;
- }
- }
-
- /*package*/ ContentService(Context context, boolean factoryTest) {
- mContext = context;
- mFactoryTest = factoryTest;
- }
-
- public void systemReady() {
- getSyncManager();
- }
-
- /**
- * Register a content observer tied to a specific user's view of the provider.
- * @param userHandle the user whose view of the provider is to be observed. May be
- * the calling user without requiring any permission, otherwise the caller needs to
- * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and
- * USER_CURRENT are properly handled; all other pseudousers are forbidden.
- */
- @Override
- public void registerContentObserver(Uri uri, boolean notifyForDescendants,
- IContentObserver observer, int userHandle) {
- if (observer == null || uri == null) {
- throw new IllegalArgumentException("You must pass a valid uri and observer");
- }
-
- final int callingUser = UserHandle.getCallingUserId();
- if (callingUser != userHandle) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "no permission to observe other users' provider view");
- }
-
- if (userHandle < 0) {
- if (userHandle == UserHandle.USER_CURRENT) {
- userHandle = ActivityManager.getCurrentUser();
- } else if (userHandle != UserHandle.USER_ALL) {
- throw new InvalidParameterException("Bad user handle for registerContentObserver: "
- + userHandle);
- }
- }
-
- synchronized (mRootNode) {
- mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
- Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
- if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
- " with notifyForDescendants " + notifyForDescendants);
- }
- }
-
- public void registerContentObserver(Uri uri, boolean notifyForDescendants,
- IContentObserver observer) {
- registerContentObserver(uri, notifyForDescendants, observer,
- UserHandle.getCallingUserId());
- }
-
- public void unregisterContentObserver(IContentObserver observer) {
- if (observer == null) {
- throw new IllegalArgumentException("You must pass a valid observer");
- }
- synchronized (mRootNode) {
- mRootNode.removeObserverLocked(observer);
- if (false) Log.v(TAG, "Unregistered observer " + observer);
- }
- }
-
- /**
- * Notify observers of a particular user's view of the provider.
- * @param userHandle the user whose view of the provider is to be notified. May be
- * the calling user without requiring any permission, otherwise the caller needs to
- * hold the INTERACT_ACROSS_USERS_FULL permission. Pseudousers USER_ALL and
- * USER_CURRENT are properly interpreted; no other pseudousers are allowed.
- */
- @Override
- public void notifyChange(Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork,
- int userHandle) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle
- + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
- }
-
- // Notify for any user other than the caller's own requires permission.
- final int callingUserHandle = UserHandle.getCallingUserId();
- if (userHandle != callingUserHandle) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
- "no permission to notify other users");
- }
-
- // We passed the permission check; resolve pseudouser targets as appropriate
- if (userHandle < 0) {
- if (userHandle == UserHandle.USER_CURRENT) {
- userHandle = ActivityManager.getCurrentUser();
- } else if (userHandle != UserHandle.USER_ALL) {
- throw new InvalidParameterException("Bad user handle for notifyChange: "
- + userHandle);
- }
- }
-
- // This makes it so that future permission checks will be in the context of this
- // process rather than the caller's process. We will restore this before returning.
- long identityToken = clearCallingIdentity();
- try {
- ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
- synchronized (mRootNode) {
- mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
- userHandle, calls);
- }
- final int numCalls = calls.size();
- for (int i=0; i<numCalls; i++) {
- ObserverCall oc = calls.get(i);
- try {
- oc.mObserver.onChange(oc.mSelfChange, uri);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
- }
- } catch (RemoteException ex) {
- synchronized (mRootNode) {
- Log.w(TAG, "Found dead observer, removing");
- IBinder binder = oc.mObserver.asBinder();
- final ArrayList<ObserverNode.ObserverEntry> list
- = oc.mNode.mObservers;
- int numList = list.size();
- for (int j=0; j<numList; j++) {
- ObserverNode.ObserverEntry oe = list.get(j);
- if (oe.observer.asBinder() == binder) {
- list.remove(j);
- j--;
- numList--;
- }
- }
- }
- }
- }
- if (syncToNetwork) {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle,
- uri.getAuthority());
- }
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void notifyChange(Uri uri, IContentObserver observer,
- boolean observerWantsSelfNotifications, boolean syncToNetwork) {
- notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
- UserHandle.getCallingUserId());
- }
-
- /**
- * Hide this class since it is not part of api,
- * but current unittest framework requires it to be public
- * @hide
- *
- */
- public static final class ObserverCall {
- final ObserverNode mNode;
- final IContentObserver mObserver;
- final boolean mSelfChange;
-
- ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) {
- mNode = node;
- mObserver = observer;
- mSelfChange = selfChange;
- }
- }
-
- public void requestSync(Account account, String authority, Bundle extras) {
- ContentResolver.validateSyncExtrasBundle(extras);
- int userId = UserHandle.getCallingUserId();
-
- // This makes it so that future permission checks will be in the context of this
- // process rather than the caller's process. We will restore this before returning.
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.scheduleSync(account, userId, authority, extras, 0 /* no delay */,
- false /* onlyThoseWithUnkownSyncableState */);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /**
- * Clear all scheduled sync operations that match the uri and cancel the active sync
- * if they match the authority and account, if they are present.
- * @param account filter the pending and active syncs to cancel using this account
- * @param authority filter the pending and active syncs to cancel using this authority
- */
- public void cancelSync(Account account, String authority) {
- int userId = UserHandle.getCallingUserId();
-
- // This makes it so that future permission checks will be in the context of this
- // process rather than the caller's process. We will restore this before returning.
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.clearScheduledSyncOperations(account, userId, authority);
- syncManager.cancelActiveSync(account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- /**
- * Get information about the SyncAdapters that are known to the system.
- * @return an array of SyncAdapters that have registered with the system
- */
- public SyncAdapterType[] getSyncAdapterTypes() {
- // This makes it so that future permission checks will be in the context of this
- // process rather than the caller's process. We will restore this before returning.
- final int userId = UserHandle.getCallingUserId();
- final long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- return syncManager.getSyncAdapterTypes(userId);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean getSyncAutomatically(Account account, String providerName) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getSyncAutomatically(
- account, userId, providerName);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- public void setSyncAutomatically(Account account, String providerName, boolean sync) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.getSyncStorageEngine().setSyncAutomatically(
- account, userId, providerName, sync);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void addPeriodicSync(Account account, String authority, Bundle extras,
- long pollFrequency) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- getSyncManager().getSyncStorageEngine().addPeriodicSync(
- account, userId, authority, extras, pollFrequency);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void removePeriodicSync(Account account, String authority, Bundle extras) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority,
- extras);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
- account, userId, providerName);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public int getIsSyncable(Account account, String providerName) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getIsSyncable(
- account, userId, providerName);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return -1;
- }
-
- public void setIsSyncable(Account account, String providerName, int syncable) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.getSyncStorageEngine().setIsSyncable(
- account, userId, providerName, syncable);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean getMasterSyncAutomatically() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
- "no permission to read the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- public void setMasterSyncAutomatically(boolean flag) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
- "no permission to write the sync settings");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public boolean isSyncActive(Account account, String authority) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().isSyncActive(
- account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- public List<SyncInfo> getCurrentSyncs() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public SyncStatusInfo getSyncStatus(Account account, String authority) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
- account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return null;
- }
-
- public boolean isSyncPending(Account account, String authority) {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
- "no permission to read the sync stats");
- int userId = UserHandle.getCallingUserId();
-
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null) {
- return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- return false;
- }
-
- public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null && callback != null) {
- syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public void removeStatusChangeListener(ISyncStatusObserver callback) {
- long identityToken = clearCallingIdentity();
- try {
- SyncManager syncManager = getSyncManager();
- if (syncManager != null && callback != null) {
- syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
- }
- } finally {
- restoreCallingIdentity(identityToken);
- }
- }
-
- public static ContentService main(Context context, boolean factoryTest) {
- ContentService service = new ContentService(context, factoryTest);
- ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
- return service;
- }
-
- /**
- * Hide this class since it is not part of api,
- * but current unittest framework requires it to be public
- * @hide
- */
- public static final class ObserverNode {
- private class ObserverEntry implements IBinder.DeathRecipient {
- public final IContentObserver observer;
- public final int uid;
- public final int pid;
- public final boolean notifyForDescendants;
- private final int userHandle;
- private final Object observersLock;
-
- public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
- int _uid, int _pid, int _userHandle) {
- this.observersLock = observersLock;
- observer = o;
- uid = _uid;
- pid = _pid;
- userHandle = _userHandle;
- notifyForDescendants = n;
- try {
- observer.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- binderDied();
- }
- }
-
- public void binderDied() {
- synchronized (observersLock) {
- removeObserverLocked(observer);
- }
- }
-
- public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- String name, String prefix, SparseIntArray pidCounts) {
- pidCounts.put(pid, pidCounts.get(pid)+1);
- pw.print(prefix); pw.print(name); pw.print(": pid=");
- pw.print(pid); pw.print(" uid=");
- pw.print(uid); pw.print(" user=");
- pw.print(userHandle); pw.print(" target=");
- pw.println(Integer.toHexString(System.identityHashCode(
- observer != null ? observer.asBinder() : null)));
- }
- }
-
- public static final int INSERT_TYPE = 0;
- public static final int UPDATE_TYPE = 1;
- public static final int DELETE_TYPE = 2;
-
- private String mName;
- private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
- private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
-
- public ObserverNode(String name) {
- mName = name;
- }
-
- public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- String name, String prefix, int[] counts, SparseIntArray pidCounts) {
- String innerName = null;
- if (mObservers.size() > 0) {
- if ("".equals(name)) {
- innerName = mName;
- } else {
- innerName = name + "/" + mName;
- }
- for (int i=0; i<mObservers.size(); i++) {
- counts[1]++;
- mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
- pidCounts);
- }
- }
- if (mChildren.size() > 0) {
- if (innerName == null) {
- if ("".equals(name)) {
- innerName = mName;
- } else {
- innerName = name + "/" + mName;
- }
- }
- for (int i=0; i<mChildren.size(); i++) {
- counts[0]++;
- mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
- counts, pidCounts);
- }
- }
- }
-
- private String getUriSegment(Uri uri, int index) {
- if (uri != null) {
- if (index == 0) {
- return uri.getAuthority();
- } else {
- return uri.getPathSegments().get(index - 1);
- }
- } else {
- return null;
- }
- }
-
- private int countUriSegments(Uri uri) {
- if (uri == null) {
- return 0;
- }
- return uri.getPathSegments().size() + 1;
- }
-
- // Invariant: userHandle is either a hard user number or is USER_ALL
- public void addObserverLocked(Uri uri, IContentObserver observer,
- boolean notifyForDescendants, Object observersLock,
- int uid, int pid, int userHandle) {
- addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
- uid, pid, userHandle);
- }
-
- private void addObserverLocked(Uri uri, int index, IContentObserver observer,
- boolean notifyForDescendants, Object observersLock,
- int uid, int pid, int userHandle) {
- // If this is the leaf node add the observer
- if (index == countUriSegments(uri)) {
- mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
- uid, pid, userHandle));
- return;
- }
-
- // Look to see if the proper child already exists
- String segment = getUriSegment(uri, index);
- if (segment == null) {
- throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
- }
- int N = mChildren.size();
- for (int i = 0; i < N; i++) {
- ObserverNode node = mChildren.get(i);
- if (node.mName.equals(segment)) {
- node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
- observersLock, uid, pid, userHandle);
- return;
- }
- }
-
- // No child found, create one
- ObserverNode node = new ObserverNode(segment);
- mChildren.add(node);
- node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
- observersLock, uid, pid, userHandle);
- }
-
- public boolean removeObserverLocked(IContentObserver observer) {
- int size = mChildren.size();
- for (int i = 0; i < size; i++) {
- boolean empty = mChildren.get(i).removeObserverLocked(observer);
- if (empty) {
- mChildren.remove(i);
- i--;
- size--;
- }
- }
-
- IBinder observerBinder = observer.asBinder();
- size = mObservers.size();
- for (int i = 0; i < size; i++) {
- ObserverEntry entry = mObservers.get(i);
- if (entry.observer.asBinder() == observerBinder) {
- mObservers.remove(i);
- // We no longer need to listen for death notifications. Remove it.
- observerBinder.unlinkToDeath(entry, 0);
- break;
- }
- }
-
- if (mChildren.size() == 0 && mObservers.size() == 0) {
- return true;
- }
- return false;
- }
-
- private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
- boolean observerWantsSelfNotifications, int targetUserHandle,
- ArrayList<ObserverCall> calls) {
- int N = mObservers.size();
- IBinder observerBinder = observer == null ? null : observer.asBinder();
- for (int i = 0; i < N; i++) {
- ObserverEntry entry = mObservers.get(i);
-
- // Don't notify the observer if it sent the notification and isn't interested
- // in self notifications
- boolean selfChange = (entry.observer.asBinder() == observerBinder);
- if (selfChange && !observerWantsSelfNotifications) {
- continue;
- }
-
- // Does this observer match the target user?
- if (targetUserHandle == UserHandle.USER_ALL
- || entry.userHandle == UserHandle.USER_ALL
- || targetUserHandle == entry.userHandle) {
- // Make sure the observer is interested in the notification
- if (leaf || (!leaf && entry.notifyForDescendants)) {
- calls.add(new ObserverCall(this, entry.observer, selfChange));
- }
- }
- }
- }
-
- /**
- * targetUserHandle is either a hard user handle or is USER_ALL
- */
- public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
- boolean observerWantsSelfNotifications, int targetUserHandle,
- ArrayList<ObserverCall> calls) {
- String segment = null;
- int segmentCount = countUriSegments(uri);
- if (index >= segmentCount) {
- // This is the leaf node, notify all observers
- collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
- targetUserHandle, calls);
- } else if (index < segmentCount){
- segment = getUriSegment(uri, index);
- // Notify any observers at this level who are interested in descendants
- collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
- targetUserHandle, calls);
- }
-
- int N = mChildren.size();
- for (int i = 0; i < N; i++) {
- ObserverNode node = mChildren.get(i);
- if (segment == null || node.mName.equals(segment)) {
- // We found the child,
- node.collectObserversLocked(uri, index + 1,
- observer, observerWantsSelfNotifications, targetUserHandle, calls);
- if (segment != null) {
- break;
- }
- }
- }
- }
- }
-}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7aa2507..8a9eed2 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -418,6 +418,9 @@ public abstract class Context {
/** Return the name of this application's package. */
public abstract String getPackageName();
+ /** @hide Return the name of the base context this context is derived from. */
+ public abstract String getBasePackageName();
+
/** Return the full application info for this context's package. */
public abstract ApplicationInfo getApplicationInfo();
@@ -1135,6 +1138,14 @@ public abstract class Context {
String receiverPermission);
/**
+ * Like {@link #sendBroadcast(Intent, String)}, but also allows specification
+ * of an assocated app op as per {@link android.app.AppOpsManager}.
+ * @hide
+ */
+ public abstract void sendBroadcast(Intent intent,
+ String receiverPermission, int appOp);
+
+ /**
* Broadcast the given intent to all interested BroadcastReceivers, delivering
* them one at a time to allow more preferred receivers to consume the
* broadcast before it is delivered to less preferred receivers. This
@@ -1205,6 +1216,17 @@ public abstract class Context {
Bundle initialExtras);
/**
+ * Like {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler,
+ * int, String, android.os.Bundle)}, but also allows specification
+ * of an assocated app op as per {@link android.app.AppOpsManager}.
+ * @hide
+ */
+ public abstract void sendOrderedBroadcast(Intent intent,
+ String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras);
+
+ /**
* Version of {@link #sendBroadcast(Intent)} that allows you to specify the
* user the broadcast will be sent to. This is not available to applications
* that are not pre-installed on the system image. Using it requires holding
@@ -1677,7 +1699,7 @@ public abstract class Context {
* argument for use by system server and other multi-user aware code.
* @hide
*/
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags, UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -1993,17 +2015,6 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a {@link
- * android.net.ThrottleManager} for handling management of
- * throttling.
- *
- * @hide
- * @see #getSystemService
- * @see android.net.ThrottleManager
- */
- public static final String THROTTLE_SERVICE = "throttle";
-
- /**
- * Use with {@link #getSystemService} to retrieve a {@link
* android.os.IUpdateLock} for managing runtime sequences that
* must not be interrupted by headless OTA application or similar.
*
@@ -2255,6 +2266,18 @@ public abstract class Context {
public static final String USER_SERVICE = "user";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.app.AppOpsManager} for tracking application operations
+ * on the device.
+ *
+ * @see #getSystemService
+ * @see android.app.AppOpsManager
+ *
+ * @hide
+ */
+ public static final String APP_OPS_SERVICE = "appops";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
@@ -2668,6 +2691,14 @@ public abstract class Context {
throws PackageManager.NameNotFoundException;
/**
+ * Get the userId associated with this context
+ * @return user id
+ *
+ * @hide
+ */
+ public abstract int getUserId();
+
+ /**
* Return a new Context object for the current Context but whose resources
* are adjusted to match the given Configuration. Each call to this method
* returns a new instance of a Context object; Context objects are not
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 84ad667..2f1bf8c 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -135,6 +135,12 @@ public class ContextWrapper extends Context {
return mBase.getPackageName();
}
+ /** @hide */
+ @Override
+ public String getBasePackageName() {
+ return mBase.getBasePackageName();
+ }
+
@Override
public ApplicationInfo getApplicationInfo() {
return mBase.getApplicationInfo();
@@ -343,6 +349,12 @@ public class ContextWrapper extends Context {
mBase.sendBroadcast(intent, receiverPermission);
}
+ /** @hide */
+ @Override
+ public void sendBroadcast(Intent intent, String receiverPermission, int appOp) {
+ mBase.sendBroadcast(intent, receiverPermission, appOp);
+ }
+
@Override
public void sendOrderedBroadcast(Intent intent,
String receiverPermission) {
@@ -359,6 +371,17 @@ public class ContextWrapper extends Context {
initialData, initialExtras);
}
+ /** @hide */
+ @Override
+ public void sendOrderedBroadcast(
+ Intent intent, String receiverPermission, int appOp, BroadcastReceiver resultReceiver,
+ Handler scheduler, int initialCode, String initialData,
+ Bundle initialExtras) {
+ mBase.sendOrderedBroadcast(intent, receiverPermission, appOp,
+ resultReceiver, scheduler, initialCode,
+ initialData, initialExtras);
+ }
+
@Override
public void sendBroadcastAsUser(Intent intent, UserHandle user) {
mBase.sendBroadcastAsUser(intent, user);
@@ -475,8 +498,9 @@ public class ContextWrapper extends Context {
/** @hide */
@Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) {
- return mBase.bindService(service, conn, flags, userHandle);
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ UserHandle user) {
+ return mBase.bindServiceAsUser(service, conn, flags, user);
}
@Override
@@ -599,6 +623,12 @@ public class ContextWrapper extends Context {
return mBase.createPackageContextAsUser(packageName, flags, user);
}
+ /** @hide */
+ @Override
+ public int getUserId() {
+ return mBase.getUserId();
+ }
+
@Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
return mBase.createConfigurationContext(overrideConfiguration);
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 9f7a104..4e89dec 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -65,9 +65,14 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal);
if (cursor != null) {
- // Ensure the cursor window is filled
- cursor.getCount();
- registerContentObserver(cursor, mObserver);
+ try {
+ // Ensure the cursor window is filled.
+ cursor.getCount();
+ registerContentObserver(cursor, mObserver);
+ } catch (RuntimeException ex) {
+ cursor.close();
+ throw ex;
+ }
}
return cursor;
} finally {
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index 254901b..af0b8f0 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -26,15 +26,16 @@ import android.content.IOnPrimaryClipChangedListener;
* {@hide}
*/
interface IClipboard {
- void setPrimaryClip(in ClipData clip);
+ void setPrimaryClip(in ClipData clip, String callingPackage);
ClipData getPrimaryClip(String pkg);
- ClipDescription getPrimaryClipDescription();
- boolean hasPrimaryClip();
- void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
+ ClipDescription getPrimaryClipDescription(String callingPackage);
+ boolean hasPrimaryClip(String callingPackage);
+ void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener,
+ String callingPackage);
void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener);
/**
* Returns true if the clipboard contains text; false otherwise.
*/
- boolean hasClipboardText();
+ boolean hasClipboardText(String callingPackage);
}
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index eeba1e0..62b79f0 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -34,30 +34,33 @@ import java.util.ArrayList;
* @hide
*/
public interface IContentProvider extends IInterface {
- public Cursor query(Uri url, String[] projection, String selection,
+ public Cursor query(String callingPkg, Uri url, String[] projection, String selection,
String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal)
throws RemoteException;
public String getType(Uri url) throws RemoteException;
- public Uri insert(Uri url, ContentValues initialValues)
+ public Uri insert(String callingPkg, Uri url, ContentValues initialValues)
throws RemoteException;
- public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException;
- public int delete(Uri url, String selection, String[] selectionArgs)
+ public int bulkInsert(String callingPkg, Uri url, ContentValues[] initialValues)
throws RemoteException;
- public int update(Uri url, ContentValues values, String selection,
+ public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
+ throws RemoteException;
+ public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException;
- public ParcelFileDescriptor openFile(Uri url, String mode)
+ public ParcelFileDescriptor openFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException;
- public AssetFileDescriptor openAssetFile(Uri url, String mode)
+ public AssetFileDescriptor openAssetFile(String callingPkg, Uri url, String mode)
throws RemoteException, FileNotFoundException;
- public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
- throws RemoteException, OperationApplicationException;
- public Bundle call(String method, String arg, Bundle extras) throws RemoteException;
+ public ContentProviderResult[] applyBatch(String callingPkg,
+ ArrayList<ContentProviderOperation> operations)
+ throws RemoteException, OperationApplicationException;
+ public Bundle call(String callingPkg, String method, String arg, Bundle extras)
+ throws RemoteException;
public ICancellationSignal createCancellationSignal() throws RemoteException;
// Data interchange.
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
- public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts)
- throws RemoteException, FileNotFoundException;
+ public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
+ Bundle opts) throws RemoteException, FileNotFoundException;
/* IPC constants */
static final String descriptor = "android.content.IContentProvider";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 89b1bbd..f8ff8d1 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -883,7 +883,7 @@ public class Intent implements Parcelable, Cloneable {
* Activity Action: Allow the user to select a particular kind of data and
* return it. This is different than {@link #ACTION_PICK} in that here we
* just say what kind of data is desired, not a URI of existing data from
- * which the user can pick. A ACTION_GET_CONTENT could allow the user to
+ * which the user can pick. An ACTION_GET_CONTENT could allow the user to
* create the data as it runs (for example taking a picture or recording a
* sound), let them browse over the web and download the desired data,
* etc.
@@ -917,12 +917,17 @@ public class Intent implements Parcelable, Cloneable {
* from a remote server but not already on the local device (thus requiring
* they be downloaded when opened).
* <p>
+ * If the caller can handle multiple returned items (the user performing
+ * multiple selection), then it can specify {@link #EXTRA_ALLOW_MULTIPLE}
+ * to indicate this.
+ * <p>
* Input: {@link #getType} is the desired MIME type to retrieve. Note
* that no URI is supplied in the intent, as there are no constraints on
* where the returned data originally comes from. You may also include the
* {@link #CATEGORY_OPENABLE} if you can only accept data that can be
* opened as a stream. You may use {@link #EXTRA_LOCAL_ONLY} to limit content
- * selection to local data.
+ * selection to local data. You may use {@link #EXTRA_ALLOW_MULTIPLE} to
+ * allow the user to select multiple items.
* <p>
* Output: The URI of the item that was picked. This must be a content:
* URI so that any receiver can access it.
@@ -1140,14 +1145,33 @@ public class Intent implements Parcelable, Cloneable {
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
+
/**
* Activity Action: Perform assist action.
* <p>
- * Input: nothing
+ * Input: {@link #EXTRA_ASSIST_PACKAGE} and {@link #EXTRA_ASSIST_CONTEXT} can provide
+ * additional optional contextual information about where the user was when they requested
+ * the assist.
* Output: nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_ASSIST = "android.intent.action.ASSIST";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} containing the name of the current
+ * foreground application package at the time the assist was invoked.
+ */
+ public static final String EXTRA_ASSIST_PACKAGE
+ = "android.intent.extra.ASSIST_PACKAGE";
+
+ /**
+ * An optional field on {@link #ACTION_ASSIST} containing additional contextual
+ * information supplied by the current foreground app at the time of the assist
+ * request. This is a {@link Bundle} of additional data.
+ */
+ public static final String EXTRA_ASSIST_CONTEXT
+ = "android.intent.extra.ASSIST_CONTEXT";
+
/**
* Activity Action: List all available applications
* <p>Input: Nothing.
@@ -1588,7 +1612,7 @@ public class Intent implements Parcelable, Cloneable {
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
* <li> {@link #EXTRA_CHANGED_COMPONENT_NAME_LIST} containing the class name
- * of the changed components.
+ * of the changed components (or the package name itself).
* <li> {@link #EXTRA_DONT_KILL_APP} containing boolean field to override the
* default action of restarting the application.
* </ul>
@@ -2295,6 +2319,61 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.DOCK_EVENT";
/**
+ * Broadcast Action: A broadcast when idle maintenance can be started.
+ * This means that the user is not interacting with the device and is
+ * not expected to do so soon. Typical use of the idle maintenance is
+ * to perform somehow expensive tasks that can be postponed at a moment
+ * when they will not degrade user experience.
+ * <p>
+ * <p class="note">In order to keep the device responsive in case of an
+ * unexpected user interaction, implementations of a maintenance task
+ * should be interruptible. In such a scenario a broadcast with action
+ * {@link #ACTION_IDLE_MAINTENANCE_END} will be sent. In other words, you
+ * should not do the maintenance work in
+ * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather start a
+ * maintenance service by {@link Context#startService(Intent)}. Also
+ * you should hold a wake lock while your maintenance service is running
+ * to prevent the device going to sleep.
+ * </p>
+ * <p>
+ * <p class="note">This is a protected intent that can only be sent by
+ * the system.
+ * </p>
+ *
+ * @see #ACTION_IDLE_MAINTENANCE_END
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_IDLE_MAINTENANCE_START =
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_START";
+
+ /**
+ * Broadcast Action: A broadcast when idle maintenance should be stopped.
+ * This means that the user was not interacting with the device as a result
+ * of which a broadcast with action {@link #ACTION_IDLE_MAINTENANCE_START}
+ * was sent and now the user started interacting with the device. Typical
+ * use of the idle maintenance is to perform somehow expensive tasks that
+ * can be postponed at a moment when they will not degrade user experience.
+ * <p>
+ * <p class="note">In order to keep the device responsive in case of an
+ * unexpected user interaction, implementations of a maintenance task
+ * should be interruptible. Hence, on receiving a broadcast with this
+ * action, the maintenance task should be interrupted as soon as possible.
+ * In other words, you should not do the maintenance work in
+ * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather stop the
+ * maintenance service that was started on receiving of
+ * {@link #ACTION_IDLE_MAINTENANCE_START}.Also you should release the wake
+ * lock you acquired when your maintenance service started.
+ * </p>
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system.
+ *
+ * @see #ACTION_IDLE_MAINTENANCE_START
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_IDLE_MAINTENANCE_END =
+ "android.intent.action.ACTION_IDLE_MAINTENANCE_END";
+
+ /**
* Broadcast Action: a remote intent is to be broadcasted.
*
* A remote intent is used for remote RPC between devices. The remote intent
@@ -2465,6 +2544,14 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_QUICK_CLOCK =
"android.intent.action.QUICK_CLOCK";
+ /**
+ * Broadcast Action: This is broadcast when a user action should request the
+ * brightness setting dialog.
+ * @hide
+ */
+ public static final String ACTION_SHOW_BRIGHTNESS_DIALOG =
+ "android.intent.action.SHOW_BRIGHTNESS_DIALOG";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2586,7 +2673,8 @@ public class Intent implements Parcelable, Cloneable {
public static final String CATEGORY_SAMPLE_CODE = "android.intent.category.SAMPLE_CODE";
/**
* Used to indicate that a GET_CONTENT intent only wants URIs that can be opened with
- * ContentResolver.openInputStream. Openable URIs must support the columns in OpenableColumns
+ * ContentResolver.openInputStream. Openable URIs must support the columns in
+ * {@link android.provider.OpenableColumns}
* when queried, though it is allowable for those columns to be blank.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
@@ -2969,7 +3057,9 @@ public class Intent implements Parcelable, Cloneable {
/**
* This field is part of {@link android.content.Intent#ACTION_PACKAGE_CHANGED},
- * and contains a string array of all of the components that have changed.
+ * and contains a string array of all of the components that have changed. If
+ * the state of the overall package has changed, then it will contain an entry
+ * with the package name itself.
*/
public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST =
"android.intent.extra.changed_component_name_list";
@@ -3024,6 +3114,17 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.extra.LOCAL_ONLY";
/**
+ * Used to indicate that a {@link #ACTION_GET_CONTENT} intent can allow the
+ * user to select and return multiple items. This is a boolean extra; the default
+ * is false. If true, an implementation of ACTION_GET_CONTENT is allowed to
+ * present the user with a UI where they can pick multiple items that are all
+ * returned to the caller. When this happens, they should be returned as
+ * the {@link #getClipData()} part of the result Intent.
+ */
+ public static final String EXTRA_ALLOW_MULTIPLE =
+ "android.intent.extra.ALLOW_MULTIPLE";
+
+ /**
* The userHandle carried with broadcast intents related to addition, removal and switching of users
* - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
* @hide
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 3b0d846..5e65b59 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -86,7 +86,8 @@ import java.util.Set;
* <strong>data scheme+authority+path</strong> if specified) must match.
*
* <p><strong>Action</strong> matches if any of the given values match the
- * Intent action, <em>or</em> if no actions were specified in the filter.
+ * Intent action; if the filter specifies no actions, then it will only match
+ * Intents that do not contain an action.
*
* <p><strong>Data Type</strong> matches if any of the given values match the
* Intent type. The Intent
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 17813ec..513a556 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -79,6 +79,25 @@ public class PeriodicSync implements Parcelable {
return account.equals(other.account)
&& authority.equals(other.authority)
&& period == other.period
- && SyncStorageEngine.equals(extras, other.extras);
+ && syncExtrasEquals(extras, other.extras);
+ }
+
+ /** {@hide} */
+ public static boolean syncExtrasEquals(Bundle b1, Bundle b2) {
+ if (b1.size() != b2.size()) {
+ return false;
+ }
+ if (b1.isEmpty()) {
+ return true;
+ }
+ for (String key : b1.keySet()) {
+ if (!b2.containsKey(key)) {
+ return false;
+ }
+ if (!b1.get(key).equals(b2.get(key))) {
+ return false;
+ }
+ }
+ return true;
}
}
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index 7b643a0..8bb3ee7 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -31,7 +31,7 @@ import java.io.IOException;
* A cache of services that export the {@link android.content.ISyncAdapter} interface.
* @hide
*/
-/* package private */ class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
+public class SyncAdaptersCache extends RegisteredServicesCache<SyncAdapterType> {
private static final String TAG = "Account";
private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
@@ -39,7 +39,7 @@ import java.io.IOException;
private static final String ATTRIBUTES_NAME = "sync-adapter";
private static final MySerializer sSerializer = new MySerializer();
- SyncAdaptersCache(Context context) {
+ public SyncAdaptersCache(Context context) {
super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
}
diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java
index abfe964..0284882 100644
--- a/core/java/android/content/SyncInfo.java
+++ b/core/java/android/content/SyncInfo.java
@@ -46,7 +46,7 @@ public class SyncInfo implements Parcelable {
public final long startTime;
/** @hide */
- SyncInfo(int authorityId, Account account, String authority,
+ public SyncInfo(int authorityId, Account account, String authority,
long startTime) {
this.authorityId = authorityId;
this.account = account;
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
deleted file mode 100644
index e4b4b97..0000000
--- a/core/java/android/content/SyncManager.java
+++ /dev/null
@@ -1,2632 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.accounts.Account;
-import android.accounts.AccountAndUser;
-import android.accounts.AccountManager;
-import android.accounts.AccountManagerService;
-import android.app.ActivityManager;
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.SyncStorageEngine.OnSyncRequestListener;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.content.pm.RegisteredServicesCache;
-import android.content.pm.RegisteredServicesCacheListener;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.WorkSource;
-import android.provider.Settings;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.EventLog;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.IndentingPrintWriter;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * @hide
- */
-public class SyncManager {
- private static final String TAG = "SyncManager";
-
- /** Delay a sync due to local changes this long. In milliseconds */
- private static final long LOCAL_SYNC_DELAY;
-
- /**
- * If a sync takes longer than this and the sync queue is not empty then we will
- * cancel it and add it back to the end of the sync queue. In milliseconds.
- */
- private static final long MAX_TIME_PER_SYNC;
-
- static {
- final boolean isLargeRAM = ActivityManager.isLargeRAM();
- int defaultMaxInitSyncs = isLargeRAM ? 5 : 2;
- int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1;
- MAX_SIMULTANEOUS_INITIALIZATION_SYNCS =
- SystemProperties.getInt("sync.max_init_syncs", defaultMaxInitSyncs);
- MAX_SIMULTANEOUS_REGULAR_SYNCS =
- SystemProperties.getInt("sync.max_regular_syncs", defaultMaxRegularSyncs);
- LOCAL_SYNC_DELAY =
- SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
- MAX_TIME_PER_SYNC =
- SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
- SYNC_NOTIFICATION_DELAY =
- SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
- }
-
- private static final long SYNC_NOTIFICATION_DELAY;
-
- /**
- * When retrying a sync for the first time use this delay. After that
- * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
- * In milliseconds.
- */
- private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
-
- /**
- * Default the max sync retry time to this value.
- */
- private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
-
- /**
- * How long to wait before retrying a sync that failed due to one already being in progress.
- */
- private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
-
- private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
-
- private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
- private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
- private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
-
- private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
- private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
-
- private Context mContext;
-
- private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
-
- // TODO: add better locking around mRunningAccounts
- private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
-
- volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
- volatile private PowerManager.WakeLock mSyncManagerWakeLock;
- volatile private boolean mDataConnectionIsConnected = false;
- volatile private boolean mStorageIsLow = false;
-
- private final NotificationManager mNotificationMgr;
- private AlarmManager mAlarmService = null;
-
- private SyncStorageEngine mSyncStorageEngine;
-
- @GuardedBy("mSyncQueue")
- private final SyncQueue mSyncQueue;
-
- protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
-
- // set if the sync active indicator should be reported
- private boolean mNeedSyncActiveNotification = false;
-
- private final PendingIntent mSyncAlarmIntent;
- // Synchronized on "this". Instead of using this directly one should instead call
- // its accessor, getConnManager().
- private ConnectivityManager mConnManagerDoNotUseDirectly;
-
- protected SyncAdaptersCache mSyncAdapters;
-
- private BroadcastReceiver mStorageIntentReceiver =
- new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Internal storage is low.");
- }
- mStorageIsLow = true;
- cancelActiveSync(null /* any account */, UserHandle.USER_ALL,
- null /* any authority */);
- } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Internal storage is ok.");
- }
- mStorageIsLow = false;
- sendCheckAlarmsMessage();
- }
- }
- };
-
- private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- mSyncHandler.onBootCompleted();
- }
- };
-
- private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- if (getConnectivityManager().getBackgroundDataSetting()) {
- scheduleSync(null /* account */, UserHandle.USER_ALL, null /* authority */,
- new Bundle(), 0 /* delay */,
- false /* onlyThoseWithUnknownSyncableState */);
- }
- }
- };
-
- private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- updateRunningAccounts();
-
- // Kick off sync for everyone, since this was a radical account change
- scheduleSync(null, UserHandle.USER_ALL, null, null, 0 /* no delay */, false);
- }
- };
-
- private final PowerManager mPowerManager;
-
- // Use this as a random offset to seed all periodic syncs
- private int mSyncRandomOffsetMillis;
-
- private final UserManager mUserManager;
-
- private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
- private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
-
- private List<UserInfo> getAllUsers() {
- return mUserManager.getUsers();
- }
-
- private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) {
- boolean found = false;
- for (int i = 0; i < accounts.length; i++) {
- if (accounts[i].userId == userId
- && accounts[i].account.equals(account)) {
- found = true;
- break;
- }
- }
- return found;
- }
-
- public void updateRunningAccounts() {
- mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
-
- if (mBootCompleted) {
- doDatabaseCleanup();
- }
-
- for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- if (!containsAccountAndUser(mRunningAccounts,
- currentSyncContext.mSyncOperation.account,
- currentSyncContext.mSyncOperation.userId)) {
- Log.d(TAG, "canceling sync since the account is no longer running");
- sendSyncFinishedOrCanceledMessage(currentSyncContext,
- null /* no result since this is a cancel */);
- }
- }
-
- // we must do this since we don't bother scheduling alarms when
- // the accounts are not set yet
- sendCheckAlarmsMessage();
- }
-
- private void doDatabaseCleanup() {
- for (UserInfo user : mUserManager.getUsers(true)) {
- // Skip any partially created/removed users
- if (user.partial) continue;
- Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id);
- mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
- }
- }
-
- private BroadcastReceiver mConnectivityIntentReceiver =
- new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- final boolean wasConnected = mDataConnectionIsConnected;
-
- // don't use the intent to figure out if network is connected, just check
- // ConnectivityManager directly.
- mDataConnectionIsConnected = readDataConnectionState();
- if (mDataConnectionIsConnected) {
- if (!wasConnected) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Reconnection detected: clearing all backoffs");
- }
- mSyncStorageEngine.clearAllBackoffs(mSyncQueue);
- }
- sendCheckAlarmsMessage();
- }
- }
- };
-
- private boolean readDataConnectionState() {
- NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo();
- return (networkInfo != null) && networkInfo.isConnected();
- }
-
- private BroadcastReceiver mShutdownIntentReceiver =
- new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- Log.w(TAG, "Writing sync state before shutdown...");
- getSyncStorageEngine().writeAllState();
- }
- };
-
- private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
- if (userId == UserHandle.USER_NULL) return;
-
- if (Intent.ACTION_USER_REMOVED.equals(action)) {
- onUserRemoved(userId);
- } else if (Intent.ACTION_USER_STARTING.equals(action)) {
- onUserStarting(userId);
- } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
- onUserStopping(userId);
- }
- }
- };
-
- private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
- private final SyncHandler mSyncHandler;
-
- private volatile boolean mBootCompleted = false;
-
- private ConnectivityManager getConnectivityManager() {
- synchronized (this) {
- if (mConnManagerDoNotUseDirectly == null) {
- mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- }
- return mConnManagerDoNotUseDirectly;
- }
- }
-
- /**
- * Should only be created after {@link ContentService#systemReady()} so that
- * {@link PackageManager} is ready to query.
- */
- public SyncManager(Context context, boolean factoryTest) {
- // Initialize the SyncStorageEngine first, before registering observers
- // and creating threads and so on; it may fail if the disk is full.
- mContext = context;
-
- SyncStorageEngine.init(context);
- mSyncStorageEngine = SyncStorageEngine.getSingleton();
- mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
- public void onSyncRequest(Account account, int userId, String authority,
- Bundle extras) {
- scheduleSync(account, userId, authority, extras, 0, false);
- }
- });
-
- mSyncAdapters = new SyncAdaptersCache(mContext);
- mSyncQueue = new SyncQueue(mSyncStorageEngine, mSyncAdapters);
-
- HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- syncThread.start();
- mSyncHandler = new SyncHandler(syncThread.getLooper());
-
- mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
- @Override
- public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
- if (!removed) {
- scheduleSync(null, UserHandle.USER_ALL, type.authority, null, 0 /* no delay */,
- false /* onlyThoseWithUnkownSyncableState */);
- }
- }
- }, mSyncHandler);
-
- mSyncAlarmIntent = PendingIntent.getBroadcast(
- mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
-
- IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
- context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
-
- if (!factoryTest) {
- intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- context.registerReceiver(mBootCompletedReceiver, intentFilter);
- }
-
- intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
- context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
-
- intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
- intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
- context.registerReceiver(mStorageIntentReceiver, intentFilter);
-
- intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
- intentFilter.setPriority(100);
- context.registerReceiver(mShutdownIntentReceiver, intentFilter);
-
- intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_REMOVED);
- intentFilter.addAction(Intent.ACTION_USER_STARTING);
- intentFilter.addAction(Intent.ACTION_USER_STOPPING);
- mContext.registerReceiverAsUser(
- mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
-
- if (!factoryTest) {
- mNotificationMgr = (NotificationManager)
- context.getSystemService(Context.NOTIFICATION_SERVICE);
- context.registerReceiver(new SyncAlarmIntentReceiver(),
- new IntentFilter(ACTION_SYNC_ALARM));
- } else {
- mNotificationMgr = null;
- }
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-
- // This WakeLock is used to ensure that we stay awake between the time that we receive
- // a sync alarm notification and when we finish processing it. We need to do this
- // because we don't do the work in the alarm handler, rather we do it in a message
- // handler.
- mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- HANDLE_SYNC_ALARM_WAKE_LOCK);
- mHandleAlarmWakeLock.setReferenceCounted(false);
-
- // This WakeLock is used to ensure that we stay awake while running the sync loop
- // message handler. Normally we will hold a sync adapter wake lock while it is being
- // synced but during the execution of the sync loop it might finish a sync for
- // one sync adapter before starting the sync for the other sync adapter and we
- // don't want the device to go to sleep during that window.
- mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- SYNC_LOOP_WAKE_LOCK);
- mSyncManagerWakeLock.setReferenceCounted(false);
-
- mSyncStorageEngine.addStatusChangeListener(
- ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
- public void onStatusChanged(int which) {
- // force the sync loop to run if the settings change
- sendCheckAlarmsMessage();
- }
- });
-
- if (!factoryTest) {
- // Register for account list updates for all users
- mContext.registerReceiverAsUser(mAccountsUpdatedReceiver,
- UserHandle.ALL,
- new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
- null, null);
- }
-
- // Pick a random second in a day to seed all periodic syncs
- mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
- }
-
- /**
- * Return a random value v that satisfies minValue <= v < maxValue. The difference between
- * maxValue and minValue must be less than Integer.MAX_VALUE.
- */
- private long jitterize(long minValue, long maxValue) {
- Random random = new Random(SystemClock.elapsedRealtime());
- long spread = maxValue - minValue;
- if (spread > Integer.MAX_VALUE) {
- throw new IllegalArgumentException("the difference between the maxValue and the "
- + "minValue must be less than " + Integer.MAX_VALUE);
- }
- return minValue + random.nextInt((int)spread);
- }
-
- public SyncStorageEngine getSyncStorageEngine() {
- return mSyncStorageEngine;
- }
-
- private void ensureAlarmService() {
- if (mAlarmService == null) {
- mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
- }
- }
-
- /**
- * Initiate a sync. This can start a sync for all providers
- * (pass null to url, set onlyTicklable to false), only those
- * providers that are marked as ticklable (pass null to url,
- * set onlyTicklable to true), or a specific provider (set url
- * to the content url of the provider).
- *
- * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
- * true then initiate a sync that just checks for local changes to send
- * to the server, otherwise initiate a sync that first gets any
- * changes from the server before sending local changes back to
- * the server.
- *
- * <p>If a specific provider is being synced (the url is non-null)
- * then the extras can contain SyncAdapter-specific information
- * to control what gets synced (e.g. which specific feed to sync).
- *
- * <p>You'll start getting callbacks after this.
- *
- * @param requestedAccount the account to sync, may be null to signify all accounts
- * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
- * then all users' accounts are considered.
- * @param requestedAuthority the authority to sync, may be null to indicate all authorities
- * @param extras a Map of SyncAdapter-specific information to control
- * syncs of a specific provider. Can be null. Is ignored
- * if the url is null.
- * @param delay how many milliseconds in the future to wait before performing this
- * @param onlyThoseWithUnkownSyncableState
- */
- public void scheduleSync(Account requestedAccount, int userId, String requestedAuthority,
- Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-
- final boolean backgroundDataUsageAllowed = !mBootCompleted ||
- getConnectivityManager().getBackgroundDataSetting();
-
- if (extras == null) extras = new Bundle();
-
- Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
- if (expedited) {
- delay = -1; // this means schedule at the front of the queue
- }
-
- AccountAndUser[] accounts;
- if (requestedAccount != null && userId != UserHandle.USER_ALL) {
- accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
- } else {
- // if the accounts aren't configured yet then we can't support an account-less
- // sync request
- accounts = mRunningAccounts;
- if (accounts.length == 0) {
- if (isLoggable) {
- Log.v(TAG, "scheduleSync: no accounts configured, dropping");
- }
- return;
- }
- }
-
- final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
- final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- if (manualSync) {
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
- }
- final boolean ignoreSettings =
- extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
-
- int source;
- if (uploadOnly) {
- source = SyncStorageEngine.SOURCE_LOCAL;
- } else if (manualSync) {
- source = SyncStorageEngine.SOURCE_USER;
- } else if (requestedAuthority == null) {
- source = SyncStorageEngine.SOURCE_POLL;
- } else {
- // this isn't strictly server, since arbitrary callers can (and do) request
- // a non-forced two-way sync on a specific url
- source = SyncStorageEngine.SOURCE_SERVER;
- }
-
- for (AccountAndUser account : accounts) {
- // Compile a list of authorities that have sync adapters.
- // For each authority sync each account that matches a sync adapter.
- final HashSet<String> syncableAuthorities = new HashSet<String>();
- for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
- mSyncAdapters.getAllServices(account.userId)) {
- syncableAuthorities.add(syncAdapter.type.authority);
- }
-
- // if the url was specified then replace the list of authorities
- // with just this authority or clear it if this authority isn't
- // syncable
- if (requestedAuthority != null) {
- final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
- syncableAuthorities.clear();
- if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
- }
-
- for (String authority : syncableAuthorities) {
- int isSyncable = mSyncStorageEngine.getIsSyncable(account.account, account.userId,
- authority);
- if (isSyncable == 0) {
- continue;
- }
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(authority, account.account.type), account.userId);
- if (syncAdapterInfo == null) {
- continue;
- }
- final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
- final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
- if (isSyncable < 0 && isAlwaysSyncable) {
- mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
- isSyncable = 1;
- }
- if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
- continue;
- }
- if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
- continue;
- }
-
- // always allow if the isSyncable state is unknown
- boolean syncAllowed =
- (isSyncable < 0)
- || ignoreSettings
- || (backgroundDataUsageAllowed
- && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
- && mSyncStorageEngine.getSyncAutomatically(account.account,
- account.userId, authority));
- if (!syncAllowed) {
- if (isLoggable) {
- Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
- + " is not allowed, dropping request");
- }
- continue;
- }
-
- Pair<Long, Long> backoff = mSyncStorageEngine
- .getBackoff(account.account, account.userId, authority);
- long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
- account.userId, authority);
- final long backoffTime = backoff != null ? backoff.first : 0;
- if (isSyncable < 0) {
- Bundle newExtras = new Bundle();
- newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
- if (isLoggable) {
- Log.v(TAG, "scheduleSync:"
- + " delay " + delay
- + ", source " + source
- + ", account " + account
- + ", authority " + authority
- + ", extras " + newExtras);
- }
- scheduleSyncOperation(
- new SyncOperation(account.account, account.userId, source, authority,
- newExtras, 0, backoffTime, delayUntil, allowParallelSyncs));
- }
- if (!onlyThoseWithUnkownSyncableState) {
- if (isLoggable) {
- Log.v(TAG, "scheduleSync:"
- + " delay " + delay
- + ", source " + source
- + ", account " + account
- + ", authority " + authority
- + ", extras " + extras);
- }
- scheduleSyncOperation(
- new SyncOperation(account.account, account.userId, source, authority,
- extras, delay, backoffTime, delayUntil, allowParallelSyncs));
- }
- }
- }
- }
-
- public void scheduleLocalSync(Account account, int userId, String authority) {
- final Bundle extras = new Bundle();
- extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
- scheduleSync(account, userId, authority, extras, LOCAL_SYNC_DELAY,
- false /* onlyThoseWithUnkownSyncableState */);
- }
-
- public SyncAdapterType[] getSyncAdapterTypes(int userId) {
- final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos;
- serviceInfos = mSyncAdapters.getAllServices(userId);
- SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
- int i = 0;
- for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
- types[i] = serviceInfo.type;
- ++i;
- }
- return types;
- }
-
- private void sendSyncAlarmMessage() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
- mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
- }
-
- private void sendCheckAlarmsMessage() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
- mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
- mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
- }
-
- private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
- SyncResult syncResult) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
- msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
- mSyncHandler.sendMessage(msg);
- }
-
- private void sendCancelSyncsMessage(final Account account, final int userId,
- final String authority) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_CANCEL;
- msg.obj = Pair.create(account, authority);
- msg.arg1 = userId;
- mSyncHandler.sendMessage(msg);
- }
-
- class SyncHandlerMessagePayload {
- public final ActiveSyncContext activeSyncContext;
- public final SyncResult syncResult;
-
- SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
- this.activeSyncContext = syncContext;
- this.syncResult = syncResult;
- }
- }
-
- class SyncAlarmIntentReceiver extends BroadcastReceiver {
- public void onReceive(Context context, Intent intent) {
- mHandleAlarmWakeLock.acquire();
- sendSyncAlarmMessage();
- }
- }
-
- private void clearBackoffSetting(SyncOperation op) {
- mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
- synchronized (mSyncQueue) {
- mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0);
- }
- }
-
- private void increaseBackoffSetting(SyncOperation op) {
- // TODO: Use this function to align it to an already scheduled sync
- // operation in the specified window
- final long now = SystemClock.elapsedRealtime();
-
- final Pair<Long, Long> previousSettings =
- mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority);
- long newDelayInMs = -1;
- if (previousSettings != null) {
- // don't increase backoff before current backoff is expired. This will happen for op's
- // with ignoreBackoff set.
- if (now < previousSettings.first) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Still in backoff, do not increase it. "
- + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds.");
- }
- return;
- }
- // Subsequent delays are the double of the previous delay
- newDelayInMs = previousSettings.second * 2;
- }
- if (newDelayInMs <= 0) {
- // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
- newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
- (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
- }
-
- // Cap the delay
- long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(),
- Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
- DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
- if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
- newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
- }
-
- final long backoff = now + newDelayInMs;
-
- mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
- backoff, newDelayInMs);
-
- op.backoff = backoff;
- op.updateEffectiveRunTime();
-
- synchronized (mSyncQueue) {
- mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff);
- }
- }
-
- private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
- final long delayUntil = delayUntilSeconds * 1000;
- final long absoluteNow = System.currentTimeMillis();
- long newDelayUntilTime;
- if (delayUntil > absoluteNow) {
- newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
- } else {
- newDelayUntilTime = 0;
- }
- mSyncStorageEngine
- .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime);
- synchronized (mSyncQueue) {
- mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
- }
- }
-
- /**
- * Cancel the active sync if it matches the authority and account.
- * @param account limit the cancelations to syncs with this account, if non-null
- * @param authority limit the cancelations to syncs with this authority, if non-null
- */
- public void cancelActiveSync(Account account, int userId, String authority) {
- sendCancelSyncsMessage(account, userId, authority);
- }
-
- /**
- * Create and schedule a SyncOperation.
- *
- * @param syncOperation the SyncOperation to schedule
- */
- public void scheduleSyncOperation(SyncOperation syncOperation) {
- boolean queueChanged;
- synchronized (mSyncQueue) {
- queueChanged = mSyncQueue.add(syncOperation);
- }
-
- if (queueChanged) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
- }
- sendCheckAlarmsMessage();
- } else {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
- + syncOperation);
- }
- }
- }
-
- /**
- * Remove scheduled sync operations.
- * @param account limit the removals to operations with this account, if non-null
- * @param authority limit the removals to operations with this authority, if non-null
- */
- public void clearScheduledSyncOperations(Account account, int userId, String authority) {
- synchronized (mSyncQueue) {
- mSyncQueue.remove(account, userId, authority);
- }
- mSyncStorageEngine.setBackoff(account, userId, authority,
- SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
- }
-
- void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
- boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
- if (isLoggable) {
- Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
- }
-
- operation = new SyncOperation(operation);
-
- // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
- // request. Retries of the request will always honor the backoff, so clear the
- // flag in case we retry this request.
- if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
- operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
- }
-
- // If this sync aborted because the internal sync loop retried too many times then
- // don't reschedule. Otherwise we risk getting into a retry loop.
- // If the operation succeeded to some extent then retry immediately.
- // If this was a two-way sync then retry soft errors with an exponential backoff.
- // If this was an upward sync then schedule a two-way sync immediately.
- // Otherwise do not reschedule.
- if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
- Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
- + operation);
- } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
- && !syncResult.syncAlreadyInProgress) {
- operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
- Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
- + "encountered an error: " + operation);
- scheduleSyncOperation(operation);
- } else if (syncResult.tooManyRetries) {
- Log.d(TAG, "not retrying sync operation because it retried too many times: "
- + operation);
- } else if (syncResult.madeSomeProgress()) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation because even though it had an error "
- + "it achieved some success");
- }
- scheduleSyncOperation(operation);
- } else if (syncResult.syncAlreadyInProgress) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation that failed because there was already a "
- + "sync in progress: " + operation);
- }
- scheduleSyncOperation(new SyncOperation(operation.account, operation.userId,
- operation.syncSource,
- operation.authority, operation.extras,
- DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
- operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
- } else if (syncResult.hasSoftError()) {
- if (isLoggable) {
- Log.d(TAG, "retrying sync operation because it encountered a soft error: "
- + operation);
- }
- scheduleSyncOperation(operation);
- } else {
- Log.d(TAG, "not retrying sync operation because the error is a hard error: "
- + operation);
- }
- }
-
- private void onUserStarting(int userId) {
- // Make sure that accounts we're about to use are valid
- AccountManagerService.getSingleton().validateAccounts(userId);
-
- mSyncAdapters.invalidateCache(userId);
-
- updateRunningAccounts();
-
- synchronized (mSyncQueue) {
- mSyncQueue.addPendingOperations(userId);
- }
-
- // Schedule sync for any accounts under started user
- final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId);
- for (Account account : accounts) {
- scheduleSync(account, userId, null, null, 0 /* no delay */,
- true /* onlyThoseWithUnknownSyncableState */);
- }
-
- sendCheckAlarmsMessage();
- }
-
- private void onUserStopping(int userId) {
- updateRunningAccounts();
-
- cancelActiveSync(
- null /* any account */,
- userId,
- null /* any authority */);
- }
-
- private void onUserRemoved(int userId) {
- updateRunningAccounts();
-
- // Clean up the storage engine database
- mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
- synchronized (mSyncQueue) {
- mSyncQueue.removeUser(userId);
- }
- }
-
- /**
- * @hide
- */
- class ActiveSyncContext extends ISyncContext.Stub
- implements ServiceConnection, IBinder.DeathRecipient {
- final SyncOperation mSyncOperation;
- final long mHistoryRowId;
- ISyncAdapter mSyncAdapter;
- final long mStartTime;
- long mTimeoutStartTime;
- boolean mBound;
- final PowerManager.WakeLock mSyncWakeLock;
- final int mSyncAdapterUid;
- SyncInfo mSyncInfo;
- boolean mIsLinkedToDeath = false;
-
- /**
- * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
- * sync adapter. Since this grabs the wakelock you need to be sure to call
- * close() when you are done with this ActiveSyncContext, whether the sync succeeded
- * or not.
- * @param syncOperation the SyncOperation we are about to sync
- * @param historyRowId the row in which to record the history info for this sync
- * @param syncAdapterUid the UID of the application that contains the sync adapter
- * for this sync. This is used to attribute the wakelock hold to that application.
- */
- public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
- int syncAdapterUid) {
- super();
- mSyncAdapterUid = syncAdapterUid;
- mSyncOperation = syncOperation;
- mHistoryRowId = historyRowId;
- mSyncAdapter = null;
- mStartTime = SystemClock.elapsedRealtime();
- mTimeoutStartTime = mStartTime;
- mSyncWakeLock = mSyncHandler.getSyncWakeLock(
- mSyncOperation.account, mSyncOperation.authority);
- mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
- mSyncWakeLock.acquire();
- }
-
- public void sendHeartbeat() {
- // heartbeats are no longer used
- }
-
- public void onFinished(SyncResult result) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
- // include "this" in the message so that the handler can ignore it if this
- // ActiveSyncContext is no longer the mActiveSyncContext at message handling
- // time
- sendSyncFinishedOrCanceledMessage(this, result);
- }
-
- public void toString(StringBuilder sb) {
- sb.append("startTime ").append(mStartTime)
- .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
- .append(", mHistoryRowId ").append(mHistoryRowId)
- .append(", syncOperation ").append(mSyncOperation);
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
- msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
- mSyncHandler.sendMessage(msg);
- }
-
- public void onServiceDisconnected(ComponentName name) {
- Message msg = mSyncHandler.obtainMessage();
- msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
- msg.obj = new ServiceConnectionData(this, null);
- mSyncHandler.sendMessage(msg);
- }
-
- boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
- }
- Intent intent = new Intent();
- intent.setAction("android.content.SyncAdapter");
- intent.setComponent(info.componentName);
- intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
- com.android.internal.R.string.sync_binding_label);
- intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
- mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0,
- null, new UserHandle(userId)));
- mBound = true;
- final boolean bindResult = mContext.bindService(intent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_ALLOW_OOM_MANAGEMENT,
- mSyncOperation.userId);
- if (!bindResult) {
- mBound = false;
- }
- return bindResult;
- }
-
- /**
- * Performs the required cleanup, which is the releasing of the wakelock and
- * unbinding from the sync adapter (if actually bound).
- */
- protected void close() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
- }
- if (mBound) {
- mBound = false;
- mContext.unbindService(this);
- }
- mSyncWakeLock.release();
- mSyncWakeLock.setWorkSource(null);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- toString(sb);
- return sb.toString();
- }
-
- @Override
- public void binderDied() {
- sendSyncFinishedOrCanceledMessage(this, null);
- }
- }
-
- protected void dump(FileDescriptor fd, PrintWriter pw) {
- final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- dumpSyncState(ipw);
- dumpSyncHistory(ipw);
- dumpSyncAdapters(ipw);
- }
-
- static String formatTime(long time) {
- Time tobj = new Time();
- tobj.set(time);
- return tobj.format("%Y-%m-%d %H:%M:%S");
- }
-
- protected void dumpSyncState(PrintWriter pw) {
- pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
- pw.print("auto sync: ");
- List<UserInfo> users = getAllUsers();
- if (users != null) {
- for (UserInfo user : users) {
- pw.print("u" + user.id + "="
- + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " ");
- }
- pw.println();
- }
- pw.print("memory low: "); pw.println(mStorageIsLow);
-
- final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
-
- pw.print("accounts: ");
- if (accounts != INITIAL_ACCOUNTS_ARRAY) {
- pw.println(accounts.length);
- } else {
- pw.println("not known yet");
- }
- final long now = SystemClock.elapsedRealtime();
- pw.print("now: "); pw.print(now);
- pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
- pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
- pw.println(" (HH:MM:SS)");
- pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
- pw.println(" (HH:MM:SS)");
- pw.print("time spent syncing: ");
- pw.print(DateUtils.formatElapsedTime(
- mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
- pw.print(" (HH:MM:SS), sync ");
- pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
- pw.println("in progress");
- if (mSyncHandler.mAlarmScheduleTime != null) {
- pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
- pw.print(" (");
- pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
- pw.println(" (HH:MM:SS) from now)");
- } else {
- pw.println("no alarm is scheduled (there had better not be any pending syncs)");
- }
-
- pw.print("notification info: ");
- final StringBuilder sb = new StringBuilder();
- mSyncHandler.mSyncNotificationInfo.toString(sb);
- pw.println(sb.toString());
-
- pw.println();
- pw.println("Active Syncs: " + mActiveSyncContexts.size());
- for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
- final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
- pw.print(" ");
- pw.print(DateUtils.formatElapsedTime(durationInSeconds));
- pw.print(" - ");
- pw.print(activeSyncContext.mSyncOperation.dump(false));
- pw.println();
- }
-
- synchronized (mSyncQueue) {
- sb.setLength(0);
- mSyncQueue.dump(sb);
- }
- pw.println();
- pw.print(sb.toString());
-
- // join the installed sync adapter with the accounts list and emit for everything
- pw.println();
- pw.println("Sync Status");
- for (AccountAndUser account : accounts) {
- pw.print(" Account "); pw.print(account.account.name);
- pw.print(" u"); pw.print(account.userId);
- pw.print(" "); pw.print(account.account.type);
- pw.println(":");
- for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType :
- mSyncAdapters.getAllServices(account.userId)) {
- if (!syncAdapterType.type.accountType.equals(account.account.type)) {
- continue;
- }
-
- SyncStorageEngine.AuthorityInfo settings =
- mSyncStorageEngine.getOrCreateAuthority(
- account.account, account.userId, syncAdapterType.type.authority);
- SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
- pw.print(" "); pw.print(settings.authority);
- pw.println(":");
- pw.print(" settings:");
- pw.print(" " + (settings.syncable > 0
- ? "syncable"
- : (settings.syncable == 0 ? "not syncable" : "not initialized")));
- pw.print(", " + (settings.enabled ? "enabled" : "disabled"));
- if (settings.delayUntil > now) {
- pw.print(", delay for "
- + ((settings.delayUntil - now) / 1000) + " sec");
- }
- if (settings.backoffTime > now) {
- pw.print(", backoff for "
- + ((settings.backoffTime - now) / 1000) + " sec");
- }
- if (settings.backoffDelay > 0) {
- pw.print(", the backoff increment is " + settings.backoffDelay / 1000
- + " sec");
- }
- pw.println();
- for (int periodicIndex = 0;
- periodicIndex < settings.periodicSyncs.size();
- periodicIndex++) {
- Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex);
- long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex);
- long nextPeriodicTime = lastPeriodicTime + info.second * 1000;
- pw.println(" periodic period=" + info.second
- + ", extras=" + info.first
- + ", next=" + formatTime(nextPeriodicTime));
- }
- pw.print(" count: local="); pw.print(status.numSourceLocal);
- pw.print(" poll="); pw.print(status.numSourcePoll);
- pw.print(" periodic="); pw.print(status.numSourcePeriodic);
- pw.print(" server="); pw.print(status.numSourceServer);
- pw.print(" user="); pw.print(status.numSourceUser);
- pw.print(" total="); pw.print(status.numSyncs);
- pw.println();
- pw.print(" total duration: ");
- pw.println(DateUtils.formatElapsedTime(status.totalElapsedTime/1000));
- if (status.lastSuccessTime != 0) {
- pw.print(" SUCCESS: source=");
- pw.print(SyncStorageEngine.SOURCES[status.lastSuccessSource]);
- pw.print(" time=");
- pw.println(formatTime(status.lastSuccessTime));
- }
- if (status.lastFailureTime != 0) {
- pw.print(" FAILURE: source=");
- pw.print(SyncStorageEngine.SOURCES[
- status.lastFailureSource]);
- pw.print(" initialTime=");
- pw.print(formatTime(status.initialFailureTime));
- pw.print(" lastTime=");
- pw.println(formatTime(status.lastFailureTime));
- int errCode = status.getLastFailureMesgAsInt(0);
- pw.print(" message: "); pw.println(
- getLastFailureMessage(errCode) + " (" + errCode + ")");
- }
- }
- }
- }
-
- private String getLastFailureMessage(int code) {
- switch (code) {
- case ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS:
- return "sync already in progress";
-
- case ContentResolver.SYNC_ERROR_AUTHENTICATION:
- return "authentication error";
-
- case ContentResolver.SYNC_ERROR_IO:
- return "I/O error";
-
- case ContentResolver.SYNC_ERROR_PARSE:
- return "parse error";
-
- case ContentResolver.SYNC_ERROR_CONFLICT:
- return "conflict error";
-
- case ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS:
- return "too many deletions error";
-
- case ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES:
- return "too many retries error";
-
- case ContentResolver.SYNC_ERROR_INTERNAL:
- return "internal error";
-
- default:
- return "unknown";
- }
- }
-
- private void dumpTimeSec(PrintWriter pw, long time) {
- pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
- pw.print('s');
- }
-
- private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
- pw.print("Success ("); pw.print(ds.successCount);
- if (ds.successCount > 0) {
- pw.print(" for "); dumpTimeSec(pw, ds.successTime);
- pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
- }
- pw.print(") Failure ("); pw.print(ds.failureCount);
- if (ds.failureCount > 0) {
- pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
- pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
- }
- pw.println(")");
- }
-
- protected void dumpSyncHistory(PrintWriter pw) {
- dumpRecentHistory(pw);
- dumpDayStatistics(pw);
- }
-
- private void dumpRecentHistory(PrintWriter pw) {
- final ArrayList<SyncStorageEngine.SyncHistoryItem> items
- = mSyncStorageEngine.getSyncHistory();
- if (items != null && items.size() > 0) {
- final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap();
- long totalElapsedTime = 0;
- long totalTimes = 0;
- final int N = items.size();
-
- int maxAuthority = 0;
- int maxAccount = 0;
- for (SyncStorageEngine.SyncHistoryItem item : items) {
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(item.authorityId);
- final String authorityName;
- final String accountKey;
- if (authority != null) {
- authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type
- + " u" + authority.userId;
- } else {
- authorityName = "Unknown";
- accountKey = "Unknown";
- }
-
- int length = authorityName.length();
- if (length > maxAuthority) {
- maxAuthority = length;
- }
- length = accountKey.length();
- if (length > maxAccount) {
- maxAccount = length;
- }
-
- final long elapsedTime = item.elapsedTime;
- totalElapsedTime += elapsedTime;
- totalTimes++;
- AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName);
- if (authoritySyncStats == null) {
- authoritySyncStats = new AuthoritySyncStats(authorityName);
- authorityMap.put(authorityName, authoritySyncStats);
- }
- authoritySyncStats.elapsedTime += elapsedTime;
- authoritySyncStats.times++;
- final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap;
- AccountSyncStats accountSyncStats = accountMap.get(accountKey);
- if (accountSyncStats == null) {
- accountSyncStats = new AccountSyncStats(accountKey);
- accountMap.put(accountKey, accountSyncStats);
- }
- accountSyncStats.elapsedTime += elapsedTime;
- accountSyncStats.times++;
-
- }
-
- if (totalElapsedTime > 0) {
- pw.println();
- pw.printf("Detailed Statistics (Recent history): "
- + "%d (# of times) %ds (sync time)\n",
- totalTimes, totalElapsedTime / 1000);
-
- final List<AuthoritySyncStats> sortedAuthorities =
- new ArrayList<AuthoritySyncStats>(authorityMap.values());
- Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() {
- @Override
- public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) {
- // reverse order
- int compare = Integer.compare(rhs.times, lhs.times);
- if (compare == 0) {
- compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
- }
- return compare;
- }
- });
-
- final int maxLength = Math.max(maxAuthority, maxAccount + 3);
- final int padLength = 2 + 2 + maxLength + 2 + 10 + 11;
- final char chars[] = new char[padLength];
- Arrays.fill(chars, '-');
- final String separator = new String(chars);
-
- final String authorityFormat =
- String.format(" %%-%ds: %%-9s %%-11s\n", maxLength + 2);
- final String accountFormat =
- String.format(" %%-%ds: %%-9s %%-11s\n", maxLength);
-
- pw.println(separator);
- for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) {
- String name = authoritySyncStats.name;
- long elapsedTime;
- int times;
- String timeStr;
- String timesStr;
-
- elapsedTime = authoritySyncStats.elapsedTime;
- times = authoritySyncStats.times;
- timeStr = String.format("%ds/%d%%",
- elapsedTime / 1000,
- elapsedTime * 100 / totalElapsedTime);
- timesStr = String.format("%d/%d%%",
- times,
- times * 100 / totalTimes);
- pw.printf(authorityFormat, name, timesStr, timeStr);
-
- final List<AccountSyncStats> sortedAccounts =
- new ArrayList<AccountSyncStats>(
- authoritySyncStats.accountMap.values());
- Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() {
- @Override
- public int compare(AccountSyncStats lhs, AccountSyncStats rhs) {
- // reverse order
- int compare = Integer.compare(rhs.times, lhs.times);
- if (compare == 0) {
- compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
- }
- return compare;
- }
- });
- for (AccountSyncStats stats: sortedAccounts) {
- elapsedTime = stats.elapsedTime;
- times = stats.times;
- timeStr = String.format("%ds/%d%%",
- elapsedTime / 1000,
- elapsedTime * 100 / totalElapsedTime);
- timesStr = String.format("%d/%d%%",
- times,
- times * 100 / totalTimes);
- pw.printf(accountFormat, stats.name, timesStr, timeStr);
- }
- pw.println(separator);
- }
- }
-
- pw.println();
- pw.println("Recent Sync History");
- final String format = " %-" + maxAccount + "s %s\n";
- final Map<String, Long> lastTimeMap = Maps.newHashMap();
-
- for (int i = 0; i < N; i++) {
- SyncStorageEngine.SyncHistoryItem item = items.get(i);
- SyncStorageEngine.AuthorityInfo authority
- = mSyncStorageEngine.getAuthority(item.authorityId);
- final String authorityName;
- final String accountKey;
- if (authority != null) {
- authorityName = authority.authority;
- accountKey = authority.account.name + "/" + authority.account.type
- + " u" + authority.userId;
- } else {
- authorityName = "Unknown";
- accountKey = "Unknown";
- }
- final long elapsedTime = item.elapsedTime;
- final Time time = new Time();
- final long eventTime = item.eventTime;
- time.set(eventTime);
-
- final String key = authorityName + "/" + accountKey;
- final Long lastEventTime = lastTimeMap.get(key);
- final String diffString;
- if (lastEventTime == null) {
- diffString = "";
- } else {
- final long diff = (lastEventTime - eventTime) / 1000;
- if (diff < 60) {
- diffString = String.valueOf(diff);
- } else if (diff < 3600) {
- diffString = String.format("%02d:%02d", diff / 60, diff % 60);
- } else {
- final long sec = diff % 3600;
- diffString = String.format("%02d:%02d:%02d",
- diff / 3600, sec / 60, sec % 60);
- }
- }
- lastTimeMap.put(key, eventTime);
-
- pw.printf(" #%-3d: %s %8s %5.1fs %8s",
- i + 1,
- formatTime(eventTime),
- SyncStorageEngine.SOURCES[item.source],
- ((float) elapsedTime) / 1000,
- diffString);
- pw.printf(format, accountKey, authorityName);
-
- if (item.event != SyncStorageEngine.EVENT_STOP
- || item.upstreamActivity != 0
- || item.downstreamActivity != 0) {
- pw.printf(" event=%d upstreamActivity=%d downstreamActivity=%d\n",
- item.event,
- item.upstreamActivity,
- item.downstreamActivity);
- }
- if (item.mesg != null
- && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
- pw.printf(" mesg=%s\n", item.mesg);
- }
- }
- }
- }
-
- private void dumpDayStatistics(PrintWriter pw) {
- SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
- if (dses != null && dses[0] != null) {
- pw.println();
- pw.println("Sync Statistics");
- pw.print(" Today: "); dumpDayStatistic(pw, dses[0]);
- int today = dses[0].day;
- int i;
- SyncStorageEngine.DayStats ds;
-
- // Print each day in the current week.
- for (i=1; i<=6 && i < dses.length; i++) {
- ds = dses[i];
- if (ds == null) break;
- int delta = today-ds.day;
- if (delta > 6) break;
-
- pw.print(" Day-"); pw.print(delta); pw.print(": ");
- dumpDayStatistic(pw, ds);
- }
-
- // Aggregate all following days into weeks and print totals.
- int weekDay = today;
- while (i < dses.length) {
- SyncStorageEngine.DayStats aggr = null;
- weekDay -= 7;
- while (i < dses.length) {
- ds = dses[i];
- if (ds == null) {
- i = dses.length;
- break;
- }
- int delta = weekDay-ds.day;
- if (delta > 6) break;
- i++;
-
- if (aggr == null) {
- aggr = new SyncStorageEngine.DayStats(weekDay);
- }
- aggr.successCount += ds.successCount;
- aggr.successTime += ds.successTime;
- aggr.failureCount += ds.failureCount;
- aggr.failureTime += ds.failureTime;
- }
- if (aggr != null) {
- pw.print(" Week-"); pw.print((today-weekDay)/7); pw.print(": ");
- dumpDayStatistic(pw, aggr);
- }
- }
- }
- }
-
- private void dumpSyncAdapters(IndentingPrintWriter pw) {
- pw.println();
- final List<UserInfo> users = getAllUsers();
- if (users != null) {
- for (UserInfo user : users) {
- pw.println("Sync adapters for " + user + ":");
- pw.increaseIndent();
- for (RegisteredServicesCache.ServiceInfo<?> info :
- mSyncAdapters.getAllServices(user.id)) {
- pw.println(info);
- }
- pw.decreaseIndent();
- pw.println();
- }
- }
- }
-
- private static class AuthoritySyncStats {
- String name;
- long elapsedTime;
- int times;
- Map<String, AccountSyncStats> accountMap = Maps.newHashMap();
-
- private AuthoritySyncStats(String name) {
- this.name = name;
- }
- }
-
- private static class AccountSyncStats {
- String name;
- long elapsedTime;
- int times;
-
- private AccountSyncStats(String name) {
- this.name = name;
- }
- }
-
- /**
- * A helper object to keep track of the time we have spent syncing since the last boot
- */
- private class SyncTimeTracker {
- /** True if a sync was in progress on the most recent call to update() */
- boolean mLastWasSyncing = false;
- /** Used to track when lastWasSyncing was last set */
- long mWhenSyncStarted = 0;
- /** The cumulative time we have spent syncing */
- private long mTimeSpentSyncing;
-
- /** Call to let the tracker know that the sync state may have changed */
- public synchronized void update() {
- final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
- if (isSyncInProgress == mLastWasSyncing) return;
- final long now = SystemClock.elapsedRealtime();
- if (isSyncInProgress) {
- mWhenSyncStarted = now;
- } else {
- mTimeSpentSyncing += now - mWhenSyncStarted;
- }
- mLastWasSyncing = isSyncInProgress;
- }
-
- /** Get how long we have been syncing, in ms */
- public synchronized long timeSpentSyncing() {
- if (!mLastWasSyncing) return mTimeSpentSyncing;
-
- final long now = SystemClock.elapsedRealtime();
- return mTimeSpentSyncing + (now - mWhenSyncStarted);
- }
- }
-
- class ServiceConnectionData {
- public final ActiveSyncContext activeSyncContext;
- public final ISyncAdapter syncAdapter;
- ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
- this.activeSyncContext = activeSyncContext;
- this.syncAdapter = syncAdapter;
- }
- }
-
- /**
- * Handles SyncOperation Messages that are posted to the associated
- * HandlerThread.
- */
- class SyncHandler extends Handler {
- // Messages that can be sent on mHandler
- private static final int MESSAGE_SYNC_FINISHED = 1;
- private static final int MESSAGE_SYNC_ALARM = 2;
- private static final int MESSAGE_CHECK_ALARMS = 3;
- private static final int MESSAGE_SERVICE_CONNECTED = 4;
- private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
- private static final int MESSAGE_CANCEL = 6;
-
- public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
- private Long mAlarmScheduleTime = null;
- public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
- private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks =
- Maps.newHashMap();
-
- private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
-
- public void onBootCompleted() {
- mBootCompleted = true;
-
- doDatabaseCleanup();
-
- if (mReadyToRunLatch != null) {
- mReadyToRunLatch.countDown();
- }
- }
-
- private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) {
- final Pair<Account, String> wakeLockKey = Pair.create(account, authority);
- PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
- if (wakeLock == null) {
- final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + account;
- wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
- wakeLock.setReferenceCounted(false);
- mWakeLocks.put(wakeLockKey, wakeLock);
- }
- return wakeLock;
- }
-
- private void waitUntilReadyToRun() {
- CountDownLatch latch = mReadyToRunLatch;
- if (latch != null) {
- while (true) {
- try {
- latch.await();
- mReadyToRunLatch = null;
- return;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- }
- }
- /**
- * Used to keep track of whether a sync notification is active and who it is for.
- */
- class SyncNotificationInfo {
- // true iff the notification manager has been asked to send the notification
- public boolean isActive = false;
-
- // Set when we transition from not running a sync to running a sync, and cleared on
- // the opposite transition.
- public Long startTime = null;
-
- public void toString(StringBuilder sb) {
- sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- toString(sb);
- return sb.toString();
- }
- }
-
- public SyncHandler(Looper looper) {
- super(looper);
- }
-
- public void handleMessage(Message msg) {
- long earliestFuturePollTime = Long.MAX_VALUE;
- long nextPendingSyncTime = Long.MAX_VALUE;
-
- // Setting the value here instead of a method because we want the dumpsys logs
- // to have the most recent value used.
- try {
- waitUntilReadyToRun();
- mDataConnectionIsConnected = readDataConnectionState();
- mSyncManagerWakeLock.acquire();
- // Always do this first so that we be sure that any periodic syncs that
- // are ready to run have been converted into pending syncs. This allows the
- // logic that considers the next steps to take based on the set of pending syncs
- // to also take into account the periodic syncs.
- earliestFuturePollTime = scheduleReadyPeriodicSyncs();
- switch (msg.what) {
- case SyncHandler.MESSAGE_CANCEL: {
- Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
- + payload.first + ", " + payload.second);
- }
- cancelActiveSyncLocked(payload.first, msg.arg1, payload.second);
- nextPendingSyncTime = maybeStartNextSyncLocked();
- break;
- }
-
- case SyncHandler.MESSAGE_SYNC_FINISHED:
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
- }
- SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
- if (!isSyncStillActive(payload.activeSyncContext)) {
- Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
- + "sync is no longer active: "
- + payload.activeSyncContext);
- break;
- }
- runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
-
- // since a sync just finished check if it is time to start a new sync
- nextPendingSyncTime = maybeStartNextSyncLocked();
- break;
-
- case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
- ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
- + msgData.activeSyncContext);
- }
- // check that this isn't an old message
- if (isSyncStillActive(msgData.activeSyncContext)) {
- runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
- }
- break;
- }
-
- case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
- final ActiveSyncContext currentSyncContext =
- ((ServiceConnectionData)msg.obj).activeSyncContext;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
- + currentSyncContext);
- }
- // check that this isn't an old message
- if (isSyncStillActive(currentSyncContext)) {
- // cancel the sync if we have a syncadapter, which means one is
- // outstanding
- if (currentSyncContext.mSyncAdapter != null) {
- try {
- currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
- } catch (RemoteException e) {
- // we don't need to retry this in this case
- }
- }
-
- // pretend that the sync failed with an IOException,
- // which is a soft error
- SyncResult syncResult = new SyncResult();
- syncResult.stats.numIoExceptions++;
- runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
-
- // since a sync just finished check if it is time to start a new sync
- nextPendingSyncTime = maybeStartNextSyncLocked();
- }
-
- break;
- }
-
- case SyncHandler.MESSAGE_SYNC_ALARM: {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) {
- Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
- }
- mAlarmScheduleTime = null;
- try {
- nextPendingSyncTime = maybeStartNextSyncLocked();
- } finally {
- mHandleAlarmWakeLock.release();
- }
- break;
- }
-
- case SyncHandler.MESSAGE_CHECK_ALARMS:
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
- }
- nextPendingSyncTime = maybeStartNextSyncLocked();
- break;
- }
- } finally {
- manageSyncNotificationLocked();
- manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
- mSyncTimeTracker.update();
- mSyncManagerWakeLock.release();
- }
- }
-
- /**
- * Turn any periodic sync operations that are ready to run into pending sync operations.
- * @return the desired start time of the earliest future periodic sync operation,
- * in milliseconds since boot
- */
- private long scheduleReadyPeriodicSyncs() {
- final boolean backgroundDataUsageAllowed =
- getConnectivityManager().getBackgroundDataSetting();
- long earliestFuturePollTime = Long.MAX_VALUE;
- if (!backgroundDataUsageAllowed) {
- return earliestFuturePollTime;
- }
-
- AccountAndUser[] accounts = mRunningAccounts;
-
- final long nowAbsolute = System.currentTimeMillis();
- final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
- ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
-
- ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
- for (SyncStorageEngine.AuthorityInfo info : infos) {
- // skip the sync if the account of this operation no longer exists
- if (!containsAccountAndUser(accounts, info.account, info.userId)) {
- continue;
- }
-
- if (!mSyncStorageEngine.getMasterSyncAutomatically(info.userId)
- || !mSyncStorageEngine.getSyncAutomatically(info.account, info.userId,
- info.authority)) {
- continue;
- }
-
- if (mSyncStorageEngine.getIsSyncable(info.account, info.userId, info.authority)
- == 0) {
- continue;
- }
-
- SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
- for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
- final Bundle extras = info.periodicSyncs.get(i).first;
- final Long periodInMillis = info.periodicSyncs.get(i).second * 1000;
- // find when this periodic sync was last scheduled to run
- final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
-
- long remainingMillis
- = periodInMillis - (shiftedNowAbsolute % periodInMillis);
-
- /*
- * Sync scheduling strategy:
- * Set the next periodic sync based on a random offset (in seconds).
- *
- * Also sync right now if any of the following cases hold
- * and mark it as having been scheduled
- *
- * Case 1: This sync is ready to run now.
- * Case 2: If the lastPollTimeAbsolute is in the future,
- * sync now and reinitialize. This can happen for
- * example if the user changed the time, synced and
- * changed back.
- * Case 3: If we failed to sync at the last scheduled time
- */
- if (remainingMillis == periodInMillis // Case 1
- || lastPollTimeAbsolute > nowAbsolute // Case 2
- || (nowAbsolute - lastPollTimeAbsolute
- >= periodInMillis)) { // Case 3
- // Sync now
- final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
- info.account, info.userId, info.authority);
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(info.authority, info.account.type),
- info.userId);
- if (syncAdapterInfo == null) {
- continue;
- }
- scheduleSyncOperation(
- new SyncOperation(info.account, info.userId,
- SyncStorageEngine.SOURCE_PERIODIC,
- info.authority, extras, 0 /* delay */,
- backoff != null ? backoff.first : 0,
- mSyncStorageEngine.getDelayUntilTime(
- info.account, info.userId, info.authority),
- syncAdapterInfo.type.allowParallelSyncs()));
- status.setPeriodicSyncTime(i, nowAbsolute);
- }
- // Compute when this periodic sync should next run
- final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
-
- // remember this time if it is earlier than earliestFuturePollTime
- if (nextPollTimeAbsolute < earliestFuturePollTime) {
- earliestFuturePollTime = nextPollTimeAbsolute;
- }
- }
- }
-
- if (earliestFuturePollTime == Long.MAX_VALUE) {
- return Long.MAX_VALUE;
- }
-
- // convert absolute time to elapsed time
- return SystemClock.elapsedRealtime()
- + ((earliestFuturePollTime < nowAbsolute)
- ? 0
- : (earliestFuturePollTime - nowAbsolute));
- }
-
- private long maybeStartNextSyncLocked() {
- final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- if (isLoggable) Log.v(TAG, "maybeStartNextSync");
-
- // If we aren't ready to run (e.g. the data connection is down), get out.
- if (!mDataConnectionIsConnected) {
- if (isLoggable) {
- Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
- }
- return Long.MAX_VALUE;
- }
-
- if (mStorageIsLow) {
- if (isLoggable) {
- Log.v(TAG, "maybeStartNextSync: memory low, skipping");
- }
- return Long.MAX_VALUE;
- }
-
- // If the accounts aren't known yet then we aren't ready to run. We will be kicked
- // when the account lookup request does complete.
- AccountAndUser[] accounts = mRunningAccounts;
- if (accounts == INITIAL_ACCOUNTS_ARRAY) {
- if (isLoggable) {
- Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
- }
- return Long.MAX_VALUE;
- }
-
- // Otherwise consume SyncOperations from the head of the SyncQueue until one is
- // found that is runnable (not disabled, etc). If that one is ready to run then
- // start it, otherwise just get out.
- final boolean backgroundDataUsageAllowed =
- getConnectivityManager().getBackgroundDataSetting();
-
- final long now = SystemClock.elapsedRealtime();
-
- // will be set to the next time that a sync should be considered for running
- long nextReadyToRunTime = Long.MAX_VALUE;
-
- // order the sync queue, dropping syncs that are not allowed
- ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
- synchronized (mSyncQueue) {
- if (isLoggable) {
- Log.v(TAG, "build the operation array, syncQueue size is "
- + mSyncQueue.getOperations().size());
- }
- final Iterator<SyncOperation> operationIterator = mSyncQueue.getOperations()
- .iterator();
-
- final ActivityManager activityManager
- = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
- final Set<Integer> removedUsers = Sets.newHashSet();
- while (operationIterator.hasNext()) {
- final SyncOperation op = operationIterator.next();
-
- // drop the sync if the account of this operation no longer exists
- if (!containsAccountAndUser(accounts, op.account, op.userId)) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- continue;
- }
-
- // drop this sync request if it isn't syncable
- int syncableState = mSyncStorageEngine.getIsSyncable(
- op.account, op.userId, op.authority);
- if (syncableState == 0) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- continue;
- }
-
- // if the user in not running, drop the request
- if (!activityManager.isUserRunning(op.userId)) {
- final UserInfo userInfo = mUserManager.getUserInfo(op.userId);
- if (userInfo == null) {
- removedUsers.add(op.userId);
- }
- continue;
- }
-
- // if the next run time is in the future, meaning there are no syncs ready
- // to run, return the time
- if (op.effectiveRunTime > now) {
- if (nextReadyToRunTime > op.effectiveRunTime) {
- nextReadyToRunTime = op.effectiveRunTime;
- }
- continue;
- }
-
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
-
- // only proceed if network is connected for requesting UID
- final boolean uidNetworkConnected;
- if (syncAdapterInfo != null) {
- final NetworkInfo networkInfo = getConnectivityManager()
- .getActiveNetworkInfoForUid(syncAdapterInfo.uid);
- uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
- } else {
- uidNetworkConnected = false;
- }
-
- // skip the sync if it isn't manual, and auto sync or
- // background data usage is disabled or network is
- // disconnected for the target UID.
- if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
- && (syncableState > 0)
- && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId)
- || !backgroundDataUsageAllowed
- || !uidNetworkConnected
- || !mSyncStorageEngine.getSyncAutomatically(
- op.account, op.userId, op.authority))) {
- operationIterator.remove();
- mSyncStorageEngine.deleteFromPending(op.pendingOperation);
- continue;
- }
-
- operations.add(op);
- }
- for (Integer user : removedUsers) {
- // if it's still removed
- if (mUserManager.getUserInfo(user) == null) {
- onUserRemoved(user);
- }
- }
- }
-
- // find the next operation to dispatch, if one is ready
- // iterate from the top, keep issuing (while potentially cancelling existing syncs)
- // until the quotas are filled.
- // once the quotas are filled iterate once more to find when the next one would be
- // (also considering pre-emption reasons).
- if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
- Collections.sort(operations);
- if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
- for (int i = 0, N = operations.size(); i < N; i++) {
- final SyncOperation candidate = operations.get(i);
- final boolean candidateIsInitialization = candidate.isInitialization();
-
- int numInit = 0;
- int numRegular = 0;
- ActiveSyncContext conflict = null;
- ActiveSyncContext longRunning = null;
- ActiveSyncContext toReschedule = null;
- ActiveSyncContext oldestNonExpeditedRegular = null;
-
- for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
- final SyncOperation activeOp = activeSyncContext.mSyncOperation;
- if (activeOp.isInitialization()) {
- numInit++;
- } else {
- numRegular++;
- if (!activeOp.isExpedited()) {
- if (oldestNonExpeditedRegular == null
- || (oldestNonExpeditedRegular.mStartTime
- > activeSyncContext.mStartTime)) {
- oldestNonExpeditedRegular = activeSyncContext;
- }
- }
- }
- if (activeOp.account.type.equals(candidate.account.type)
- && activeOp.authority.equals(candidate.authority)
- && activeOp.userId == candidate.userId
- && (!activeOp.allowParallelSyncs
- || activeOp.account.name.equals(candidate.account.name))) {
- conflict = activeSyncContext;
- // don't break out since we want to do a full count of the varieties
- } else {
- if (candidateIsInitialization == activeOp.isInitialization()
- && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
- longRunning = activeSyncContext;
- // don't break out since we want to do a full count of the varieties
- }
- }
- }
-
- if (isLoggable) {
- Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
- Log.v(TAG, " numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
- Log.v(TAG, " longRunning: " + longRunning);
- Log.v(TAG, " conflict: " + conflict);
- Log.v(TAG, " oldestNonExpeditedRegular: " + oldestNonExpeditedRegular);
- }
-
- final boolean roomAvailable = candidateIsInitialization
- ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
- : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
-
- if (conflict != null) {
- if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
- && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
- toReschedule = conflict;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since an initialization "
- + "takes higher priority, " + conflict);
- }
- } else if (candidate.expedited && !conflict.mSyncOperation.expedited
- && (candidateIsInitialization
- == conflict.mSyncOperation.isInitialization())) {
- toReschedule = conflict;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since an expedited "
- + "takes higher priority, " + conflict);
- }
- } else {
- continue;
- }
- } else if (roomAvailable) {
- // dispatch candidate
- } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null
- && !candidateIsInitialization) {
- // We found an active, non-expedited regular sync. We also know that the
- // candidate doesn't conflict with this active sync since conflict
- // is null. Reschedule the active sync and start the candidate.
- toReschedule = oldestNonExpeditedRegular;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, "
- + oldestNonExpeditedRegular);
- }
- } else if (longRunning != null
- && (candidateIsInitialization
- == longRunning.mSyncOperation.isInitialization())) {
- // We found an active, long-running sync. Reschedule the active
- // sync and start the candidate.
- toReschedule = longRunning;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
- + longRunning);
- }
- } else {
- // we were unable to find or make space to run this candidate, go on to
- // the next one
- continue;
- }
-
- if (toReschedule != null) {
- runSyncFinishedOrCanceledLocked(null, toReschedule);
- scheduleSyncOperation(toReschedule.mSyncOperation);
- }
- synchronized (mSyncQueue) {
- mSyncQueue.remove(candidate);
- }
- dispatchSyncOperation(candidate);
- }
-
- return nextReadyToRunTime;
- }
-
- private boolean dispatchSyncOperation(SyncOperation op) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
- Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
- for (ActiveSyncContext syncContext : mActiveSyncContexts) {
- Log.v(TAG, syncContext.toString());
- }
- }
-
- // connect to the sync adapter
- SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
- final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
- syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId);
- if (syncAdapterInfo == null) {
- Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
- + ", removing settings for it");
- mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority);
- return false;
- }
-
- ActiveSyncContext activeSyncContext =
- new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
- activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
- mActiveSyncContexts.add(activeSyncContext);
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
- }
- if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) {
- Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
- closeActiveSyncContext(activeSyncContext);
- return false;
- }
-
- return true;
- }
-
- private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
- ISyncAdapter syncAdapter) {
- activeSyncContext.mSyncAdapter = syncAdapter;
- final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
- try {
- activeSyncContext.mIsLinkedToDeath = true;
- syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);
-
- syncAdapter.startSync(activeSyncContext, syncOperation.authority,
- syncOperation.account, syncOperation.extras);
- } catch (RemoteException remoteExc) {
- Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
- closeActiveSyncContext(activeSyncContext);
- increaseBackoffSetting(syncOperation);
- scheduleSyncOperation(new SyncOperation(syncOperation));
- } catch (RuntimeException exc) {
- closeActiveSyncContext(activeSyncContext);
- Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
- }
- }
-
- private void cancelActiveSyncLocked(Account account, int userId, String authority) {
- ArrayList<ActiveSyncContext> activeSyncs =
- new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
- for (ActiveSyncContext activeSyncContext : activeSyncs) {
- if (activeSyncContext != null) {
- // if an account was specified then only cancel the sync if it matches
- if (account != null) {
- if (!account.equals(activeSyncContext.mSyncOperation.account)) {
- continue;
- }
- }
- // if an authority was specified then only cancel the sync if it matches
- if (authority != null) {
- if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
- continue;
- }
- }
- // check if the userid matches
- if (userId != UserHandle.USER_ALL
- && userId != activeSyncContext.mSyncOperation.userId) {
- continue;
- }
- runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
- activeSyncContext);
- }
- }
- }
-
- private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
- ActiveSyncContext activeSyncContext) {
- boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
-
- if (activeSyncContext.mIsLinkedToDeath) {
- activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
- activeSyncContext.mIsLinkedToDeath = false;
- }
- closeActiveSyncContext(activeSyncContext);
-
- final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
-
- final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
-
- String historyMessage;
- int downstreamActivity;
- int upstreamActivity;
- if (syncResult != null) {
- if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
- + syncOperation + ", result " + syncResult);
- }
-
- if (!syncResult.hasError()) {
- historyMessage = SyncStorageEngine.MESG_SUCCESS;
- // TODO: set these correctly when the SyncResult is extended to include it
- downstreamActivity = 0;
- upstreamActivity = 0;
- clearBackoffSetting(syncOperation);
- } else {
- Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
- // the operation failed so increase the backoff time
- if (!syncResult.syncAlreadyInProgress) {
- increaseBackoffSetting(syncOperation);
- }
- // reschedule the sync if so indicated by the syncResult
- maybeRescheduleSync(syncResult, syncOperation);
- historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
- // TODO: set these correctly when the SyncResult is extended to include it
- downstreamActivity = 0;
- upstreamActivity = 0;
- }
-
- setDelayUntilTime(syncOperation, syncResult.delayUntil);
- } else {
- if (isLoggable) {
- Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
- }
- if (activeSyncContext.mSyncAdapter != null) {
- try {
- activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
- } catch (RemoteException e) {
- // we don't need to retry this in this case
- }
- }
- historyMessage = SyncStorageEngine.MESG_CANCELED;
- downstreamActivity = 0;
- upstreamActivity = 0;
- }
-
- stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
- upstreamActivity, downstreamActivity, elapsedTime);
-
- if (syncResult != null && syncResult.tooManyDeletions) {
- installHandleTooManyDeletesNotification(syncOperation.account,
- syncOperation.authority, syncResult.stats.numDeletes,
- syncOperation.userId);
- } else {
- mNotificationMgr.cancelAsUser(null,
- syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(),
- new UserHandle(syncOperation.userId));
- }
-
- if (syncResult != null && syncResult.fullSyncRequested) {
- scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId,
- syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
- syncOperation.backoff, syncOperation.delayUntil,
- syncOperation.allowParallelSyncs));
- }
- // no need to schedule an alarm, as that will be done by our caller.
- }
-
- private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
- activeSyncContext.close();
- mActiveSyncContexts.remove(activeSyncContext);
- mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
- activeSyncContext.mSyncOperation.userId);
- }
-
- /**
- * Convert the error-containing SyncResult into the Sync.History error number. Since
- * the SyncResult may indicate multiple errors at once, this method just returns the
- * most "serious" error.
- * @param syncResult the SyncResult from which to read
- * @return the most "serious" error set in the SyncResult
- * @throws IllegalStateException if the SyncResult does not indicate any errors.
- * If SyncResult.error() is true then it is safe to call this.
- */
- private int syncResultToErrorNumber(SyncResult syncResult) {
- if (syncResult.syncAlreadyInProgress)
- return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
- if (syncResult.stats.numAuthExceptions > 0)
- return ContentResolver.SYNC_ERROR_AUTHENTICATION;
- if (syncResult.stats.numIoExceptions > 0)
- return ContentResolver.SYNC_ERROR_IO;
- if (syncResult.stats.numParseExceptions > 0)
- return ContentResolver.SYNC_ERROR_PARSE;
- if (syncResult.stats.numConflictDetectedExceptions > 0)
- return ContentResolver.SYNC_ERROR_CONFLICT;
- if (syncResult.tooManyDeletions)
- return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
- if (syncResult.tooManyRetries)
- return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
- if (syncResult.databaseError)
- return ContentResolver.SYNC_ERROR_INTERNAL;
- throw new IllegalStateException("we are not in an error state, " + syncResult);
- }
-
- private void manageSyncNotificationLocked() {
- boolean shouldCancel;
- boolean shouldInstall;
-
- if (mActiveSyncContexts.isEmpty()) {
- mSyncNotificationInfo.startTime = null;
-
- // we aren't syncing. if the notification is active then remember that we need
- // to cancel it and then clear out the info
- shouldCancel = mSyncNotificationInfo.isActive;
- shouldInstall = false;
- } else {
- // we are syncing
- final long now = SystemClock.elapsedRealtime();
- if (mSyncNotificationInfo.startTime == null) {
- mSyncNotificationInfo.startTime = now;
- }
-
- // there are three cases:
- // - the notification is up: do nothing
- // - the notification is not up but it isn't time yet: don't install
- // - the notification is not up and it is time: need to install
-
- if (mSyncNotificationInfo.isActive) {
- shouldInstall = shouldCancel = false;
- } else {
- // it isn't currently up, so there is nothing to cancel
- shouldCancel = false;
-
- final boolean timeToShowNotification =
- now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
- if (timeToShowNotification) {
- shouldInstall = true;
- } else {
- // show the notification immediately if this is a manual sync
- shouldInstall = false;
- for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
- final boolean manualSync = activeSyncContext.mSyncOperation.extras
- .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
- if (manualSync) {
- shouldInstall = true;
- break;
- }
- }
- }
- }
- }
-
- if (shouldCancel && !shouldInstall) {
- mNeedSyncActiveNotification = false;
- sendSyncStateIntent();
- mSyncNotificationInfo.isActive = false;
- }
-
- if (shouldInstall) {
- mNeedSyncActiveNotification = true;
- sendSyncStateIntent();
- mSyncNotificationInfo.isActive = true;
- }
- }
-
- private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
- long nextPendingEventElapsedTime) {
- // in each of these cases the sync loop will be kicked, which will cause this
- // method to be called again
- if (!mDataConnectionIsConnected) return;
- if (mStorageIsLow) return;
-
- // When the status bar notification should be raised
- final long notificationTime =
- (!mSyncHandler.mSyncNotificationInfo.isActive
- && mSyncHandler.mSyncNotificationInfo.startTime != null)
- ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
- : Long.MAX_VALUE;
-
- // When we should consider canceling an active sync
- long earliestTimeoutTime = Long.MAX_VALUE;
- for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
- final long currentSyncTimeoutTime =
- currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
- + currentSyncTimeoutTime);
- }
- if (earliestTimeoutTime > currentSyncTimeoutTime) {
- earliestTimeoutTime = currentSyncTimeoutTime;
- }
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
- }
-
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
- + nextPeriodicEventElapsedTime);
- }
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
- + nextPendingEventElapsedTime);
- }
-
- long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
- alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
- alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
-
- // Bound the alarm time.
- final long now = SystemClock.elapsedRealtime();
- if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
- + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
- }
- alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
- } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
- + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
- }
- alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
- }
-
- // determine if we need to set or cancel the alarm
- boolean shouldSet = false;
- boolean shouldCancel = false;
- final boolean alarmIsActive = mAlarmScheduleTime != null;
- final boolean needAlarm = alarmTime != Long.MAX_VALUE;
- if (needAlarm) {
- if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
- shouldSet = true;
- }
- } else {
- shouldCancel = alarmIsActive;
- }
-
- // set or cancel the alarm as directed
- ensureAlarmService();
- if (shouldSet) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
- + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
- + " secs from now");
- }
- mAlarmScheduleTime = alarmTime;
- mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
- mSyncAlarmIntent);
- } else if (shouldCancel) {
- mAlarmScheduleTime = null;
- mAlarmService.cancel(mSyncAlarmIntent);
- }
- }
-
- private void sendSyncStateIntent() {
- Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
- syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
- syncStateIntent.putExtra("failing", false);
- mContext.sendBroadcastAsUser(syncStateIntent, UserHandle.OWNER);
- }
-
- private void installHandleTooManyDeletesNotification(Account account, String authority,
- long numDeletes, int userId) {
- if (mNotificationMgr == null) return;
-
- final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
- authority, 0 /* flags */);
- if (providerInfo == null) {
- return;
- }
- CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
-
- Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class);
- clickIntent.putExtra("account", account);
- clickIntent.putExtra("authority", authority);
- clickIntent.putExtra("provider", authorityName.toString());
- clickIntent.putExtra("numDeletes", numDeletes);
-
- if (!isActivityAvailable(clickIntent)) {
- Log.w(TAG, "No activity found to handle too many deletes.");
- return;
- }
-
- final PendingIntent pendingIntent = PendingIntent
- .getActivityAsUser(mContext, 0, clickIntent,
- PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId));
-
- CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
- R.string.contentServiceTooManyDeletesNotificationDesc);
-
- Notification notification =
- new Notification(R.drawable.stat_notify_sync_error,
- mContext.getString(R.string.contentServiceSync),
- System.currentTimeMillis());
- notification.setLatestEventInfo(mContext,
- mContext.getString(R.string.contentServiceSyncNotificationTitle),
- String.format(tooManyDeletesDescFormat.toString(), authorityName),
- pendingIntent);
- notification.flags |= Notification.FLAG_ONGOING_EVENT;
- mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(),
- notification, new UserHandle(userId));
- }
-
- /**
- * Checks whether an activity exists on the system image for the given intent.
- *
- * @param intent The intent for an activity.
- * @return Whether or not an activity exists.
- */
- private boolean isActivityAvailable(Intent intent) {
- PackageManager pm = mContext.getPackageManager();
- List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
- int listSize = list.size();
- for (int i = 0; i < listSize; i++) {
- ResolveInfo resolveInfo = list.get(i);
- if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
- != 0) {
- return true;
- }
- }
-
- return false;
- }
-
- public long insertStartSyncEvent(SyncOperation syncOperation) {
- final int source = syncOperation.syncSource;
- final long now = System.currentTimeMillis();
-
- EventLog.writeEvent(2720, syncOperation.authority,
- SyncStorageEngine.EVENT_START, source,
- syncOperation.account.name.hashCode());
-
- return mSyncStorageEngine.insertStartSyncEvent(
- syncOperation.account, syncOperation.userId, syncOperation.authority,
- now, source, syncOperation.isInitialization());
- }
-
- public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
- int upstreamActivity, int downstreamActivity, long elapsedTime) {
- EventLog.writeEvent(2720, syncOperation.authority,
- SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
- syncOperation.account.name.hashCode());
-
- mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
- resultMessage, downstreamActivity, upstreamActivity);
- }
- }
-
- private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
- for (ActiveSyncContext sync : mActiveSyncContexts) {
- if (sync == activeSyncContext) {
- return true;
- }
- }
- return false;
- }
-}
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
deleted file mode 100644
index 6611fcd..0000000
--- a/core/java/android/content/SyncOperation.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.accounts.Account;
-import android.os.Bundle;
-import android.os.SystemClock;
-
-/**
- * Value type that represents a sync operation.
- * @hide
- */
-public class SyncOperation implements Comparable {
- public final Account account;
- public final int userId;
- public int syncSource;
- public String authority;
- public final boolean allowParallelSyncs;
- public Bundle extras;
- public final String key;
- public long earliestRunTime;
- public boolean expedited;
- public SyncStorageEngine.PendingOperation pendingOperation;
- public Long backoff;
- public long delayUntil;
- public long effectiveRunTime;
-
- public SyncOperation(Account account, int userId, int source, String authority, Bundle extras,
- long delayInMs, long backoff, long delayUntil, boolean allowParallelSyncs) {
- this.account = account;
- this.userId = userId;
- this.syncSource = source;
- this.authority = authority;
- this.allowParallelSyncs = allowParallelSyncs;
- this.extras = new Bundle(extras);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
- this.delayUntil = delayUntil;
- this.backoff = backoff;
- final long now = SystemClock.elapsedRealtime();
- if (delayInMs < 0) {
- this.expedited = true;
- this.earliestRunTime = now;
- } else {
- this.expedited = false;
- this.earliestRunTime = now + delayInMs;
- }
- updateEffectiveRunTime();
- this.key = toKey();
- }
-
- private void removeFalseExtra(String extraName) {
- if (!extras.getBoolean(extraName, false)) {
- extras.remove(extraName);
- }
- }
-
- SyncOperation(SyncOperation other) {
- this.account = other.account;
- this.userId = other.userId;
- this.syncSource = other.syncSource;
- this.authority = other.authority;
- this.extras = new Bundle(other.extras);
- this.expedited = other.expedited;
- this.earliestRunTime = SystemClock.elapsedRealtime();
- this.backoff = other.backoff;
- this.delayUntil = other.delayUntil;
- this.allowParallelSyncs = other.allowParallelSyncs;
- this.updateEffectiveRunTime();
- this.key = toKey();
- }
-
- public String toString() {
- return dump(true);
- }
-
- public String dump(boolean useOneLine) {
- StringBuilder sb = new StringBuilder()
- .append(account.name)
- .append(" u")
- .append(userId).append(" (")
- .append(account.type)
- .append(")")
- .append(", ")
- .append(authority)
- .append(", ")
- .append(SyncStorageEngine.SOURCES[syncSource])
- .append(", earliestRunTime ")
- .append(earliestRunTime);
- if (expedited) {
- sb.append(", EXPEDITED");
- }
- if (!useOneLine && !extras.keySet().isEmpty()) {
- sb.append("\n ");
- extrasToStringBuilder(extras, sb);
- }
- return sb.toString();
- }
-
- public boolean isInitialization() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
- }
-
- public boolean isExpedited() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
- }
-
- public boolean ignoreBackoff() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
- }
-
- private String toKey() {
- StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
- + "}");
- sb.append(" extras: ");
- extrasToStringBuilder(extras, sb);
- return sb.toString();
- }
-
- public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
- sb.append("[");
- for (String key : bundle.keySet()) {
- sb.append(key).append("=").append(bundle.get(key)).append(" ");
- }
- sb.append("]");
- }
-
- public void updateEffectiveRunTime() {
- effectiveRunTime = ignoreBackoff()
- ? earliestRunTime
- : Math.max(
- Math.max(earliestRunTime, delayUntil),
- backoff);
- }
-
- public int compareTo(Object o) {
- SyncOperation other = (SyncOperation)o;
-
- if (expedited != other.expedited) {
- return expedited ? -1 : 1;
- }
-
- if (effectiveRunTime == other.effectiveRunTime) {
- return 0;
- }
-
- return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
- }
-}
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
deleted file mode 100644
index c9a325e..0000000
--- a/core/java/android/content/SyncQueue.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.accounts.Account;
-import android.content.pm.RegisteredServicesCache.ServiceInfo;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.android.collect.Maps;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Queue of pending sync operations. Not inherently thread safe, external
- * callers are responsible for locking.
- *
- * @hide
- */
-public class SyncQueue {
- private static final String TAG = "SyncManager";
-
- private final SyncStorageEngine mSyncStorageEngine;
- private final SyncAdaptersCache mSyncAdapters;
-
- // A Map of SyncOperations operationKey -> SyncOperation that is designed for
- // quick lookup of an enqueued SyncOperation.
- private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
-
- public SyncQueue(SyncStorageEngine syncStorageEngine, final SyncAdaptersCache syncAdapters) {
- mSyncStorageEngine = syncStorageEngine;
- mSyncAdapters = syncAdapters;
- }
-
- public void addPendingOperations(int userId) {
- for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
- if (op.userId != userId) continue;
-
- final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
- op.account, op.userId, op.authority);
- final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
- SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
- if (syncAdapterInfo == null) {
- Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId "
- + op.userId);
- continue;
- }
- SyncOperation syncOperation = new SyncOperation(
- op.account, op.userId, op.syncSource, op.authority, op.extras, 0 /* delay */,
- backoff != null ? backoff.first : 0,
- mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
- syncAdapterInfo.type.allowParallelSyncs());
- syncOperation.expedited = op.expedited;
- syncOperation.pendingOperation = op;
- add(syncOperation, op);
- }
- }
-
- public boolean add(SyncOperation operation) {
- return add(operation, null /* this is not coming from the database */);
- }
-
- private boolean add(SyncOperation operation,
- SyncStorageEngine.PendingOperation pop) {
- // - if an operation with the same key exists and this one should run earlier,
- // update the earliestRunTime of the existing to the new time
- // - if an operation with the same key exists and if this one should run
- // later, ignore it
- // - if no operation exists then add the new one
- final String operationKey = operation.key;
- final SyncOperation existingOperation = mOperationsMap.get(operationKey);
-
- if (existingOperation != null) {
- boolean changed = false;
- if (existingOperation.expedited == operation.expedited) {
- final long newRunTime =
- Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
- if (existingOperation.earliestRunTime != newRunTime) {
- existingOperation.earliestRunTime = newRunTime;
- changed = true;
- }
- } else {
- if (operation.expedited) {
- existingOperation.expedited = true;
- changed = true;
- }
- }
- return changed;
- }
-
- operation.pendingOperation = pop;
- if (operation.pendingOperation == null) {
- pop = new SyncStorageEngine.PendingOperation(
- operation.account, operation.userId, operation.syncSource,
- operation.authority, operation.extras, operation.expedited);
- pop = mSyncStorageEngine.insertIntoPending(pop);
- if (pop == null) {
- throw new IllegalStateException("error adding pending sync operation "
- + operation);
- }
- operation.pendingOperation = pop;
- }
-
- mOperationsMap.put(operationKey, operation);
- return true;
- }
-
- public void removeUser(int userId) {
- ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
- for (SyncOperation op : mOperationsMap.values()) {
- if (op.userId == userId) {
- opsToRemove.add(op);
- }
- }
-
- for (SyncOperation op : opsToRemove) {
- remove(op);
- }
- }
-
- /**
- * Remove the specified operation if it is in the queue.
- * @param operation the operation to remove
- */
- public void remove(SyncOperation operation) {
- SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
- if (operationToRemove == null) {
- return;
- }
- if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + operationToRemove;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
- }
-
- public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
- // for each op that matches the account and provider update its
- // backoff and effectiveStartTime
- for (SyncOperation op : mOperationsMap.values()) {
- if (op.account.equals(account) && op.authority.equals(providerName)
- && op.userId == userId) {
- op.backoff = backoff;
- op.updateEffectiveRunTime();
- }
- }
- }
-
- public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
- // for each op that matches the account and provider update its
- // delayUntilTime and effectiveStartTime
- for (SyncOperation op : mOperationsMap.values()) {
- if (op.account.equals(account) && op.authority.equals(providerName)) {
- op.delayUntil = delayUntil;
- op.updateEffectiveRunTime();
- }
- }
- }
-
- public void remove(Account account, int userId, String authority) {
- Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
- while (entries.hasNext()) {
- Map.Entry<String, SyncOperation> entry = entries.next();
- SyncOperation syncOperation = entry.getValue();
- if (account != null && !syncOperation.account.equals(account)) {
- continue;
- }
- if (authority != null && !syncOperation.authority.equals(authority)) {
- continue;
- }
- if (userId != syncOperation.userId) {
- continue;
- }
- entries.remove();
- if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
- final String errorMessage = "unable to find pending row for " + syncOperation;
- Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
- }
- }
- }
-
- public Collection<SyncOperation> getOperations() {
- return mOperationsMap.values();
- }
-
- public void dump(StringBuilder sb) {
- final long now = SystemClock.elapsedRealtime();
- sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
- for (SyncOperation operation : mOperationsMap.values()) {
- sb.append(" ");
- if (operation.effectiveRunTime <= now) {
- sb.append("READY");
- } else {
- sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
- }
- sb.append(" - ");
- sb.append(operation.dump(false)).append("\n");
- }
- }
-}
diff --git a/core/java/android/content/SyncStatusInfo.java b/core/java/android/content/SyncStatusInfo.java
index bb2b2da..ff628d9 100644
--- a/core/java/android/content/SyncStatusInfo.java
+++ b/core/java/android/content/SyncStatusInfo.java
@@ -46,19 +46,18 @@ public class SyncStatusInfo implements Parcelable {
private static final String TAG = "Sync";
- SyncStatusInfo(int authorityId) {
+ public SyncStatusInfo(int authorityId) {
this.authorityId = authorityId;
}
public int getLastFailureMesgAsInt(int def) {
- try {
- if (lastFailureMesg != null) {
- return Integer.parseInt(lastFailureMesg);
- }
- } catch (NumberFormatException e) {
- Log.d(TAG, "error parsing lastFailureMesg of " + lastFailureMesg, e);
+ final int i = ContentResolver.syncErrorStringToInt(lastFailureMesg);
+ if (i > 0) {
+ return i;
+ } else {
+ Log.d(TAG, "Unknown lastFailureMesg:" + lastFailureMesg);
+ return def;
}
- return def;
}
public int describeContents() {
@@ -92,7 +91,7 @@ public class SyncStatusInfo implements Parcelable {
}
}
- SyncStatusInfo(Parcel parcel) {
+ public SyncStatusInfo(Parcel parcel) {
int version = parcel.readInt();
if (version != VERSION && version != 1) {
Log.w("SyncStatusInfo", "Unknown version: " + version);
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
deleted file mode 100644
index 1ecab09..0000000
--- a/core/java/android/content/SyncStorageEngine.java
+++ /dev/null
@@ -1,2303 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FastXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import android.accounts.Account;
-import android.accounts.AccountAndUser;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Parcel;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.util.AtomicFile;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.Xml;
-import android.util.Pair;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Random;
-import java.util.TimeZone;
-import java.util.List;
-
-/**
- * Singleton that tracks the sync data and overall sync
- * history on the device.
- *
- * @hide
- */
-public class SyncStorageEngine extends Handler {
-
- private static final String TAG = "SyncManager";
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_FILE = false;
-
- private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
- private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
- private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
- private static final String XML_ATTR_ENABLED = "enabled";
- private static final String XML_ATTR_USER = "user";
- private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
-
- private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
-
- @VisibleForTesting
- static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
-
- /** Enum value for a sync start event. */
- public static final int EVENT_START = 0;
-
- /** Enum value for a sync stop event. */
- public static final int EVENT_STOP = 1;
-
- // TODO: i18n -- grab these out of resources.
- /** String names for the sync event types. */
- public static final String[] EVENTS = { "START", "STOP" };
-
- /** Enum value for a server-initiated sync. */
- public static final int SOURCE_SERVER = 0;
-
- /** Enum value for a local-initiated sync. */
- public static final int SOURCE_LOCAL = 1;
- /**
- * Enum value for a poll-based sync (e.g., upon connection to
- * network)
- */
- public static final int SOURCE_POLL = 2;
-
- /** Enum value for a user-initiated sync. */
- public static final int SOURCE_USER = 3;
-
- /** Enum value for a periodic sync. */
- public static final int SOURCE_PERIODIC = 4;
-
- public static final long NOT_IN_BACKOFF_MODE = -1;
-
- public static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
- new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
-
- // TODO: i18n -- grab these out of resources.
- /** String names for the sync source types. */
- public static final String[] SOURCES = { "SERVER",
- "LOCAL",
- "POLL",
- "USER",
- "PERIODIC" };
-
- // The MESG column will contain one of these or one of the Error types.
- public static final String MESG_SUCCESS = "success";
- public static final String MESG_CANCELED = "canceled";
-
- public static final int MAX_HISTORY = 100;
-
- private static final int MSG_WRITE_STATUS = 1;
- private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
-
- private static final int MSG_WRITE_STATISTICS = 2;
- private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
-
- private static final boolean SYNC_ENABLED_DEFAULT = false;
-
- // the version of the accounts xml file format
- private static final int ACCOUNTS_VERSION = 2;
-
- private static HashMap<String, String> sAuthorityRenames;
-
- static {
- sAuthorityRenames = new HashMap<String, String>();
- sAuthorityRenames.put("contacts", "com.android.contacts");
- sAuthorityRenames.put("calendar", "com.android.calendar");
- }
-
- public static class PendingOperation {
- final Account account;
- final int userId;
- final int syncSource;
- final String authority;
- final Bundle extras; // note: read-only.
- final boolean expedited;
-
- int authorityId;
- byte[] flatExtras;
-
- PendingOperation(Account account, int userId, int source,
- String authority, Bundle extras, boolean expedited) {
- this.account = account;
- this.userId = userId;
- this.syncSource = source;
- this.authority = authority;
- this.extras = extras != null ? new Bundle(extras) : extras;
- this.expedited = expedited;
- this.authorityId = -1;
- }
-
- PendingOperation(PendingOperation other) {
- this.account = other.account;
- this.userId = other.userId;
- this.syncSource = other.syncSource;
- this.authority = other.authority;
- this.extras = other.extras;
- this.authorityId = other.authorityId;
- this.expedited = other.expedited;
- }
- }
-
- static class AccountInfo {
- final AccountAndUser accountAndUser;
- final HashMap<String, AuthorityInfo> authorities =
- new HashMap<String, AuthorityInfo>();
-
- AccountInfo(AccountAndUser accountAndUser) {
- this.accountAndUser = accountAndUser;
- }
- }
-
- public static class AuthorityInfo {
- final Account account;
- final int userId;
- final String authority;
- final int ident;
- boolean enabled;
- int syncable;
- long backoffTime;
- long backoffDelay;
- long delayUntil;
- final ArrayList<Pair<Bundle, Long>> periodicSyncs;
-
- /**
- * Copy constructor for making deep-ish copies. Only the bundles stored
- * in periodic syncs can make unexpected changes.
- *
- * @param toCopy AuthorityInfo to be copied.
- */
- AuthorityInfo(AuthorityInfo toCopy) {
- account = toCopy.account;
- userId = toCopy.userId;
- authority = toCopy.authority;
- ident = toCopy.ident;
- enabled = toCopy.enabled;
- syncable = toCopy.syncable;
- backoffTime = toCopy.backoffTime;
- backoffDelay = toCopy.backoffDelay;
- delayUntil = toCopy.delayUntil;
- periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
- for (Pair<Bundle, Long> sync : toCopy.periodicSyncs) {
- // Still not a perfect copy, because we are just copying the mappings.
- periodicSyncs.add(Pair.create(new Bundle(sync.first), sync.second));
- }
- }
-
- AuthorityInfo(Account account, int userId, String authority, int ident) {
- this.account = account;
- this.userId = userId;
- this.authority = authority;
- this.ident = ident;
- enabled = SYNC_ENABLED_DEFAULT;
- syncable = -1; // default to "unknown"
- backoffTime = -1; // if < 0 then we aren't in backoff mode
- backoffDelay = -1; // if < 0 then we aren't in backoff mode
- periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
- periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
- }
- }
-
- public static class SyncHistoryItem {
- int authorityId;
- int historyId;
- long eventTime;
- long elapsedTime;
- int source;
- int event;
- long upstreamActivity;
- long downstreamActivity;
- String mesg;
- boolean initialization;
- }
-
- public static class DayStats {
- public final int day;
- public int successCount;
- public long successTime;
- public int failureCount;
- public long failureTime;
-
- public DayStats(int day) {
- this.day = day;
- }
- }
-
- interface OnSyncRequestListener {
- /**
- * Called when a sync is needed on an account(s) due to some change in state.
- * @param account
- * @param userId
- * @param authority
- * @param extras
- */
- public void onSyncRequest(Account account, int userId, String authority, Bundle extras);
- }
-
- // Primary list of all syncable authorities. Also our global lock.
- private final SparseArray<AuthorityInfo> mAuthorities =
- new SparseArray<AuthorityInfo>();
-
- private final HashMap<AccountAndUser, AccountInfo> mAccounts
- = new HashMap<AccountAndUser, AccountInfo>();
-
- private final ArrayList<PendingOperation> mPendingOperations =
- new ArrayList<PendingOperation>();
-
- private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
- = new SparseArray<ArrayList<SyncInfo>>();
-
- private final SparseArray<SyncStatusInfo> mSyncStatus =
- new SparseArray<SyncStatusInfo>();
-
- private final ArrayList<SyncHistoryItem> mSyncHistory =
- new ArrayList<SyncHistoryItem>();
-
- private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
- = new RemoteCallbackList<ISyncStatusObserver>();
-
- private int mNextAuthorityId = 0;
-
- // We keep 4 weeks of stats.
- private final DayStats[] mDayStats = new DayStats[7*4];
- private final Calendar mCal;
- private int mYear;
- private int mYearInDays;
-
- private final Context mContext;
-
- private static volatile SyncStorageEngine sSyncStorageEngine = null;
-
- private int mSyncRandomOffset;
-
- /**
- * This file contains the core engine state: all accounts and the
- * settings for them. It must never be lost, and should be changed
- * infrequently, so it is stored as an XML file.
- */
- private final AtomicFile mAccountInfoFile;
-
- /**
- * This file contains the current sync status. We would like to retain
- * it across boots, but its loss is not the end of the world, so we store
- * this information as binary data.
- */
- private final AtomicFile mStatusFile;
-
- /**
- * This file contains sync statistics. This is purely debugging information
- * so is written infrequently and can be thrown away at any time.
- */
- private final AtomicFile mStatisticsFile;
-
- /**
- * This file contains the pending sync operations. It is a binary file,
- * which must be updated every time an operation is added or removed,
- * so we have special handling of it.
- */
- private final AtomicFile mPendingFile;
- private static final int PENDING_FINISH_TO_WRITE = 4;
- private int mNumPendingFinished = 0;
-
- private int mNextHistoryId = 0;
- private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>();
- private boolean mDefaultMasterSyncAutomatically;
-
- private OnSyncRequestListener mSyncRequestListener;
-
- private SyncStorageEngine(Context context, File dataDir) {
- mContext = context;
- sSyncStorageEngine = this;
-
- mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
-
- mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
-
- File systemDir = new File(dataDir, "system");
- File syncDir = new File(systemDir, "sync");
- syncDir.mkdirs();
- mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
- mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
- mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
- mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
-
- readAccountInfoLocked();
- readStatusLocked();
- readPendingOperationsLocked();
- readStatisticsLocked();
- readAndDeleteLegacyAccountInfoLocked();
- writeAccountInfoLocked();
- writeStatusLocked();
- writePendingOperationsLocked();
- writeStatisticsLocked();
- }
-
- public static SyncStorageEngine newTestInstance(Context context) {
- return new SyncStorageEngine(context, context.getFilesDir());
- }
-
- public static void init(Context context) {
- if (sSyncStorageEngine != null) {
- return;
- }
- // This call will return the correct directory whether Encrypted File Systems is
- // enabled or not.
- File dataDir = Environment.getSecureDataDirectory();
- sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
- }
-
- public static SyncStorageEngine getSingleton() {
- if (sSyncStorageEngine == null) {
- throw new IllegalStateException("not initialized");
- }
- return sSyncStorageEngine;
- }
-
- protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
- if (mSyncRequestListener == null) {
- mSyncRequestListener = listener;
- }
- }
-
- @Override public void handleMessage(Message msg) {
- if (msg.what == MSG_WRITE_STATUS) {
- synchronized (mAuthorities) {
- writeStatusLocked();
- }
- } else if (msg.what == MSG_WRITE_STATISTICS) {
- synchronized (mAuthorities) {
- writeStatisticsLocked();
- }
- }
- }
-
- public int getSyncRandomOffset() {
- return mSyncRandomOffset;
- }
-
- public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
- synchronized (mAuthorities) {
- mChangeListeners.register(callback, mask);
- }
- }
-
- public void removeStatusChangeListener(ISyncStatusObserver callback) {
- synchronized (mAuthorities) {
- mChangeListeners.unregister(callback);
- }
- }
-
- private void reportChange(int which) {
- ArrayList<ISyncStatusObserver> reports = null;
- synchronized (mAuthorities) {
- int i = mChangeListeners.beginBroadcast();
- while (i > 0) {
- i--;
- Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
- if ((which & mask.intValue()) == 0) {
- continue;
- }
- if (reports == null) {
- reports = new ArrayList<ISyncStatusObserver>(i);
- }
- reports.add(mChangeListeners.getBroadcastItem(i));
- }
- mChangeListeners.finishBroadcast();
- }
-
- if (DEBUG) {
- Log.v(TAG, "reportChange " + which + " to: " + reports);
- }
-
- if (reports != null) {
- int i = reports.size();
- while (i > 0) {
- i--;
- try {
- reports.get(i).onStatusChanged(which);
- } catch (RemoteException e) {
- // The remote callback list will take care of this for us.
- }
- }
- }
- }
-
- public boolean getSyncAutomatically(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- if (account != null) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getSyncAutomatically");
- return authority != null && authority.enabled;
- }
-
- int i = mAuthorities.size();
- while (i > 0) {
- i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(providerName)
- && authority.userId == userId
- && authority.enabled) {
- return true;
- }
- }
- return false;
- }
- }
-
- public void setSyncAutomatically(Account account, int userId, String providerName,
- boolean sync) {
- if (DEBUG) {
- Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
- + ", user " + userId + " -> " + sync);
- }
- synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
- false);
- if (authority.enabled == sync) {
- if (DEBUG) {
- Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
- }
- return;
- }
- authority.enabled = sync;
- writeAccountInfoLocked();
- }
-
- if (sync) {
- requestSync(account, userId, providerName, new Bundle());
- }
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public int getIsSyncable(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- if (account != null) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getIsSyncable");
- if (authority == null) {
- return -1;
- }
- return authority.syncable;
- }
-
- int i = mAuthorities.size();
- while (i > 0) {
- i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(providerName)) {
- return authority.syncable;
- }
- }
- return -1;
- }
- }
-
- public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
- if (syncable > 1) {
- syncable = 1;
- } else if (syncable < -1) {
- syncable = -1;
- }
- if (DEBUG) {
- Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName
- + ", user " + userId + " -> " + syncable);
- }
- synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
- false);
- if (authority.syncable == syncable) {
- if (DEBUG) {
- Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
- }
- return;
- }
- authority.syncable = syncable;
- writeAccountInfoLocked();
- }
-
- if (syncable > 0) {
- requestSync(account, userId, providerName, new Bundle());
- }
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getBackoff");
- if (authority == null || authority.backoffTime < 0) {
- return null;
- }
- return Pair.create(authority.backoffTime, authority.backoffDelay);
- }
- }
-
- public void setBackoff(Account account, int userId, String providerName,
- long nextSyncTime, long nextDelay) {
- if (DEBUG) {
- Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
- + ", user " + userId
- + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
- }
- boolean changed = false;
- synchronized (mAuthorities) {
- if (account == null || providerName == null) {
- for (AccountInfo accountInfo : mAccounts.values()) {
- if (account != null && !account.equals(accountInfo.accountAndUser.account)
- && userId != accountInfo.accountAndUser.userId) {
- continue;
- }
- for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
- if (providerName != null && !providerName.equals(authorityInfo.authority)) {
- continue;
- }
- if (authorityInfo.backoffTime != nextSyncTime
- || authorityInfo.backoffDelay != nextDelay) {
- authorityInfo.backoffTime = nextSyncTime;
- authorityInfo.backoffDelay = nextDelay;
- changed = true;
- }
- }
- }
- } else {
- AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */,
- true);
- if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
- return;
- }
- authority.backoffTime = nextSyncTime;
- authority.backoffDelay = nextDelay;
- changed = true;
- }
- }
-
- if (changed) {
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
- }
-
- public void clearAllBackoffs(SyncQueue syncQueue) {
- boolean changed = false;
- synchronized (mAuthorities) {
- synchronized (syncQueue) {
- for (AccountInfo accountInfo : mAccounts.values()) {
- for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
- if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
- || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
- if (DEBUG) {
- Log.v(TAG, "clearAllBackoffs:"
- + " authority:" + authorityInfo.authority
- + " account:" + accountInfo.accountAndUser.account.name
- + " user:" + accountInfo.accountAndUser.userId
- + " backoffTime was: " + authorityInfo.backoffTime
- + " backoffDelay was: " + authorityInfo.backoffDelay);
- }
- authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
- authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
- syncQueue.onBackoffChanged(accountInfo.accountAndUser.account,
- accountInfo.accountAndUser.userId, authorityInfo.authority, 0);
- changed = true;
- }
- }
- }
- }
- }
-
- if (changed) {
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
- }
-
- public void setDelayUntilTime(Account account, int userId, String providerName,
- long delayUntil) {
- if (DEBUG) {
- Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
- + ", user " + userId + " -> delayUntil " + delayUntil);
- }
- synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(
- account, userId, providerName, -1 /* ident */, true);
- if (authority.delayUntil == delayUntil) {
- return;
- }
- authority.delayUntil = delayUntil;
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public long getDelayUntilTime(Account account, int userId, String providerName) {
- synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getDelayUntil");
- if (authority == null) {
- return 0;
- }
- return authority.delayUntil;
- }
- }
-
- private void updateOrRemovePeriodicSync(Account account, int userId, String providerName,
- Bundle extras,
- long period, boolean add) {
- if (period <= 0) {
- period = 0;
- }
- if (extras == null) {
- extras = new Bundle();
- }
- if (DEBUG) {
- Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId
- + ", provider " + providerName
- + " -> period " + period + ", extras " + extras);
- }
- synchronized (mAuthorities) {
- try {
- AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
- if (add) {
- // add this periodic sync if one with the same extras doesn't already
- // exist in the periodicSyncs array
- boolean alreadyPresent = false;
- for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
- Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
- final Bundle existingExtras = syncInfo.first;
- if (equals(existingExtras, extras)) {
- if (syncInfo.second == period) {
- return;
- }
- authority.periodicSyncs.set(i, Pair.create(extras, period));
- alreadyPresent = true;
- break;
- }
- }
- // if we added an entry to the periodicSyncs array also add an entry to
- // the periodic syncs status to correspond to it
- if (!alreadyPresent) {
- authority.periodicSyncs.add(Pair.create(extras, period));
- SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
- status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
- }
- } else {
- // remove any periodic syncs that match the authority and extras
- SyncStatusInfo status = mSyncStatus.get(authority.ident);
- boolean changed = false;
- Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
- int i = 0;
- while (iterator.hasNext()) {
- Pair<Bundle, Long> syncInfo = iterator.next();
- if (equals(syncInfo.first, extras)) {
- iterator.remove();
- changed = true;
- // if we removed an entry from the periodicSyncs array also
- // remove the corresponding entry from the status
- if (status != null) {
- status.removePeriodicSyncTime(i);
- }
- } else {
- i++;
- }
- }
- if (!changed) {
- return;
- }
- }
- } finally {
- writeAccountInfoLocked();
- writeStatusLocked();
- }
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- }
-
- public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras,
- long pollFrequency) {
- updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency,
- true /* add */);
- }
-
- public void removePeriodicSync(Account account, int userId, String providerName,
- Bundle extras) {
- updateOrRemovePeriodicSync(account, userId, providerName, extras, 0 /* period, ignored */,
- false /* remove */);
- }
-
- public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
- ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
- synchronized (mAuthorities) {
- AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
- "getPeriodicSyncs");
- if (authority != null) {
- for (Pair<Bundle, Long> item : authority.periodicSyncs) {
- syncs.add(new PeriodicSync(account, providerName, item.first,
- item.second));
- }
- }
- }
- return syncs;
- }
-
- public void setMasterSyncAutomatically(boolean flag, int userId) {
- synchronized (mAuthorities) {
- Boolean auto = mMasterSyncAutomatically.get(userId);
- if (auto != null && (boolean) auto == flag) {
- return;
- }
- mMasterSyncAutomatically.put(userId, flag);
- writeAccountInfoLocked();
- }
- if (flag) {
- requestSync(null, userId, null, new Bundle());
- }
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
- mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
- }
-
- public boolean getMasterSyncAutomatically(int userId) {
- synchronized (mAuthorities) {
- Boolean auto = mMasterSyncAutomatically.get(userId);
- return auto == null ? mDefaultMasterSyncAutomatically : auto;
- }
- }
-
- public AuthorityInfo getOrCreateAuthority(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- return getOrCreateAuthorityLocked(account, userId, authority,
- -1 /* assign a new identifier if creating a new authority */,
- true /* write to storage if this results in a change */);
- }
- }
-
- public void removeAuthority(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- removeAuthorityLocked(account, userId, authority, true /* doWrite */);
- }
- }
-
- public AuthorityInfo getAuthority(int authorityId) {
- synchronized (mAuthorities) {
- return mAuthorities.get(authorityId);
- }
- }
-
- /**
- * Returns true if there is currently a sync operation for the given
- * account or authority actively being processed.
- */
- public boolean isSyncActive(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- for (SyncInfo syncInfo : getCurrentSyncs(userId)) {
- AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
- if (ainfo != null && ainfo.account.equals(account)
- && ainfo.authority.equals(authority)
- && ainfo.userId == userId) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- public PendingOperation insertIntoPending(PendingOperation op) {
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "insertIntoPending: account=" + op.account
- + " user=" + op.userId
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " extras=" + op.extras);
- }
-
- AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId,
- op.authority,
- -1 /* desired identifier */,
- true /* write accounts to storage */);
- if (authority == null) {
- return null;
- }
-
- op = new PendingOperation(op);
- op.authorityId = authority.ident;
- mPendingOperations.add(op);
- appendPendingOperationLocked(op);
-
- SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
- status.pending = true;
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
- return op;
- }
-
- public boolean deleteFromPending(PendingOperation op) {
- boolean res = false;
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "deleteFromPending: account=" + op.account
- + " user=" + op.userId
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " extras=" + op.extras);
- }
- if (mPendingOperations.remove(op)) {
- if (mPendingOperations.size() == 0
- || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
- writePendingOperationsLocked();
- mNumPendingFinished = 0;
- } else {
- mNumPendingFinished++;
- }
-
- AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
- "deleteFromPending");
- if (authority != null) {
- if (DEBUG) Log.v(TAG, "removing - " + authority);
- final int N = mPendingOperations.size();
- boolean morePending = false;
- for (int i=0; i<N; i++) {
- PendingOperation cur = mPendingOperations.get(i);
- if (cur.account.equals(op.account)
- && cur.authority.equals(op.authority)
- && cur.userId == op.userId) {
- morePending = true;
- break;
- }
- }
-
- if (!morePending) {
- if (DEBUG) Log.v(TAG, "no more pending!");
- SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
- status.pending = false;
- }
- }
-
- res = true;
- }
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
- return res;
- }
-
- /**
- * Return a copy of the current array of pending operations. The
- * PendingOperation objects are the real objects stored inside, so that
- * they can be used with deleteFromPending().
- */
- public ArrayList<PendingOperation> getPendingOperations() {
- synchronized (mAuthorities) {
- return new ArrayList<PendingOperation>(mPendingOperations);
- }
- }
-
- /**
- * Return the number of currently pending operations.
- */
- public int getPendingOperationCount() {
- synchronized (mAuthorities) {
- return mPendingOperations.size();
- }
- }
-
- /**
- * Called when the set of account has changed, given the new array of
- * active accounts.
- */
- public void doDatabaseCleanup(Account[] accounts, int userId) {
- synchronized (mAuthorities) {
- if (DEBUG) Log.v(TAG, "Updating for new accounts...");
- SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
- Iterator<AccountInfo> accIt = mAccounts.values().iterator();
- while (accIt.hasNext()) {
- AccountInfo acc = accIt.next();
- if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
- && acc.accountAndUser.userId == userId) {
- // This account no longer exists...
- if (DEBUG) {
- Log.v(TAG, "Account removed: " + acc.accountAndUser);
- }
- for (AuthorityInfo auth : acc.authorities.values()) {
- removing.put(auth.ident, auth);
- }
- accIt.remove();
- }
- }
-
- // Clean out all data structures.
- int i = removing.size();
- if (i > 0) {
- while (i > 0) {
- i--;
- int ident = removing.keyAt(i);
- mAuthorities.remove(ident);
- int j = mSyncStatus.size();
- while (j > 0) {
- j--;
- if (mSyncStatus.keyAt(j) == ident) {
- mSyncStatus.remove(mSyncStatus.keyAt(j));
- }
- }
- j = mSyncHistory.size();
- while (j > 0) {
- j--;
- if (mSyncHistory.get(j).authorityId == ident) {
- mSyncHistory.remove(j);
- }
- }
- }
- writeAccountInfoLocked();
- writeStatusLocked();
- writePendingOperationsLocked();
- writeStatisticsLocked();
- }
- }
- }
-
- /**
- * Called when a sync is starting. Supply a valid ActiveSyncContext with information
- * about the sync.
- */
- public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
- final SyncInfo syncInfo;
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "setActiveSync: account="
- + activeSyncContext.mSyncOperation.account
- + " auth=" + activeSyncContext.mSyncOperation.authority
- + " src=" + activeSyncContext.mSyncOperation.syncSource
- + " extras=" + activeSyncContext.mSyncOperation.extras);
- }
- AuthorityInfo authority = getOrCreateAuthorityLocked(
- activeSyncContext.mSyncOperation.account,
- activeSyncContext.mSyncOperation.userId,
- activeSyncContext.mSyncOperation.authority,
- -1 /* assign a new identifier if creating a new authority */,
- true /* write to storage if this results in a change */);
- syncInfo = new SyncInfo(authority.ident,
- authority.account, authority.authority,
- activeSyncContext.mStartTime);
- getCurrentSyncs(authority.userId).add(syncInfo);
- }
-
- reportActiveChange();
- return syncInfo;
- }
-
- /**
- * Called to indicate that a previously active sync is no longer active.
- */
- public void removeActiveSync(SyncInfo syncInfo, int userId) {
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
- + " user=" + userId
- + " auth=" + syncInfo.authority);
- }
- getCurrentSyncs(userId).remove(syncInfo);
- }
-
- reportActiveChange();
- }
-
- /**
- * To allow others to send active change reports, to poke clients.
- */
- public void reportActiveChange() {
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
- }
-
- /**
- * Note that sync has started for the given account and authority.
- */
- public long insertStartSyncEvent(Account accountName, int userId, String authorityName,
- long now, int source, boolean initialization) {
- long id;
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId
- + " auth=" + authorityName + " source=" + source);
- }
- AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName,
- "insertStartSyncEvent");
- if (authority == null) {
- return -1;
- }
- SyncHistoryItem item = new SyncHistoryItem();
- item.initialization = initialization;
- item.authorityId = authority.ident;
- item.historyId = mNextHistoryId++;
- if (mNextHistoryId < 0) mNextHistoryId = 0;
- item.eventTime = now;
- item.source = source;
- item.event = EVENT_START;
- mSyncHistory.add(0, item);
- while (mSyncHistory.size() > MAX_HISTORY) {
- mSyncHistory.remove(mSyncHistory.size()-1);
- }
- id = item.historyId;
- if (DEBUG) Log.v(TAG, "returning historyId " + id);
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
- return id;
- }
-
- public static boolean equals(Bundle b1, Bundle b2) {
- if (b1.size() != b2.size()) {
- return false;
- }
- if (b1.isEmpty()) {
- return true;
- }
- for (String key : b1.keySet()) {
- if (!b2.containsKey(key)) {
- return false;
- }
- if (!b1.get(key).equals(b2.get(key))) {
- return false;
- }
- }
- return true;
- }
-
- public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
- long downstreamActivity, long upstreamActivity) {
- synchronized (mAuthorities) {
- if (DEBUG) {
- Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
- }
- SyncHistoryItem item = null;
- int i = mSyncHistory.size();
- while (i > 0) {
- i--;
- item = mSyncHistory.get(i);
- if (item.historyId == historyId) {
- break;
- }
- item = null;
- }
-
- if (item == null) {
- Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
- return;
- }
-
- item.elapsedTime = elapsedTime;
- item.event = EVENT_STOP;
- item.mesg = resultMessage;
- item.downstreamActivity = downstreamActivity;
- item.upstreamActivity = upstreamActivity;
-
- SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
-
- status.numSyncs++;
- status.totalElapsedTime += elapsedTime;
- switch (item.source) {
- case SOURCE_LOCAL:
- status.numSourceLocal++;
- break;
- case SOURCE_POLL:
- status.numSourcePoll++;
- break;
- case SOURCE_USER:
- status.numSourceUser++;
- break;
- case SOURCE_SERVER:
- status.numSourceServer++;
- break;
- case SOURCE_PERIODIC:
- status.numSourcePeriodic++;
- break;
- }
-
- boolean writeStatisticsNow = false;
- int day = getCurrentDayLocked();
- if (mDayStats[0] == null) {
- mDayStats[0] = new DayStats(day);
- } else if (day != mDayStats[0].day) {
- System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
- mDayStats[0] = new DayStats(day);
- writeStatisticsNow = true;
- } else if (mDayStats[0] == null) {
- }
- final DayStats ds = mDayStats[0];
-
- final long lastSyncTime = (item.eventTime + elapsedTime);
- boolean writeStatusNow = false;
- if (MESG_SUCCESS.equals(resultMessage)) {
- // - if successful, update the successful columns
- if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
- writeStatusNow = true;
- }
- status.lastSuccessTime = lastSyncTime;
- status.lastSuccessSource = item.source;
- status.lastFailureTime = 0;
- status.lastFailureSource = -1;
- status.lastFailureMesg = null;
- status.initialFailureTime = 0;
- ds.successCount++;
- ds.successTime += elapsedTime;
- } else if (!MESG_CANCELED.equals(resultMessage)) {
- if (status.lastFailureTime == 0) {
- writeStatusNow = true;
- }
- status.lastFailureTime = lastSyncTime;
- status.lastFailureSource = item.source;
- status.lastFailureMesg = resultMessage;
- if (status.initialFailureTime == 0) {
- status.initialFailureTime = lastSyncTime;
- }
- ds.failureCount++;
- ds.failureTime += elapsedTime;
- }
-
- if (writeStatusNow) {
- writeStatusLocked();
- } else if (!hasMessages(MSG_WRITE_STATUS)) {
- sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
- WRITE_STATUS_DELAY);
- }
- if (writeStatisticsNow) {
- writeStatisticsLocked();
- } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
- sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
- WRITE_STATISTICS_DELAY);
- }
- }
-
- reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
- }
-
- /**
- * Return a list of the currently active syncs. Note that the returned items are the
- * real, live active sync objects, so be careful what you do with it.
- */
- public List<SyncInfo> getCurrentSyncs(int userId) {
- synchronized (mAuthorities) {
- ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
- if (syncs == null) {
- syncs = new ArrayList<SyncInfo>();
- mCurrentSyncs.put(userId, syncs);
- }
- return syncs;
- }
- }
-
- /**
- * Return an array of the current sync status for all authorities. Note
- * that the objects inside the array are the real, live status objects,
- * so be careful what you do with them.
- */
- public ArrayList<SyncStatusInfo> getSyncStatus() {
- synchronized (mAuthorities) {
- final int N = mSyncStatus.size();
- ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
- for (int i=0; i<N; i++) {
- ops.add(mSyncStatus.valueAt(i));
- }
- return ops;
- }
- }
-
- /**
- * Return an array of the current authorities. Note
- * that the objects inside the array are the real, live objects,
- * so be careful what you do with them.
- */
- public ArrayList<AuthorityInfo> getAuthorities() {
- synchronized (mAuthorities) {
- final int N = mAuthorities.size();
- ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
- for (int i=0; i<N; i++) {
- // Make deep copy because AuthorityInfo syncs are liable to change.
- infos.add(new AuthorityInfo(mAuthorities.valueAt(i)));
- }
- return infos;
- }
- }
-
- /**
- * Returns the status that matches the authority and account.
- *
- * @param account the account we want to check
- * @param authority the authority whose row should be selected
- * @return the SyncStatusInfo for the authority
- */
- public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
- String authority) {
- if (account == null || authority == null) {
- throw new IllegalArgumentException();
- }
- synchronized (mAuthorities) {
- final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo cur = mSyncStatus.valueAt(i);
- AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
-
- if (ainfo != null && ainfo.authority.equals(authority)
- && ainfo.userId == userId
- && account.equals(ainfo.account)) {
- return cur;
- }
- }
- return null;
- }
- }
-
- /**
- * Return true if the pending status is true of any matching authorities.
- */
- public boolean isSyncPending(Account account, int userId, String authority) {
- synchronized (mAuthorities) {
- final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo cur = mSyncStatus.valueAt(i);
- AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
- if (ainfo == null) {
- continue;
- }
- if (userId != ainfo.userId) {
- continue;
- }
- if (account != null && !ainfo.account.equals(account)) {
- continue;
- }
- if (ainfo.authority.equals(authority) && cur.pending) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Return an array of the current sync status for all authorities. Note
- * that the objects inside the array are the real, live status objects,
- * so be careful what you do with them.
- */
- public ArrayList<SyncHistoryItem> getSyncHistory() {
- synchronized (mAuthorities) {
- final int N = mSyncHistory.size();
- ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
- for (int i=0; i<N; i++) {
- items.add(mSyncHistory.get(i));
- }
- return items;
- }
- }
-
- /**
- * Return an array of the current per-day statistics. Note
- * that the objects inside the array are the real, live status objects,
- * so be careful what you do with them.
- */
- public DayStats[] getDayStatistics() {
- synchronized (mAuthorities) {
- DayStats[] ds = new DayStats[mDayStats.length];
- System.arraycopy(mDayStats, 0, ds, 0, ds.length);
- return ds;
- }
- }
-
- private int getCurrentDayLocked() {
- mCal.setTimeInMillis(System.currentTimeMillis());
- final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
- if (mYear != mCal.get(Calendar.YEAR)) {
- mYear = mCal.get(Calendar.YEAR);
- mCal.clear();
- mCal.set(Calendar.YEAR, mYear);
- mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
- }
- return dayOfYear + mYearInDays;
- }
-
- /**
- * Retrieve an authority, returning null if one does not exist.
- *
- * @param accountName The name of the account for the authority.
- * @param authorityName The name of the authority itself.
- * @param tag If non-null, this will be used in a log message if the
- * requested authority does not exist.
- */
- private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName,
- String tag) {
- AccountAndUser au = new AccountAndUser(accountName, userId);
- AccountInfo accountInfo = mAccounts.get(au);
- if (accountInfo == null) {
- if (tag != null) {
- if (DEBUG) {
- Log.v(TAG, tag + ": unknown account " + au);
- }
- }
- return null;
- }
- AuthorityInfo authority = accountInfo.authorities.get(authorityName);
- if (authority == null) {
- if (tag != null) {
- if (DEBUG) {
- Log.v(TAG, tag + ": unknown authority " + authorityName);
- }
- }
- return null;
- }
-
- return authority;
- }
-
- private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
- String authorityName, int ident, boolean doWrite) {
- AccountAndUser au = new AccountAndUser(accountName, userId);
- AccountInfo account = mAccounts.get(au);
- if (account == null) {
- account = new AccountInfo(au);
- mAccounts.put(au, account);
- }
- AuthorityInfo authority = account.authorities.get(authorityName);
- if (authority == null) {
- if (ident < 0) {
- ident = mNextAuthorityId;
- mNextAuthorityId++;
- doWrite = true;
- }
- if (DEBUG) {
- Log.v(TAG, "created a new AuthorityInfo for " + accountName
- + ", user " + userId
- + ", provider " + authorityName);
- }
- authority = new AuthorityInfo(accountName, userId, authorityName, ident);
- account.authorities.put(authorityName, authority);
- mAuthorities.put(ident, authority);
- if (doWrite) {
- writeAccountInfoLocked();
- }
- }
-
- return authority;
- }
-
- private void removeAuthorityLocked(Account account, int userId, String authorityName,
- boolean doWrite) {
- AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
- if (accountInfo != null) {
- final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
- if (authorityInfo != null) {
- mAuthorities.remove(authorityInfo.ident);
- if (doWrite) {
- writeAccountInfoLocked();
- }
- }
- }
- }
-
- public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
- synchronized (mAuthorities) {
- return getOrCreateSyncStatusLocked(authority.ident);
- }
- }
-
- private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
- SyncStatusInfo status = mSyncStatus.get(authorityId);
- if (status == null) {
- status = new SyncStatusInfo(authorityId);
- mSyncStatus.put(authorityId, status);
- }
- return status;
- }
-
- public void writeAllState() {
- synchronized (mAuthorities) {
- // Account info is always written so no need to do it here.
-
- if (mNumPendingFinished > 0) {
- // Only write these if they are out of date.
- writePendingOperationsLocked();
- }
-
- // Just always write these... they are likely out of date.
- writeStatusLocked();
- writeStatisticsLocked();
- }
- }
-
- /**
- * public for testing
- */
- public void clearAndReadState() {
- synchronized (mAuthorities) {
- mAuthorities.clear();
- mAccounts.clear();
- mPendingOperations.clear();
- mSyncStatus.clear();
- mSyncHistory.clear();
-
- readAccountInfoLocked();
- readStatusLocked();
- readPendingOperationsLocked();
- readStatisticsLocked();
- readAndDeleteLegacyAccountInfoLocked();
- writeAccountInfoLocked();
- writeStatusLocked();
- writePendingOperationsLocked();
- writeStatisticsLocked();
- }
- }
-
- /**
- * Read all account information back in to the initial engine state.
- */
- private void readAccountInfoLocked() {
- int highestAuthorityId = -1;
- FileInputStream fis = null;
- try {
- fis = mAccountInfoFile.openRead();
- if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(fis, null);
- int eventType = parser.getEventType();
- while (eventType != XmlPullParser.START_TAG) {
- eventType = parser.next();
- }
- String tagName = parser.getName();
- if ("accounts".equals(tagName)) {
- String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
- String versionString = parser.getAttributeValue(null, "version");
- int version;
- try {
- version = (versionString == null) ? 0 : Integer.parseInt(versionString);
- } catch (NumberFormatException e) {
- version = 0;
- }
- String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
- try {
- int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
- mNextAuthorityId = Math.max(mNextAuthorityId, id);
- } catch (NumberFormatException e) {
- // don't care
- }
- String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
- try {
- mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
- } catch (NumberFormatException e) {
- mSyncRandomOffset = 0;
- }
- if (mSyncRandomOffset == 0) {
- Random random = new Random(System.currentTimeMillis());
- mSyncRandomOffset = random.nextInt(86400);
- }
- mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
- eventType = parser.next();
- AuthorityInfo authority = null;
- Pair<Bundle, Long> periodicSync = null;
- do {
- if (eventType == XmlPullParser.START_TAG) {
- tagName = parser.getName();
- if (parser.getDepth() == 2) {
- if ("authority".equals(tagName)) {
- authority = parseAuthority(parser, version);
- periodicSync = null;
- if (authority.ident > highestAuthorityId) {
- highestAuthorityId = authority.ident;
- }
- } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
- parseListenForTickles(parser);
- }
- } else if (parser.getDepth() == 3) {
- if ("periodicSync".equals(tagName) && authority != null) {
- periodicSync = parsePeriodicSync(parser, authority);
- }
- } else if (parser.getDepth() == 4 && periodicSync != null) {
- if ("extra".equals(tagName)) {
- parseExtra(parser, periodicSync);
- }
- }
- }
- eventType = parser.next();
- } while (eventType != XmlPullParser.END_DOCUMENT);
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Error reading accounts", e);
- return;
- } catch (java.io.IOException e) {
- if (fis == null) Log.i(TAG, "No initial accounts");
- else Log.w(TAG, "Error reading accounts", e);
- return;
- } finally {
- mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
- if (fis != null) {
- try {
- fis.close();
- } catch (java.io.IOException e1) {
- }
- }
- }
-
- maybeMigrateSettingsForRenamedAuthorities();
- }
-
- /**
- * some authority names have changed. copy over their settings and delete the old ones
- * @return true if a change was made
- */
- private boolean maybeMigrateSettingsForRenamedAuthorities() {
- boolean writeNeeded = false;
-
- ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
- final int N = mAuthorities.size();
- for (int i=0; i<N; i++) {
- AuthorityInfo authority = mAuthorities.valueAt(i);
- // skip this authority if it isn't one of the renamed ones
- final String newAuthorityName = sAuthorityRenames.get(authority.authority);
- if (newAuthorityName == null) {
- continue;
- }
-
- // remember this authority so we can remove it later. we can't remove it
- // now without messing up this loop iteration
- authoritiesToRemove.add(authority);
-
- // this authority isn't enabled, no need to copy it to the new authority name since
- // the default is "disabled"
- if (!authority.enabled) {
- continue;
- }
-
- // if we already have a record of this new authority then don't copy over the settings
- if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup")
- != null) {
- continue;
- }
-
- AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
- authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */);
- newAuthority.enabled = true;
- writeNeeded = true;
- }
-
- for (AuthorityInfo authorityInfo : authoritiesToRemove) {
- removeAuthorityLocked(authorityInfo.account, authorityInfo.userId,
- authorityInfo.authority, false /* doWrite */);
- writeNeeded = true;
- }
-
- return writeNeeded;
- }
-
- private void parseListenForTickles(XmlPullParser parser) {
- String user = parser.getAttributeValue(null, XML_ATTR_USER);
- int userId = 0;
- try {
- userId = Integer.parseInt(user);
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing the user for listen-for-tickles", e);
- } catch (NullPointerException e) {
- Log.e(TAG, "the user in listen-for-tickles is null", e);
- }
- String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
- boolean listen = enabled == null || Boolean.parseBoolean(enabled);
- mMasterSyncAutomatically.put(userId, listen);
- }
-
- private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
- AuthorityInfo authority = null;
- int id = -1;
- try {
- id = Integer.parseInt(parser.getAttributeValue(
- null, "id"));
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing the id of the authority", e);
- } catch (NullPointerException e) {
- Log.e(TAG, "the id of the authority is null", e);
- }
- if (id >= 0) {
- String authorityName = parser.getAttributeValue(null, "authority");
- String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
- String syncable = parser.getAttributeValue(null, "syncable");
- String accountName = parser.getAttributeValue(null, "account");
- String accountType = parser.getAttributeValue(null, "type");
- String user = parser.getAttributeValue(null, XML_ATTR_USER);
- int userId = user == null ? 0 : Integer.parseInt(user);
- if (accountType == null) {
- accountType = "com.google";
- syncable = "unknown";
- }
- authority = mAuthorities.get(id);
- if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
- + accountName + " auth=" + authorityName
- + " user=" + userId
- + " enabled=" + enabled
- + " syncable=" + syncable);
- if (authority == null) {
- if (DEBUG_FILE) Log.v(TAG, "Creating entry");
- authority = getOrCreateAuthorityLocked(
- new Account(accountName, accountType), userId, authorityName, id, false);
- // If the version is 0 then we are upgrading from a file format that did not
- // know about periodic syncs. In that case don't clear the list since we
- // want the default, which is a daily periodioc sync.
- // Otherwise clear out this default list since we will populate it later with
- // the periodic sync descriptions that are read from the configuration file.
- if (version > 0) {
- authority.periodicSyncs.clear();
- }
- }
- if (authority != null) {
- authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
- if ("unknown".equals(syncable)) {
- authority.syncable = -1;
- } else {
- authority.syncable =
- (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
- }
- } else {
- Log.w(TAG, "Failure adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
- }
- }
-
- return authority;
- }
-
- private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
- Bundle extras = new Bundle();
- String periodValue = parser.getAttributeValue(null, "period");
- final long period;
- try {
- period = Long.parseLong(periodValue);
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing the period of a periodic sync", e);
- return null;
- } catch (NullPointerException e) {
- Log.e(TAG, "the period of a periodic sync is null", e);
- return null;
- }
- final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
- authority.periodicSyncs.add(periodicSync);
-
- return periodicSync;
- }
-
- private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
- final Bundle extras = periodicSync.first;
- String name = parser.getAttributeValue(null, "name");
- String type = parser.getAttributeValue(null, "type");
- String value1 = parser.getAttributeValue(null, "value1");
- String value2 = parser.getAttributeValue(null, "value2");
-
- try {
- if ("long".equals(type)) {
- extras.putLong(name, Long.parseLong(value1));
- } else if ("integer".equals(type)) {
- extras.putInt(name, Integer.parseInt(value1));
- } else if ("double".equals(type)) {
- extras.putDouble(name, Double.parseDouble(value1));
- } else if ("float".equals(type)) {
- extras.putFloat(name, Float.parseFloat(value1));
- } else if ("boolean".equals(type)) {
- extras.putBoolean(name, Boolean.parseBoolean(value1));
- } else if ("string".equals(type)) {
- extras.putString(name, value1);
- } else if ("account".equals(type)) {
- extras.putParcelable(name, new Account(value1, value2));
- }
- } catch (NumberFormatException e) {
- Log.e(TAG, "error parsing bundle value", e);
- } catch (NullPointerException e) {
- Log.e(TAG, "error parsing bundle value", e);
- }
- }
-
- /**
- * Write all account information to the account file.
- */
- private void writeAccountInfoLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
- FileOutputStream fos = null;
-
- try {
- fos = mAccountInfoFile.startWrite();
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(fos, "utf-8");
- out.startDocument(null, true);
- out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
-
- out.startTag(null, "accounts");
- out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
- out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
- out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
-
- // Write the Sync Automatically flags for each user
- final int M = mMasterSyncAutomatically.size();
- for (int m = 0; m < M; m++) {
- int userId = mMasterSyncAutomatically.keyAt(m);
- Boolean listen = mMasterSyncAutomatically.valueAt(m);
- out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
- out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
- out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
- out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
- }
-
- final int N = mAuthorities.size();
- for (int i=0; i<N; i++) {
- AuthorityInfo authority = mAuthorities.valueAt(i);
- out.startTag(null, "authority");
- out.attribute(null, "id", Integer.toString(authority.ident));
- out.attribute(null, "account", authority.account.name);
- out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
- out.attribute(null, "type", authority.account.type);
- out.attribute(null, "authority", authority.authority);
- out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
- if (authority.syncable < 0) {
- out.attribute(null, "syncable", "unknown");
- } else {
- out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
- }
- for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
- out.startTag(null, "periodicSync");
- out.attribute(null, "period", Long.toString(periodicSync.second));
- final Bundle extras = periodicSync.first;
- for (String key : extras.keySet()) {
- out.startTag(null, "extra");
- out.attribute(null, "name", key);
- final Object value = extras.get(key);
- if (value instanceof Long) {
- out.attribute(null, "type", "long");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Integer) {
- out.attribute(null, "type", "integer");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Boolean) {
- out.attribute(null, "type", "boolean");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Float) {
- out.attribute(null, "type", "float");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Double) {
- out.attribute(null, "type", "double");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof String) {
- out.attribute(null, "type", "string");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Account) {
- out.attribute(null, "type", "account");
- out.attribute(null, "value1", ((Account)value).name);
- out.attribute(null, "value2", ((Account)value).type);
- }
- out.endTag(null, "extra");
- }
- out.endTag(null, "periodicSync");
- }
- out.endTag(null, "authority");
- }
-
- out.endTag(null, "accounts");
-
- out.endDocument();
-
- mAccountInfoFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing accounts", e1);
- if (fos != null) {
- mAccountInfoFile.failWrite(fos);
- }
- }
- }
-
- static int getIntColumn(Cursor c, String name) {
- return c.getInt(c.getColumnIndex(name));
- }
-
- static long getLongColumn(Cursor c, String name) {
- return c.getLong(c.getColumnIndex(name));
- }
-
- /**
- * Load sync engine state from the old syncmanager database, and then
- * erase it. Note that we don't deal with pending operations, active
- * sync, or history.
- */
- private void readAndDeleteLegacyAccountInfoLocked() {
- // Look for old database to initialize from.
- File file = mContext.getDatabasePath("syncmanager.db");
- if (!file.exists()) {
- return;
- }
- String path = file.getPath();
- SQLiteDatabase db = null;
- try {
- db = SQLiteDatabase.openDatabase(path, null,
- SQLiteDatabase.OPEN_READONLY);
- } catch (SQLiteException e) {
- }
-
- if (db != null) {
- final boolean hasType = db.getVersion() >= 11;
-
- // Copy in all of the status information, as well as accounts.
- if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setTables("stats, status");
- HashMap<String,String> map = new HashMap<String,String>();
- map.put("_id", "status._id as _id");
- map.put("account", "stats.account as account");
- if (hasType) {
- map.put("account_type", "stats.account_type as account_type");
- }
- map.put("authority", "stats.authority as authority");
- map.put("totalElapsedTime", "totalElapsedTime");
- map.put("numSyncs", "numSyncs");
- map.put("numSourceLocal", "numSourceLocal");
- map.put("numSourcePoll", "numSourcePoll");
- map.put("numSourceServer", "numSourceServer");
- map.put("numSourceUser", "numSourceUser");
- map.put("lastSuccessSource", "lastSuccessSource");
- map.put("lastSuccessTime", "lastSuccessTime");
- map.put("lastFailureSource", "lastFailureSource");
- map.put("lastFailureTime", "lastFailureTime");
- map.put("lastFailureMesg", "lastFailureMesg");
- map.put("pending", "pending");
- qb.setProjectionMap(map);
- qb.appendWhere("stats._id = status.stats_id");
- Cursor c = qb.query(db, null, null, null, null, null, null);
- while (c.moveToNext()) {
- String accountName = c.getString(c.getColumnIndex("account"));
- String accountType = hasType
- ? c.getString(c.getColumnIndex("account_type")) : null;
- if (accountType == null) {
- accountType = "com.google";
- }
- String authorityName = c.getString(c.getColumnIndex("authority"));
- AuthorityInfo authority = this.getOrCreateAuthorityLocked(
- new Account(accountName, accountType), 0 /* legacy is single-user */,
- authorityName, -1, false);
- if (authority != null) {
- int i = mSyncStatus.size();
- boolean found = false;
- SyncStatusInfo st = null;
- while (i > 0) {
- i--;
- st = mSyncStatus.valueAt(i);
- if (st.authorityId == authority.ident) {
- found = true;
- break;
- }
- }
- if (!found) {
- st = new SyncStatusInfo(authority.ident);
- mSyncStatus.put(authority.ident, st);
- }
- st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
- st.numSyncs = getIntColumn(c, "numSyncs");
- st.numSourceLocal = getIntColumn(c, "numSourceLocal");
- st.numSourcePoll = getIntColumn(c, "numSourcePoll");
- st.numSourceServer = getIntColumn(c, "numSourceServer");
- st.numSourceUser = getIntColumn(c, "numSourceUser");
- st.numSourcePeriodic = 0;
- st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
- st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
- st.lastFailureSource = getIntColumn(c, "lastFailureSource");
- st.lastFailureTime = getLongColumn(c, "lastFailureTime");
- st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
- st.pending = getIntColumn(c, "pending") != 0;
- }
- }
-
- c.close();
-
- // Retrieve the settings.
- qb = new SQLiteQueryBuilder();
- qb.setTables("settings");
- c = qb.query(db, null, null, null, null, null, null);
- while (c.moveToNext()) {
- String name = c.getString(c.getColumnIndex("name"));
- String value = c.getString(c.getColumnIndex("value"));
- if (name == null) continue;
- if (name.equals("listen_for_tickles")) {
- setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
- } else if (name.startsWith("sync_provider_")) {
- String provider = name.substring("sync_provider_".length(),
- name.length());
- int i = mAuthorities.size();
- while (i > 0) {
- i--;
- AuthorityInfo authority = mAuthorities.valueAt(i);
- if (authority.authority.equals(provider)) {
- authority.enabled = value == null || Boolean.parseBoolean(value);
- authority.syncable = 1;
- }
- }
- }
- }
-
- c.close();
-
- db.close();
-
- (new File(path)).delete();
- }
- }
-
- public static final int STATUS_FILE_END = 0;
- public static final int STATUS_FILE_ITEM = 100;
-
- /**
- * Read all sync status back in to the initial engine state.
- */
- private void readStatusLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
- try {
- byte[] data = mStatusFile.readFully();
- Parcel in = Parcel.obtain();
- in.unmarshall(data, 0, data.length);
- in.setDataPosition(0);
- int token;
- while ((token=in.readInt()) != STATUS_FILE_END) {
- if (token == STATUS_FILE_ITEM) {
- SyncStatusInfo status = new SyncStatusInfo(in);
- if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
- status.pending = false;
- if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
- + status.authorityId);
- mSyncStatus.put(status.authorityId, status);
- }
- } else {
- // Ooops.
- Log.w(TAG, "Unknown status token: " + token);
- break;
- }
- }
- } catch (java.io.IOException e) {
- Log.i(TAG, "No initial status");
- }
- }
-
- /**
- * Write all sync status to the sync status file.
- */
- private void writeStatusLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
-
- // The file is being written, so we don't need to have a scheduled
- // write until the next change.
- removeMessages(MSG_WRITE_STATUS);
-
- FileOutputStream fos = null;
- try {
- fos = mStatusFile.startWrite();
- Parcel out = Parcel.obtain();
- final int N = mSyncStatus.size();
- for (int i=0; i<N; i++) {
- SyncStatusInfo status = mSyncStatus.valueAt(i);
- out.writeInt(STATUS_FILE_ITEM);
- status.writeToParcel(out, 0);
- }
- out.writeInt(STATUS_FILE_END);
- fos.write(out.marshall());
- out.recycle();
-
- mStatusFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing status", e1);
- if (fos != null) {
- mStatusFile.failWrite(fos);
- }
- }
- }
-
- public static final int PENDING_OPERATION_VERSION = 2;
-
- /**
- * Read all pending operations back in to the initial engine state.
- */
- private void readPendingOperationsLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
- try {
- byte[] data = mPendingFile.readFully();
- Parcel in = Parcel.obtain();
- in.unmarshall(data, 0, data.length);
- in.setDataPosition(0);
- final int SIZE = in.dataSize();
- while (in.dataPosition() < SIZE) {
- int version = in.readInt();
- if (version != PENDING_OPERATION_VERSION && version != 1) {
- Log.w(TAG, "Unknown pending operation version "
- + version + "; dropping all ops");
- break;
- }
- int authorityId = in.readInt();
- int syncSource = in.readInt();
- byte[] flatExtras = in.createByteArray();
- boolean expedited;
- if (version == PENDING_OPERATION_VERSION) {
- expedited = in.readInt() != 0;
- } else {
- expedited = false;
- }
- AuthorityInfo authority = mAuthorities.get(authorityId);
- if (authority != null) {
- Bundle extras;
- if (flatExtras != null) {
- extras = unflattenBundle(flatExtras);
- } else {
- // if we are unable to parse the extras for whatever reason convert this
- // to a regular sync by creating an empty extras
- extras = new Bundle();
- }
- PendingOperation op = new PendingOperation(
- authority.account, authority.userId, syncSource,
- authority.authority, extras, expedited);
- op.authorityId = authorityId;
- op.flatExtras = flatExtras;
- if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " expedited=" + op.expedited
- + " extras=" + op.extras);
- mPendingOperations.add(op);
- }
- }
- } catch (java.io.IOException e) {
- Log.i(TAG, "No initial pending operations");
- }
- }
-
- private void writePendingOperationLocked(PendingOperation op, Parcel out) {
- out.writeInt(PENDING_OPERATION_VERSION);
- out.writeInt(op.authorityId);
- out.writeInt(op.syncSource);
- if (op.flatExtras == null && op.extras != null) {
- op.flatExtras = flattenBundle(op.extras);
- }
- out.writeByteArray(op.flatExtras);
- out.writeInt(op.expedited ? 1 : 0);
- }
-
- /**
- * Write all currently pending ops to the pending ops file.
- */
- private void writePendingOperationsLocked() {
- final int N = mPendingOperations.size();
- FileOutputStream fos = null;
- try {
- if (N == 0) {
- if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
- mPendingFile.truncate();
- return;
- }
-
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
- fos = mPendingFile.startWrite();
-
- Parcel out = Parcel.obtain();
- for (int i=0; i<N; i++) {
- PendingOperation op = mPendingOperations.get(i);
- writePendingOperationLocked(op, out);
- }
- fos.write(out.marshall());
- out.recycle();
-
- mPendingFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing pending operations", e1);
- if (fos != null) {
- mPendingFile.failWrite(fos);
- }
- }
- }
-
- /**
- * Append the given operation to the pending ops file; if unable to,
- * write all pending ops.
- */
- private void appendPendingOperationLocked(PendingOperation op) {
- if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
- FileOutputStream fos = null;
- try {
- fos = mPendingFile.openAppend();
- } catch (java.io.IOException e) {
- if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
- writePendingOperationsLocked();
- return;
- }
-
- try {
- Parcel out = Parcel.obtain();
- writePendingOperationLocked(op, out);
- fos.write(out.marshall());
- out.recycle();
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing pending operations", e1);
- } finally {
- try {
- fos.close();
- } catch (java.io.IOException e2) {
- }
- }
- }
-
- static private byte[] flattenBundle(Bundle bundle) {
- byte[] flatData = null;
- Parcel parcel = Parcel.obtain();
- try {
- bundle.writeToParcel(parcel, 0);
- flatData = parcel.marshall();
- } finally {
- parcel.recycle();
- }
- return flatData;
- }
-
- static private Bundle unflattenBundle(byte[] flatData) {
- Bundle bundle;
- Parcel parcel = Parcel.obtain();
- try {
- parcel.unmarshall(flatData, 0, flatData.length);
- parcel.setDataPosition(0);
- bundle = parcel.readBundle();
- } catch (RuntimeException e) {
- // A RuntimeException is thrown if we were unable to parse the parcel.
- // Create an empty parcel in this case.
- bundle = new Bundle();
- } finally {
- parcel.recycle();
- }
- return bundle;
- }
-
- private void requestSync(Account account, int userId, String authority, Bundle extras) {
- // If this is happening in the system process, then call the syncrequest listener
- // to make a request back to the SyncManager directly.
- // If this is probably a test instance, then call back through the ContentResolver
- // which will know which userId to apply based on the Binder id.
- if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
- && mSyncRequestListener != null) {
- mSyncRequestListener.onSyncRequest(account, userId, authority, extras);
- } else {
- ContentResolver.requestSync(account, authority, extras);
- }
- }
-
- public static final int STATISTICS_FILE_END = 0;
- public static final int STATISTICS_FILE_ITEM_OLD = 100;
- public static final int STATISTICS_FILE_ITEM = 101;
-
- /**
- * Read all sync statistics back in to the initial engine state.
- */
- private void readStatisticsLocked() {
- try {
- byte[] data = mStatisticsFile.readFully();
- Parcel in = Parcel.obtain();
- in.unmarshall(data, 0, data.length);
- in.setDataPosition(0);
- int token;
- int index = 0;
- while ((token=in.readInt()) != STATISTICS_FILE_END) {
- if (token == STATISTICS_FILE_ITEM
- || token == STATISTICS_FILE_ITEM_OLD) {
- int day = in.readInt();
- if (token == STATISTICS_FILE_ITEM_OLD) {
- day = day - 2009 + 14245; // Magic!
- }
- DayStats ds = new DayStats(day);
- ds.successCount = in.readInt();
- ds.successTime = in.readLong();
- ds.failureCount = in.readInt();
- ds.failureTime = in.readLong();
- if (index < mDayStats.length) {
- mDayStats[index] = ds;
- index++;
- }
- } else {
- // Ooops.
- Log.w(TAG, "Unknown stats token: " + token);
- break;
- }
- }
- } catch (java.io.IOException e) {
- Log.i(TAG, "No initial statistics");
- }
- }
-
- /**
- * Write all sync statistics to the sync status file.
- */
- private void writeStatisticsLocked() {
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
-
- // The file is being written, so we don't need to have a scheduled
- // write until the next change.
- removeMessages(MSG_WRITE_STATISTICS);
-
- FileOutputStream fos = null;
- try {
- fos = mStatisticsFile.startWrite();
- Parcel out = Parcel.obtain();
- final int N = mDayStats.length;
- for (int i=0; i<N; i++) {
- DayStats ds = mDayStats[i];
- if (ds == null) {
- break;
- }
- out.writeInt(STATISTICS_FILE_ITEM);
- out.writeInt(ds.day);
- out.writeInt(ds.successCount);
- out.writeLong(ds.successTime);
- out.writeInt(ds.failureCount);
- out.writeLong(ds.failureTime);
- }
- out.writeInt(STATISTICS_FILE_END);
- fos.write(out.marshall());
- out.recycle();
-
- mStatisticsFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing stats", e1);
- if (fos != null) {
- mStatisticsFile.failWrite(fos);
- }
- }
- }
-}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index b9e432c..a368451 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -127,7 +127,16 @@ interface IPackageManager {
* limit that kicks in when flags are included that bloat up the data
* returned.
*/
- ParceledListSlice getInstalledPackages(int flags, in String lastRead, in int userId);
+ ParceledListSlice getInstalledPackages(int flags, in int userId);
+
+ /**
+ * This implements getPackagesHoldingPermissions via a "last returned row"
+ * mechanism that is not exposed in the API. This is to get around the IPC
+ * limit that kicks in when flags are included that bloat up the data
+ * returned.
+ */
+ ParceledListSlice getPackagesHoldingPermissions(in String[] permissions,
+ int flags, int userId);
/**
* This implements getInstalledApplications via a "last returned row"
@@ -135,7 +144,7 @@ interface IPackageManager {
* limit that kicks in when flags are included that bloat up the data
* returned.
*/
- ParceledListSlice getInstalledApplications(int flags, in String lastRead, int userId);
+ ParceledListSlice getInstalledApplications(int flags, int userId);
/**
* Retrieve all applications that are marked as persistent.
@@ -201,6 +210,8 @@ interface IPackageManager {
List<PackageInfo> getPreferredPackages(int flags);
+ void resetPreferredActivities(int userId);
+
void addPreferredActivity(in IntentFilter filter, int match,
in ComponentName[] set, in ComponentName activity, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8ba1988..c507245 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -175,6 +175,14 @@ public abstract class PackageManager {
public static final int GET_CONFIGURATIONS = 0x00004000;
/**
+ * {@link PackageInfo} flag: include disabled components which are in
+ * that state only because of {@link #COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED}
+ * in the returned info. Note that if you set this flag, applications
+ * that are in this disabled state will be reported as enabled.
+ */
+ public static final int GET_DISABLED_UNTIL_USED_COMPONENTS = 0x00008000;
+
+ /**
* Resolution and querying flag: if set, only filters that support the
* {@link android.content.Intent#CATEGORY_DEFAULT} will be considered for
* matching. This is a synonym for including the CATEGORY_DEFAULT in your
@@ -265,6 +273,19 @@ public abstract class PackageManager {
public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3;
/**
+ * Flag for {@link #setApplicationEnabledSetting(String, int, int)} only: This
+ * application should be considered, until the point where the user actually
+ * wants to use it. This means that it will not normally show up to the user
+ * (such as in the launcher), but various parts of the user interface can
+ * use {@link #GET_DISABLED_UNTIL_USED_COMPONENTS} to still see it and allow
+ * the user to select it (as for example an IME, device admin, etc). Such code,
+ * once the user has selected the app, should at that point also make it enabled.
+ * This option currently <strong>can not</strong> be used with
+ * {@link #setComponentEnabledSetting(ComponentName, int, int)}.
+ */
+ public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4;
+
+ /**
* Flag parameter for {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} to
* indicate that this package should be installed as forward locked, i.e. only the app itself
* should have access to its code and non-resource assets.
@@ -645,6 +666,15 @@ public abstract class PackageManager {
public static final int INSTALL_FAILED_INTERNAL_ERROR = -110;
/**
+ * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by
+ * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)}
+ * if the system failed to install the package because the user is restricted from installing
+ * apps.
+ * @hide
+ */
+ public static final int INSTALL_FAILED_USER_RESTRICTED = -111;
+
+ /**
* Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the
* package's data directory.
*
@@ -689,6 +719,15 @@ public abstract class PackageManager {
public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
/**
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
+ * failed to delete the package since the user is restricted.
+ *
+ * @hide
+ */
+ public static final int DELETE_FAILED_USER_RESTRICTED = -3;
+
+ /**
* Return code that is passed to the {@link IPackageMoveObserver} by
* {@link #movePackage(android.net.Uri, IPackageMoveObserver)} when the
* package has been successfully moved by the system.
@@ -1280,6 +1319,22 @@ public abstract class PackageManager {
throws NameNotFoundException;
/**
+ * @hide Return the uid associated with the given package name for the
+ * given user.
+ *
+ * <p>Throws {@link NameNotFoundException} if a package with the given
+ * name can not be found on the system.
+ *
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the
+ * desired package.
+ * @param userHandle The user handle identifier to look up the package under.
+ *
+ * @return Returns an integer uid who owns the given package name.
+ */
+ public abstract int getPackageUid(String packageName, int userHandle)
+ throws NameNotFoundException;
+
+ /**
* Retrieve all of the information we know about a particular permission.
*
* <p>Throws {@link NameNotFoundException} if a permission with the given
@@ -1496,11 +1551,43 @@ public abstract class PackageManager {
* @see #GET_SERVICES
* @see #GET_SIGNATURES
* @see #GET_UNINSTALLED_PACKAGES
- *
*/
public abstract List<PackageInfo> getInstalledPackages(int flags);
/**
+ * Return a List of all installed packages that are currently
+ * holding any of the given permissions.
+ *
+ * @param flags Additional option flags. Use any combination of
+ * {@link #GET_ACTIVITIES},
+ * {@link #GET_GIDS},
+ * {@link #GET_CONFIGURATIONS},
+ * {@link #GET_INSTRUMENTATION},
+ * {@link #GET_PERMISSIONS},
+ * {@link #GET_PROVIDERS},
+ * {@link #GET_RECEIVERS},
+ * {@link #GET_SERVICES},
+ * {@link #GET_SIGNATURES},
+ * {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
+ *
+ * @return Returns a List of PackageInfo objects, one for each installed
+ * application that is holding any of the permissions that were provided.
+ *
+ * @see #GET_ACTIVITIES
+ * @see #GET_GIDS
+ * @see #GET_CONFIGURATIONS
+ * @see #GET_INSTRUMENTATION
+ * @see #GET_PERMISSIONS
+ * @see #GET_PROVIDERS
+ * @see #GET_RECEIVERS
+ * @see #GET_SERVICES
+ * @see #GET_SIGNATURES
+ * @see #GET_UNINSTALLED_PACKAGES
+ */
+ public abstract List<PackageInfo> getPackagesHoldingPermissions(
+ String[] permissions, int flags);
+
+ /**
* Return a List of all packages that are installed on the device, for a specific user.
* Requesting a list of installed packages for another user
* will require the permission INTERACT_ACROSS_USERS_FULL.
@@ -1726,14 +1813,14 @@ public abstract class PackageManager {
/**
* Return a List of all application packages that are installed on the
* device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all
- * applications including those deleted with DONT_DELETE_DATA(partially
+ * applications including those deleted with DONT_DELETE_DATA (partially
* installed apps with data directory) will be returned.
*
* @param flags Additional option flags. Use any combination of
* {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES},
* {@link #GET_UNINSTALLED_PACKAGES} to modify the data returned.
*
- * @return A List of ApplicationInfo objects, one for each application that
+ * @return Returns a List of ApplicationInfo objects, one for each application that
* is installed on the device. In the unlikely case of there being
* no installed applications, an empty list is returned.
* If flag GET_UNINSTALLED_PACKAGES is set, a list of all
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3e8c2a8..e1887bc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3527,29 +3527,45 @@ public class PackageParser {
return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
}
+ private static void updateApplicationInfo(ApplicationInfo ai, int flags,
+ PackageUserState state) {
+ // CompatibilityMode is global state.
+ if (!sCompatibilityModeEnabled) {
+ ai.disableCompatibilityMode();
+ }
+ if (state.installed) {
+ ai.flags |= ApplicationInfo.FLAG_INSTALLED;
+ } else {
+ ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
+ }
+ if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ ai.enabled = true;
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+ ai.enabled = (flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0;
+ } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+ ai.enabled = false;
+ }
+ ai.enabledSetting = state.enabled;
+ }
+
public static ApplicationInfo generateApplicationInfo(Package p, int flags,
PackageUserState state, int userId) {
if (p == null) return null;
if (!checkUseInstalled(flags, state)) {
return null;
}
- if (!copyNeeded(flags, p, state, null, userId)) {
- // CompatibilityMode is global state. It's safe to modify the instance
- // of the package.
- if (!sCompatibilityModeEnabled) {
- p.applicationInfo.disableCompatibilityMode();
- }
- // Make sure we report as installed. Also safe to do, since the
- // default state should be installed (we will always copy if we
- // need to report it is not installed).
- p.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
- if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
- p.applicationInfo.enabled = true;
- } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
- p.applicationInfo.enabled = false;
- }
- p.applicationInfo.enabledSetting = state.enabled;
+ if (!copyNeeded(flags, p, state, null, userId)
+ && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 0
+ || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
+ // In this case it is safe to directly modify the internal ApplicationInfo state:
+ // - CompatibilityMode is global state, so will be the same for every call.
+ // - We only come in to here if the app should reported as installed; this is the
+ // default state, and we will do a copy otherwise.
+ // - The enable state will always be reported the same for the application across
+ // calls; the only exception is for the UNTIL_USED mode, and in that case we will
+ // be doing a copy.
+ updateApplicationInfo(p.applicationInfo, flags, state);
return p.applicationInfo;
}
@@ -3565,26 +3581,12 @@ public class PackageParser {
if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != 0) {
ai.sharedLibraryFiles = p.usesLibraryFiles;
}
- if (!sCompatibilityModeEnabled) {
- ai.disableCompatibilityMode();
- }
if (state.stopped) {
ai.flags |= ApplicationInfo.FLAG_STOPPED;
} else {
ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
}
- if (state.installed) {
- ai.flags |= ApplicationInfo.FLAG_INSTALLED;
- } else {
- ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
- }
- if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
- ai.enabled = true;
- } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
- || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
- ai.enabled = false;
- }
- ai.enabledSetting = state.enabled;
+ updateApplicationInfo(ai, flags, state);
return ai;
}
diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java
index f3a98db..8a43472 100644
--- a/core/java/android/content/pm/ParceledListSlice.java
+++ b/core/java/android/content/pm/ParceledListSlice.java
@@ -16,44 +16,92 @@
package android.content.pm;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+import java.util.ArrayList;
import java.util.List;
/**
- * Builds up a parcel that is discarded when written to another parcel or
- * written to a list. This is useful for API that sends huge lists across a
- * Binder that may be larger than the IPC limit.
+ * Transfer a large list of Parcelable objects across an IPC. Splits into
+ * multiple transactions if needed.
*
* @hide
*/
public class ParceledListSlice<T extends Parcelable> implements Parcelable {
+ private static String TAG = "ParceledListSlice";
+ private static boolean DEBUG = false;
+
/*
* TODO get this number from somewhere else. For now set it to a quarter of
* the 1MB limit.
*/
private static final int MAX_IPC_SIZE = 256 * 1024;
+ private static final int MAX_FIRST_IPC_SIZE = MAX_IPC_SIZE / 2;
- private Parcel mParcel;
-
- private int mNumItems;
+ private final List<T> mList;
- private boolean mIsLastSlice;
+ public ParceledListSlice(List<T> list) {
+ mList = list;
+ }
- public ParceledListSlice() {
- mParcel = Parcel.obtain();
+ private ParceledListSlice(Parcel p, ClassLoader loader) {
+ final int N = p.readInt();
+ mList = new ArrayList<T>(N);
+ if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
+ if (N <= 0) {
+ return;
+ }
+ Parcelable.Creator<T> creator = p.readParcelableCreator(loader);
+ int i = 0;
+ while (i < N) {
+ if (p.readInt() == 0) {
+ break;
+ }
+ mList.add(p.readCreator(creator, loader));
+ if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ if (i >= N) {
+ return;
+ }
+ final IBinder retriever = p.readStrongBinder();
+ while (i < N) {
+ if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInt(i);
+ try {
+ retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
+ return;
+ }
+ while (i < N && reply.readInt() != 0) {
+ mList.add(reply.readCreator(creator, loader));
+ if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
+ i++;
+ }
+ reply.recycle();
+ data.recycle();
+ }
}
- private ParceledListSlice(Parcel p, int numItems, boolean lastSlice) {
- mParcel = p;
- mNumItems = numItems;
- mIsLastSlice = lastSlice;
+ public List<T> getList() {
+ return mList;
}
@Override
public int describeContents() {
- return 0;
+ int contents = 0;
+ for (int i=0; i<mList.size(); i++) {
+ contents |= mList.get(i).describeContents();
+ }
+ return contents;
}
/**
@@ -63,104 +111,59 @@ public class ParceledListSlice<T extends Parcelable> implements Parcelable {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mNumItems);
- dest.writeInt(mIsLastSlice ? 1 : 0);
-
- if (mNumItems > 0) {
- final int parcelSize = mParcel.dataSize();
- dest.writeInt(parcelSize);
- dest.appendFrom(mParcel, 0, parcelSize);
- }
-
- mNumItems = 0;
- mParcel.recycle();
- mParcel = null;
- }
-
- /**
- * Appends a parcel to this list slice.
- *
- * @param item Parcelable item to append to this list slice
- * @return true when the list slice is full and should not be appended to
- * anymore
- */
- public boolean append(T item) {
- if (mParcel == null) {
- throw new IllegalStateException("ParceledListSlice has already been recycled");
- }
-
- item.writeToParcel(mParcel, PARCELABLE_WRITE_RETURN_VALUE);
- mNumItems++;
-
- return mParcel.dataSize() > MAX_IPC_SIZE;
- }
-
- /**
- * Populates a list and discards the internal state of the
- * ParceledListSlice in the process. The instance should
- * not be used anymore.
- *
- * @param list list to insert items from this slice.
- * @param creator creator that knows how to unparcel the
- * target object type.
- * @return the last item inserted into the list or null if none.
- */
- public T populateList(List<T> list, Creator<T> creator) {
- mParcel.setDataPosition(0);
-
- T item = null;
- for (int i = 0; i < mNumItems; i++) {
- item = creator.createFromParcel(mParcel);
- list.add(item);
+ final int N = mList.size();
+ final int callFlags = flags;
+ dest.writeInt(N);
+ if (DEBUG) Log.d(TAG, "Writing " + N + " items");
+ if (N > 0) {
+ dest.writeParcelableCreator(mList.get(0));
+ int i = 0;
+ while (i < N && dest.dataSize() < MAX_FIRST_IPC_SIZE) {
+ dest.writeInt(1);
+ mList.get(i).writeToParcel(dest, callFlags);
+ if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ dest.writeInt(0);
+ Binder retriever = new Binder() {
+ @Override
+ protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code != FIRST_CALL_TRANSACTION) {
+ return super.onTransact(code, data, reply, flags);
+ }
+ int i = data.readInt();
+ if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
+ while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
+ reply.writeInt(1);
+ mList.get(i).writeToParcel(reply, callFlags);
+ if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
+ i++;
+ }
+ if (i < N) {
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
+ reply.writeInt(0);
+ }
+ return true;
+ }
+ };
+ if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
+ dest.writeStrongBinder(retriever);
+ }
}
-
- mParcel.recycle();
- mParcel = null;
-
- return item;
- }
-
- /**
- * Sets whether this is the last list slice in the series.
- *
- * @param lastSlice
- */
- public void setLastSlice(boolean lastSlice) {
- mIsLastSlice = lastSlice;
- }
-
- /**
- * Returns whether this is the last slice in a series of slices.
- *
- * @return true if this is the last slice in the series.
- */
- public boolean isLastSlice() {
- return mIsLastSlice;
}
@SuppressWarnings("unchecked")
- public static final Parcelable.Creator<ParceledListSlice> CREATOR =
- new Parcelable.Creator<ParceledListSlice>() {
+ public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
+ new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
public ParceledListSlice createFromParcel(Parcel in) {
- final int numItems = in.readInt();
- final boolean lastSlice = in.readInt() == 1;
-
- if (numItems > 0) {
- final int parcelSize = in.readInt();
-
- // Advance within this Parcel
- int offset = in.dataPosition();
- in.setDataPosition(offset + parcelSize);
-
- Parcel p = Parcel.obtain();
- p.setDataPosition(0);
- p.appendFrom(in, offset, parcelSize);
- p.setDataPosition(0);
+ return new ParceledListSlice(in, null);
+ }
- return new ParceledListSlice(p, numItems, lastSlice);
- } else {
- return new ParceledListSlice();
- }
+ @Override
+ public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
+ return new ParceledListSlice(in, loader);
}
public ParceledListSlice[] newArray(int size) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index b316f23..24a0bb5 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -77,7 +77,7 @@ public class Resources {
private static final int ID_OTHER = 0x01000004;
- private static final Object mSync = new Object();
+ private static final Object sSync = new Object();
/*package*/ static Resources mSystem = null;
// Information about preloaded resources. Note that they are not
@@ -92,17 +92,18 @@ public class Resources {
private static boolean sPreloaded;
private static int sPreloadedDensity;
- /*package*/ final TypedValue mTmpValue = new TypedValue();
- /*package*/ final Configuration mTmpConfig = new Configuration();
+ // These are protected by mAccessLock.
- // These are protected by the mTmpValue lock.
- private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
+ /*package*/ final Object mAccessLock = new Object();
+ /*package*/ final Configuration mTmpConfig = new Configuration();
+ /*package*/ TypedValue mTmpValue = new TypedValue();
+ /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
- private final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
+ /*package*/ final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
= new LongSparseArray<WeakReference<ColorStateList> >();
- private final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
+ /*package*/ final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
- private boolean mPreloading;
+ /*package*/ boolean mPreloading;
/*package*/ TypedArray mCachedStyledAttributes = null;
RuntimeException mLastRetrievedAttrs = null;
@@ -196,7 +197,7 @@ public class Resources {
* on orientation, etc).
*/
public static Resources getSystem() {
- synchronized (mSync) {
+ synchronized (sSync) {
Resources ret = mSystem;
if (ret == null) {
ret = new Resources();
@@ -266,7 +267,7 @@ public class Resources {
}
private NativePluralRules getPluralRule() {
- synchronized (mSync) {
+ synchronized (sSync) {
if (mPluralRule == null) {
mPluralRule = NativePluralRules.forLocale(mConfiguration.locale);
}
@@ -517,8 +518,11 @@ public class Resources {
* @see #getDimensionPixelSize
*/
public float getDimension(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, mMetrics);
@@ -549,8 +553,11 @@ public class Resources {
* @see #getDimensionPixelSize
*/
public int getDimensionPixelOffset(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(
@@ -583,8 +590,11 @@ public class Resources {
* @see #getDimensionPixelOffset
*/
public int getDimensionPixelSize(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
@@ -614,8 +624,11 @@ public class Resources {
* @throws NotFoundException Throws NotFoundException if the given ID does not exist.
*/
public float getFraction(int id, int base, int pbase) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_FRACTION) {
return TypedValue.complexToFraction(value.data, base, pbase);
@@ -654,11 +667,23 @@ public class Resources {
* @return Drawable An object that can be used to draw this resource.
*/
public Drawable getDrawable(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValue(id, value, true);
- return loadDrawable(value, id);
}
+ Drawable res = loadDrawable(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
+ }
+ return res;
}
/**
@@ -681,8 +706,14 @@ public class Resources {
* @return Drawable An object that can be used to draw this resource.
*/
public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValueForDensity(id, density, value, true);
/*
@@ -699,9 +730,15 @@ public class Resources {
value.density = (value.density * mMetrics.densityDpi) / density;
}
}
+ }
- return loadDrawable(value, id);
+ Drawable res = loadDrawable(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
}
+ return res;
}
/**
@@ -739,20 +776,31 @@ public class Resources {
* @return Returns a single color value in the form 0xAARRGGBB.
*/
public int getColor(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
+ mTmpValue = value;
return value.data;
- } else if (value.type == TypedValue.TYPE_STRING) {
- ColorStateList csl = loadColorStateList(mTmpValue, id);
- return csl.getDefaultColor();
+ } else if (value.type != TypedValue.TYPE_STRING) {
+ throw new NotFoundException(
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
+ }
+ mTmpValue = null;
+ }
+ ColorStateList csl = loadColorStateList(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
}
- throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
}
+ return csl.getDefaultColor();
}
/**
@@ -770,11 +818,23 @@ public class Resources {
* solid color or multiple colors that can be selected based on a state.
*/
public ColorStateList getColorStateList(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValue(id, value, true);
- return loadColorStateList(value, id);
}
+ ColorStateList res = loadColorStateList(value, id);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
+ }
+ return res;
}
/**
@@ -791,8 +851,11 @@ public class Resources {
* @return Returns the boolean value contained in the resource.
*/
public boolean getBoolean(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
@@ -816,8 +879,11 @@ public class Resources {
* @return Returns the integer value contained in the resource.
*/
public int getInteger(int id) throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
@@ -917,9 +983,22 @@ public class Resources {
*
*/
public InputStream openRawResource(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- return openRawResource(id, mTmpValue);
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
+ }
+ InputStream res = openRawResource(id, value);
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
}
+ return res;
}
/**
@@ -971,22 +1050,32 @@ public class Resources {
*
*/
public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
- synchronized (mTmpValue) {
- TypedValue value = mTmpValue;
+ TypedValue value;
+ synchronized (mAccessLock) {
+ value = mTmpValue;
+ if (value == null) {
+ value = new TypedValue();
+ } else {
+ mTmpValue = null;
+ }
getValue(id, value, true);
-
- try {
- return mAssets.openNonAssetFd(
- value.assetCookie, value.string.toString());
- } catch (Exception e) {
- NotFoundException rnf = new NotFoundException(
- "File " + value.string.toString()
- + " from drawable resource ID #0x"
- + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
+ }
+ try {
+ return mAssets.openNonAssetFd(
+ value.assetCookie, value.string.toString());
+ } catch (Exception e) {
+ NotFoundException rnf = new NotFoundException(
+ "File " + value.string.toString()
+ + " from drawable resource ID #0x"
+ + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ } finally {
+ synchronized (mAccessLock) {
+ if (mTmpValue == null) {
+ mTmpValue = value;
+ }
}
-
}
}
@@ -1118,7 +1207,7 @@ public class Resources {
}
/**
- * Return a StyledAttributes holding the values defined by
+ * Return a TypedArray holding the values defined by
* <var>Theme</var> which are listed in <var>attrs</var>.
*
* <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done
@@ -1146,7 +1235,7 @@ public class Resources {
}
/**
- * Return a StyledAttributes holding the values defined by the style
+ * Return a TypedArray holding the values defined by the style
* resource <var>resid</var> which are listed in <var>attrs</var>.
*
* <p>Be sure to call {@link TypedArray#recycle() TypedArray.recycle()} when you are done
@@ -1203,7 +1292,7 @@ public class Resources {
}
/**
- * Return a StyledAttributes holding the attribute values in
+ * Return a TypedArray holding the attribute values in
* <var>set</var>
* that are listed in <var>attrs</var>. In addition, if the given
* AttributeSet specifies a style class (through the "style" attribute),
@@ -1235,10 +1324,10 @@ public class Resources {
* @param attrs The desired attributes to be retrieved.
* @param defStyleAttr An attribute in the current theme that contains a
* reference to a style resource that supplies
- * defaults values for the StyledAttributes. Can be
+ * defaults values for the TypedArray. Can be
* 0 to not look for defaults.
* @param defStyleRes A resource identifier of a style resource that
- * supplies default values for the StyledAttributes,
+ * supplies default values for the TypedArray,
* used only if defStyleAttr is 0 or can not be found
* in the theme. Can be 0 to not look for defaults.
*
@@ -1407,7 +1496,7 @@ public class Resources {
*/
public void updateConfiguration(Configuration config,
DisplayMetrics metrics, CompatibilityInfo compat) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
if (false) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is " + mCompatibilityInfo);
@@ -1497,21 +1586,21 @@ public class Resources {
+ " final compat is " + mCompatibilityInfo);
}
- clearDrawableCache(mDrawableCache, configChanges);
- clearDrawableCache(mColorDrawableCache, configChanges);
+ clearDrawableCacheLocked(mDrawableCache, configChanges);
+ clearDrawableCacheLocked(mColorDrawableCache, configChanges);
mColorStateListCache.clear();
flushLayoutCache();
}
- synchronized (mSync) {
+ synchronized (sSync) {
if (mPluralRule != null) {
mPluralRule = NativePluralRules.forLocale(config.locale);
}
}
}
- private void clearDrawableCache(
+ private void clearDrawableCacheLocked(
LongSparseArray<WeakReference<ConstantState>> cache,
int configChanges) {
int N = cache.size();
@@ -1631,6 +1720,9 @@ public class Resources {
* resource was found. (0 is not a valid resource ID.)
*/
public int getIdentifier(String name, String defType, String defPackage) {
+ if (name == null) {
+ throw new NullPointerException("name is null");
+ }
try {
return Integer.parseInt(name);
} catch (Exception e) {
@@ -1846,7 +1938,7 @@ public class Resources {
* {@hide}
*/
public final void startPreloading() {
- synchronized (mSync) {
+ synchronized (sSync) {
if (sPreloaded) {
throw new IllegalStateException("Resources already preloaded");
}
@@ -1990,7 +2082,7 @@ public class Resources {
}
}
} else {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
//Log.i(TAG, "Saving cached drawable @ #" +
// Integer.toHexString(key.intValue())
// + " in " + this + ": " + cs);
@@ -2010,7 +2102,7 @@ public class Resources {
private Drawable getCachedDrawable(
LongSparseArray<WeakReference<ConstantState>> drawableCache,
long key) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
WeakReference<Drawable.ConstantState> wr = drawableCache.get(key);
if (wr != null) { // we have the key
Drawable.ConstantState entry = wr.get();
@@ -2102,7 +2194,7 @@ public class Resources {
sPreloadedColorStateLists.put(key, csl);
}
} else {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
//Log.i(TAG, "Saving cached color state list @ #" +
// Integer.toHexString(key.intValue())
// + " in " + this + ": " + csl);
@@ -2115,7 +2207,7 @@ public class Resources {
}
private ColorStateList getCachedColorStateList(long key) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
if (wr != null) { // we have the key
ColorStateList entry = wr.get();
@@ -2134,8 +2226,11 @@ public class Resources {
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedValue value = mTmpValue;
+ if (value == null) {
+ mTmpValue = value = new TypedValue();
+ }
getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
@@ -2197,7 +2292,7 @@ public class Resources {
}
private TypedArray getCachedStyledAttributes(int len) {
- synchronized (mTmpValue) {
+ synchronized (mAccessLock) {
TypedArray attrs = mCachedStyledAttributes;
if (attrs != null) {
mCachedStyledAttributes = null;
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 63e33ce..78180b1 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.graphics.Color;
import android.text.*;
import android.text.style.*;
import android.util.Log;
@@ -24,7 +25,7 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
-import com.android.internal.util.XmlUtils;
+import java.util.Arrays;
/**
* Conveniences for retrieving data out of a compiled string resource.
@@ -33,7 +34,7 @@ import com.android.internal.util.XmlUtils;
*/
final class StringBlock {
private static final String TAG = "AssetManager";
- private static final boolean localLOGV = false || false;
+ private static final boolean localLOGV = false;
private final int mNative;
private final boolean mUseSparse;
@@ -82,7 +83,7 @@ final class StringBlock {
CharSequence res = str;
int[] style = nativeGetStyle(mNative, idx);
if (localLOGV) Log.v(TAG, "Got string: " + str);
- if (localLOGV) Log.v(TAG, "Got styles: " + style);
+ if (localLOGV) Log.v(TAG, "Got styles: " + Arrays.toString(style));
if (style != null) {
if (mStyleIDs == null) {
mStyleIDs = new StyleIDs();
@@ -139,8 +140,12 @@ final class StringBlock {
}
protected void finalize() throws Throwable {
- if (mOwnsNative) {
- nativeDestroy(mNative);
+ try {
+ super.finalize();
+ } finally {
+ if (mOwnsNative) {
+ nativeDestroy(mNative);
+ }
}
}
@@ -236,19 +241,31 @@ final class StringBlock {
sub = subtag(tag, ";fgcolor=");
if (sub != null) {
- int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
- buffer.setSpan(new ForegroundColorSpan(color),
+ buffer.setSpan(getColor(sub, true),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ sub = subtag(tag, ";color=");
+ if (sub != null) {
+ buffer.setSpan(getColor(sub, true),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+
sub = subtag(tag, ";bgcolor=");
if (sub != null) {
- int color = XmlUtils.convertValueToUnsignedInt(sub, -1);
- buffer.setSpan(new BackgroundColorSpan(color),
+ buffer.setSpan(getColor(sub, false),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+
+ sub = subtag(tag, ";face=");
+ if (sub != null) {
+ buffer.setSpan(new TypefaceSpan(sub),
+ style[i+1], style[i+2]+1,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
} else if (tag.startsWith("a;")) {
String sub;
@@ -289,6 +306,48 @@ final class StringBlock {
}
/**
+ * Returns a span for the specified color string representation.
+ * If the specified string does not represent a color (null, empty, etc.)
+ * the color black is returned instead.
+ *
+ * @param color The color as a string. Can be a resource reference,
+ * HTML hexadecimal, octal or a name
+ * @param foreground True if the color will be used as the foreground color,
+ * false otherwise
+ *
+ * @return A CharacterStyle
+ *
+ * @see Color#getHtmlColor(String)
+ */
+ private static CharacterStyle getColor(String color, boolean foreground) {
+ int c = 0xff000000;
+
+ if (!TextUtils.isEmpty(color)) {
+ if (color.startsWith("@")) {
+ Resources res = Resources.getSystem();
+ String name = color.substring(1);
+ int colorRes = res.getIdentifier(name, "color", "android");
+ if (colorRes != 0) {
+ ColorStateList colors = res.getColorStateList(colorRes);
+ if (foreground) {
+ return new TextAppearanceSpan(null, 0, 0, colors, null);
+ } else {
+ c = colors.getDefaultColor();
+ }
+ }
+ } else {
+ c = Color.getHtmlColor(color);
+ }
+ }
+
+ if (foreground) {
+ return new ForegroundColorSpan(c);
+ } else {
+ return new BackgroundColorSpan(c);
+ }
+ }
+
+ /**
* If a translator has messed up the edges of paragraph-level markup,
* fix it to actually cover the entire paragraph that it is attached to
* instead of just whatever range they put it on.
@@ -423,11 +482,11 @@ final class StringBlock {
+ ": " + nativeGetSize(mNative));
}
- private static final native int nativeCreate(byte[] data,
+ private static native int nativeCreate(byte[] data,
int offset,
int size);
- private static final native int nativeGetSize(int obj);
- private static final native String nativeGetString(int obj, int idx);
- private static final native int[] nativeGetStyle(int obj, int idx);
- private static final native void nativeDestroy(int obj);
+ private static native int nativeGetSize(int obj);
+ private static native String nativeGetString(int obj, int idx);
+ private static native int[] nativeGetStyle(int obj, int idx);
+ private static native void nativeDestroy(int obj);
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 2968fbb..27dddd4 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -687,7 +687,7 @@ public class TypedArray {
* Give back a previously retrieved array, for later re-use.
*/
public void recycle() {
- synchronized (mResources.mTmpValue) {
+ synchronized (mResources.mAccessLock) {
TypedArray cached = mResources.mCachedStyledAttributes;
if (cached == null || cached.mData.length < mData.length) {
mXml = null;
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index 525be96..82a61d4 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -132,6 +132,11 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
}
+ /**
+ * Returns an object that contains sufficient metadata to reconstruct
+ * the cursor remotely. May throw if an error occurs when executing the query
+ * and obtaining the row count.
+ */
public BulkCursorDescriptor getBulkCursorDescriptor() {
synchronized (mLock) {
throwIfCursorIsClosed();
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index b29897e..5a1a8e2 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -138,17 +138,26 @@ public class SQLiteCursor extends AbstractWindowedCursor {
private void fillWindow(int requiredPos) {
clearOrCreateWindow(getDatabase().getPath());
- if (mCount == NO_COUNT) {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
- mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
- mCursorWindowCapacity = mWindow.getNumRows();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
+ try {
+ if (mCount == NO_COUNT) {
+ int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
+ mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
+ mCursorWindowCapacity = mWindow.getNumRows();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
+ }
+ } else {
+ int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
+ mCursorWindowCapacity);
+ mQuery.fillWindow(mWindow, startPos, requiredPos, false);
}
- } else {
- int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
- mCursorWindowCapacity);
- mQuery.fillWindow(mWindow, startPos, requiredPos, false);
+ } catch (RuntimeException ex) {
+ // Close the cursor window if the query failed and therefore will
+ // not produce any results. This helps to avoid accidentally leaking
+ // the cursor window if the client does not correctly handle exceptions
+ // and fails to close the cursor.
+ closeWindow();
+ throw ex;
}
}
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index e99fa92..842a482 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -36,6 +36,10 @@ public class DdmHandleHello extends ChunkHandler {
private static DdmHandleHello mInstance = new DdmHandleHello();
+ private static final String[] FRAMEWORK_FEATURES = new String[] {
+ "opengl-tracing",
+ "view-hierarchy",
+ };
/* singleton, do not instantiate */
private DdmHandleHello() {}
@@ -149,21 +153,27 @@ public class DdmHandleHello extends ChunkHandler {
private Chunk handleFEAT(Chunk request) {
// TODO: query the VM to ensure that support for these features
// is actually compiled in
- final String[] features = Debug.getVmFeatureList();
+ final String[] vmFeatures = Debug.getVmFeatureList();
if (false)
Log.v("ddm-heap", "Got feature list request");
- int size = 4 + 4 * features.length;
- for (int i = features.length-1; i >= 0; i--)
- size += features[i].length() * 2;
+ int size = 4 + 4 * (vmFeatures.length + FRAMEWORK_FEATURES.length);
+ for (int i = vmFeatures.length-1; i >= 0; i--)
+ size += vmFeatures[i].length() * 2;
+ for (int i = FRAMEWORK_FEATURES.length-1; i>= 0; i--)
+ size += FRAMEWORK_FEATURES[i].length() * 2;
ByteBuffer out = ByteBuffer.allocate(size);
out.order(ChunkHandler.CHUNK_ORDER);
- out.putInt(features.length);
- for (int i = features.length-1; i >= 0; i--) {
- out.putInt(features[i].length());
- putString(out, features[i]);
+ out.putInt(vmFeatures.length + FRAMEWORK_FEATURES.length);
+ for (int i = vmFeatures.length-1; i >= 0; i--) {
+ out.putInt(vmFeatures[i].length());
+ putString(out, vmFeatures[i]);
+ }
+ for (int i = FRAMEWORK_FEATURES.length-1; i >= 0; i--) {
+ out.putInt(FRAMEWORK_FEATURES[i].length());
+ putString(out, FRAMEWORK_FEATURES[i]);
}
return new Chunk(CHUNK_FEAT, out);
diff --git a/core/java/android/ddm/DdmHandleViewDebug.java b/core/java/android/ddm/DdmHandleViewDebug.java
new file mode 100644
index 0000000..ce83796
--- /dev/null
+++ b/core/java/android/ddm/DdmHandleViewDebug.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2013 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.ddm;
+
+import android.opengl.GLUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+
+import org.apache.harmony.dalvik.ddmc.Chunk;
+import org.apache.harmony.dalvik.ddmc.ChunkHandler;
+import org.apache.harmony.dalvik.ddmc.DdmServer;
+
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Method;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * Handle various requests related to profiling / debugging of the view system.
+ * Support for these features are advertised via {@link DdmHandleHello}.
+ */
+public class DdmHandleViewDebug extends ChunkHandler {
+ /** Enable/Disable tracing of OpenGL calls. */
+ public static final int CHUNK_VUGL = type("VUGL");
+
+ /** List {@link ViewRootImpl}'s of this process. */
+ private static final int CHUNK_VULW = type("VULW");
+
+ /** Operation on view root, first parameter in packet should be one of VURT_* constants */
+ private static final int CHUNK_VURT = type("VURT");
+
+ /** Dump view hierarchy. */
+ private static final int VURT_DUMP_HIERARCHY = 1;
+
+ /** Capture View Layers. */
+ private static final int VURT_CAPTURE_LAYERS = 2;
+
+ /**
+ * Generic View Operation, first parameter in the packet should be one of the
+ * VUOP_* constants below.
+ */
+ private static final int CHUNK_VUOP = type("VUOP");
+
+ /** Capture View. */
+ private static final int VUOP_CAPTURE_VIEW = 1;
+
+ /** Obtain the Display List corresponding to the view. */
+ private static final int VUOP_DUMP_DISPLAYLIST = 2;
+
+ /** Profile a view. */
+ private static final int VUOP_PROFILE_VIEW = 3;
+
+ /** Invoke a method on the view. */
+ private static final int VUOP_INVOKE_VIEW_METHOD = 4;
+
+ /** Set layout parameter. */
+ private static final int VUOP_SET_LAYOUT_PARAMETER = 5;
+
+ /** Error code indicating operation specified in chunk is invalid. */
+ private static final int ERR_INVALID_OP = -1;
+
+ /** Error code indicating that the parameters are invalid. */
+ private static final int ERR_INVALID_PARAM = -2;
+
+ /** Error code indicating an exception while performing operation. */
+ private static final int ERR_EXCEPTION = -3;
+
+ private static final String TAG = "DdmViewDebug";
+
+ private static final DdmHandleViewDebug sInstance = new DdmHandleViewDebug();
+
+ /** singleton, do not instantiate. */
+ private DdmHandleViewDebug() {}
+
+ public static void register() {
+ DdmServer.registerHandler(CHUNK_VUGL, sInstance);
+ DdmServer.registerHandler(CHUNK_VULW, sInstance);
+ DdmServer.registerHandler(CHUNK_VURT, sInstance);
+ DdmServer.registerHandler(CHUNK_VUOP, sInstance);
+ }
+
+ @Override
+ public void connected() {
+ }
+
+ @Override
+ public void disconnected() {
+ }
+
+ @Override
+ public Chunk handleChunk(Chunk request) {
+ int type = request.type;
+
+ if (type == CHUNK_VUGL) {
+ return handleOpenGlTrace(request);
+ } else if (type == CHUNK_VULW) {
+ return listWindows();
+ }
+
+ ByteBuffer in = wrapChunk(request);
+ int op = in.getInt();
+
+ View rootView = getRootView(in);
+ if (rootView == null) {
+ return createFailChunk(ERR_INVALID_PARAM, "Invalid View Root");
+ }
+
+ if (type == CHUNK_VURT) {
+ if (op == VURT_DUMP_HIERARCHY)
+ return dumpHierarchy(rootView, in);
+ else if (op == VURT_CAPTURE_LAYERS)
+ return captureLayers(rootView);
+ else
+ return createFailChunk(ERR_INVALID_OP, "Unknown view root operation: " + op);
+ }
+
+ final View targetView = getTargetView(rootView, in);
+ if (targetView == null) {
+ return createFailChunk(ERR_INVALID_PARAM, "Invalid target view");
+ }
+
+ if (type == CHUNK_VUOP) {
+ switch (op) {
+ case VUOP_CAPTURE_VIEW:
+ return captureView(rootView, targetView);
+ case VUOP_DUMP_DISPLAYLIST:
+ return dumpDisplayLists(rootView, targetView);
+ case VUOP_PROFILE_VIEW:
+ return profileView(rootView, targetView);
+ case VUOP_INVOKE_VIEW_METHOD:
+ return invokeViewMethod(rootView, targetView, in);
+ case VUOP_SET_LAYOUT_PARAMETER:
+ return setLayoutParameter(rootView, targetView, in);
+ default:
+ return createFailChunk(ERR_INVALID_OP, "Unknown view operation: " + op);
+ }
+ } else {
+ throw new RuntimeException("Unknown packet " + ChunkHandler.name(type));
+ }
+ }
+
+ private Chunk handleOpenGlTrace(Chunk request) {
+ ByteBuffer in = wrapChunk(request);
+ GLUtils.setTracingLevel(in.getInt());
+ return null; // empty response
+ }
+
+ /** Returns the list of windows owned by this client. */
+ private Chunk listWindows() {
+ String[] windowNames = WindowManagerGlobal.getInstance().getViewRootNames();
+
+ int responseLength = 4; // # of windows
+ for (String name : windowNames) {
+ responseLength += 4; // length of next window name
+ responseLength += name.length() * 2; // window name
+ }
+
+ ByteBuffer out = ByteBuffer.allocate(responseLength);
+ out.order(ChunkHandler.CHUNK_ORDER);
+
+ out.putInt(windowNames.length);
+ for (String name : windowNames) {
+ out.putInt(name.length());
+ putString(out, name);
+ }
+
+ return new Chunk(CHUNK_VULW, out);
+ }
+
+ private View getRootView(ByteBuffer in) {
+ try {
+ int viewRootNameLength = in.getInt();
+ String viewRootName = getString(in, viewRootNameLength);
+ return WindowManagerGlobal.getInstance().getRootView(viewRootName);
+ } catch (BufferUnderflowException e) {
+ return null;
+ }
+ }
+
+ private View getTargetView(View root, ByteBuffer in) {
+ int viewLength;
+ String viewName;
+
+ try {
+ viewLength = in.getInt();
+ viewName = getString(in, viewLength);
+ } catch (BufferUnderflowException e) {
+ return null;
+ }
+
+ return ViewDebug.findView(root, viewName);
+ }
+
+ /**
+ * Returns the view hierarchy and/or view properties starting at the provided view.
+ * Based on the input options, the return data may include:
+ * - just the view hierarchy
+ * - view hierarchy & the properties for each of the views
+ * - just the view properties for a specific view.
+ * TODO: Currently this only returns views starting at the root, need to fix so that
+ * it can return properties of any view.
+ */
+ private Chunk dumpHierarchy(View rootView, ByteBuffer in) {
+ boolean skipChildren = in.getInt() > 0;
+ boolean includeProperties = in.getInt() > 0;
+
+ ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
+ try {
+ ViewDebug.dump(rootView, skipChildren, includeProperties, b);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
+ + e.getMessage());
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VURT, data, 0, data.length);
+ }
+
+ /** Returns a buffer with region details & bitmap of every single view. */
+ private Chunk captureLayers(View rootView) {
+ ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
+ DataOutputStream dos = new DataOutputStream(b);
+ try {
+ ViewDebug.captureLayers(rootView, dos);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while obtaining view hierarchy: "
+ + e.getMessage());
+ } finally {
+ try {
+ dos.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VURT, data, 0, data.length);
+ }
+
+ private Chunk captureView(View rootView, View targetView) {
+ ByteArrayOutputStream b = new ByteArrayOutputStream(1024);
+ try {
+ ViewDebug.capture(rootView, b, targetView);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while capturing view: "
+ + e.getMessage());
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VUOP, data, 0, data.length);
+ }
+
+ /** Returns the display lists corresponding to the provided view. */
+ private Chunk dumpDisplayLists(final View rootView, final View targetView) {
+ rootView.post(new Runnable() {
+ @Override
+ public void run() {
+ ViewDebug.outputDisplayList(rootView, targetView);
+ }
+ });
+ return null;
+ }
+
+ /**
+ * Invokes provided method on the view.
+ * The method name and its arguments are passed in as inputs via the byte buffer.
+ * The buffer contains:<ol>
+ * <li> len(method name) </li>
+ * <li> method name </li>
+ * <li> # of args </li>
+ * <li> arguments: Each argument comprises of a type specifier followed by the actual argument.
+ * The type specifier is a single character as used in JNI:
+ * (Z - boolean, B - byte, C - char, S - short, I - int, J - long,
+ * F - float, D - double). <p>
+ * The type specifier is followed by the actual value of argument.
+ * Booleans are encoded via bytes with 0 indicating false.</li>
+ * </ol>
+ * Methods that take no arguments need only specify the method name.
+ */
+ private Chunk invokeViewMethod(final View rootView, final View targetView, ByteBuffer in) {
+ int l = in.getInt();
+ String methodName = getString(in, l);
+
+ Class<?>[] argTypes;
+ Object[] args;
+ if (!in.hasRemaining()) {
+ argTypes = new Class<?>[0];
+ args = new Object[0];
+ } else {
+ int nArgs = in.getInt();
+
+ argTypes = new Class<?>[nArgs];
+ args = new Object[nArgs];
+
+ for (int i = 0; i < nArgs; i++) {
+ char c = in.getChar();
+ switch (c) {
+ case 'Z':
+ argTypes[i] = boolean.class;
+ args[i] = in.get() == 0 ? false : true;
+ break;
+ case 'B':
+ argTypes[i] = byte.class;
+ args[i] = in.get();
+ break;
+ case 'C':
+ argTypes[i] = char.class;
+ args[i] = in.getChar();
+ break;
+ case 'S':
+ argTypes[i] = short.class;
+ args[i] = in.getShort();
+ break;
+ case 'I':
+ argTypes[i] = int.class;
+ args[i] = in.getInt();
+ break;
+ case 'J':
+ argTypes[i] = long.class;
+ args[i] = in.getLong();
+ break;
+ case 'F':
+ argTypes[i] = float.class;
+ args[i] = in.getFloat();
+ break;
+ case 'D':
+ argTypes[i] = double.class;
+ args[i] = in.getDouble();
+ break;
+ default:
+ Log.e(TAG, "arg " + i + ", unrecognized type: " + c);
+ return createFailChunk(ERR_INVALID_PARAM,
+ "Unsupported parameter type (" + c + ") to invoke view method.");
+ }
+ }
+ }
+
+ Method method = null;
+ try {
+ method = targetView.getClass().getMethod(methodName, argTypes);
+ } catch (NoSuchMethodException e) {
+ Log.e(TAG, "No such method: " + e.getMessage());
+ return createFailChunk(ERR_INVALID_PARAM,
+ "No such method: " + e.getMessage());
+ }
+
+ try {
+ ViewDebug.invokeViewMethod(targetView, method, args);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while invoking method: " + e.getCause().getMessage());
+ String msg = e.getCause().getMessage();
+ if (msg == null) {
+ msg = e.getCause().toString();
+ }
+ return createFailChunk(ERR_EXCEPTION, msg);
+ }
+
+ return null;
+ }
+
+ private Chunk setLayoutParameter(final View rootView, final View targetView, ByteBuffer in) {
+ int l = in.getInt();
+ String param = getString(in, l);
+ int value = in.getInt();
+ try {
+ ViewDebug.setLayoutParameter(targetView, param, value);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception setting layout parameter: " + e);
+ return createFailChunk(ERR_EXCEPTION, "Error accessing field "
+ + param + ":" + e.getMessage());
+ }
+
+ return null;
+ }
+
+ /** Profiles provided view. */
+ private Chunk profileView(View rootView, final View targetView) {
+ ByteArrayOutputStream b = new ByteArrayOutputStream(32 * 1024);
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(b), 32 * 1024);
+ try {
+ ViewDebug.profileViewAndChildren(targetView, bw);
+ } catch (IOException e) {
+ return createFailChunk(1, "Unexpected error while profiling view: " + e.getMessage());
+ } finally {
+ try {
+ bw.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ byte[] data = b.toByteArray();
+ return new Chunk(CHUNK_VUOP, data, 0, data.length);
+ }
+}
diff --git a/core/java/android/ddm/DdmRegister.java b/core/java/android/ddm/DdmRegister.java
index ecd450d..e0faa51 100644
--- a/core/java/android/ddm/DdmRegister.java
+++ b/core/java/android/ddm/DdmRegister.java
@@ -51,6 +51,7 @@ public class DdmRegister {
DdmHandleNativeHeap.register();
DdmHandleProfiling.register();
DdmHandleExit.register();
+ DdmHandleViewDebug.register();
DdmServer.registrationComplete();
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 483e9de..4e51080 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -16,6 +16,7 @@
package android.hardware;
+import android.app.ActivityThread;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
@@ -337,7 +338,9 @@ public class Camera {
mEventHandler = null;
}
- native_setup(new WeakReference<Camera>(this), cameraId);
+ String packageName = ActivityThread.currentPackageName();
+
+ native_setup(new WeakReference<Camera>(this), cameraId, packageName);
}
/**
@@ -350,7 +353,9 @@ public class Camera {
release();
}
- private native final void native_setup(Object camera_this, int cameraId);
+ private native final void native_setup(Object camera_this, int cameraId,
+ String packageName);
+
private native final void native_release();
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index e0c9d2c..41384d2 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -194,7 +194,8 @@ public final class Sensor {
return mMinDelay;
}
- int getHandle() {
+ /** @hide */
+ public int getHandle() {
return mHandle;
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index b8ad818..c0d2fae 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -1314,56 +1314,4 @@ public abstract class SensorManager {
return mLegacySensorManager;
}
}
-
- /**
- * Sensor event pool implementation.
- * @hide
- */
- protected static final class SensorEventPool {
- private final int mPoolSize;
- private final SensorEvent mPool[];
- private int mNumItemsInPool;
-
- private SensorEvent createSensorEvent() {
- // maximal size for all legacy events is 3
- return new SensorEvent(3);
- }
-
- SensorEventPool(int poolSize) {
- mPoolSize = poolSize;
- mNumItemsInPool = poolSize;
- mPool = new SensorEvent[poolSize];
- }
-
- SensorEvent getFromPool() {
- SensorEvent t = null;
- synchronized (this) {
- if (mNumItemsInPool > 0) {
- // remove the "top" item from the pool
- final int index = mPoolSize - mNumItemsInPool;
- t = mPool[index];
- mPool[index] = null;
- mNumItemsInPool--;
- }
- }
- if (t == null) {
- // the pool was empty or this item was removed from the pool for
- // the first time. In any case, we need to create a new item.
- t = createSensorEvent();
- }
- return t;
- }
-
- void returnToPool(SensorEvent t) {
- synchronized (this) {
- // is there space left in the pool?
- if (mNumItemsInPool < mPoolSize) {
- // if so, return the item to the pool
- mNumItemsInPool++;
- final int index = mPoolSize - mNumItemsInPool;
- mPool[index] = t;
- }
- }
- }
- }
}
diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java
index 5ef122b..f50cdef 100644
--- a/core/java/android/hardware/SerialPort.java
+++ b/core/java/android/hardware/SerialPort.java
@@ -82,7 +82,9 @@ public class SerialPort {
}
/**
- * Reads data into the provided buffer
+ * Reads data into the provided buffer.
+ * Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is
+ * unchanged after a call to this method.
*
* @param buffer to read into
* @return number of bytes read
@@ -98,7 +100,9 @@ public class SerialPort {
}
/**
- * Writes data from provided buffer
+ * Writes data from provided buffer.
+ * Note that the value returned by {@link java.nio.Buffer#position()} on this buffer is
+ * unchanged after a call to this method.
*
* @param buffer to write
* @param length number of bytes to write
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 7375e7d..9591631 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,18 +16,19 @@
package android.hardware;
-import android.os.Looper;
-import android.os.Process;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+
+import dalvik.system.CloseGuard;
+
import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
+import android.os.Looper;
+import android.os.MessageQueue;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Sensor manager implementation that communicates with the built-in
* system sensors.
@@ -35,236 +36,43 @@ import java.util.List;
* @hide
*/
public class SystemSensorManager extends SensorManager {
- private static final int SENSOR_DISABLE = -1;
+ private static native void nativeClassInit();
+ private static native int nativeGetNextSensor(Sensor sensor, int next);
+
private static boolean sSensorModuleInitialized = false;
- private static ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
- /* The thread and the sensor list are global to the process
- * but the actual thread is spawned on demand */
- private static SensorThread sSensorThread;
- private static int sQueue;
+ private static final Object sSensorModuleLock = new Object();
+ private static final ArrayList<Sensor> sFullSensorsList = new ArrayList<Sensor>();
+ private static final SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
- // Used within this module from outside SensorManager, don't make private
- static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>();
- static final ArrayList<ListenerDelegate> sListeners =
- new ArrayList<ListenerDelegate>();
+ // Listener list
+ private final ArrayList<SensorEventListenerSensorPair> mListenerDelegates = new ArrayList<SensorEventListenerSensorPair>();
// Common pool of sensor events.
- static SensorEventPool sPool;
+ private static SensorEventPool sPool;
// Looper associated with the context in which this instance was created.
- final Looper mMainLooper;
-
- /*-----------------------------------------------------------------------*/
-
- static private class SensorThread {
-
- Thread mThread;
- boolean mSensorsReady;
-
- SensorThread() {
- }
-
- @Override
- protected void finalize() {
- }
-
- // must be called with sListeners lock
- boolean startLocked() {
- try {
- if (mThread == null) {
- mSensorsReady = false;
- SensorThreadRunnable runnable = new SensorThreadRunnable();
- Thread thread = new Thread(runnable, SensorThread.class.getName());
- thread.start();
- synchronized (runnable) {
- while (mSensorsReady == false) {
- runnable.wait();
- }
- }
- mThread = thread;
- }
- } catch (InterruptedException e) {
- }
- return mThread == null ? false : true;
- }
-
- private class SensorThreadRunnable implements Runnable {
- SensorThreadRunnable() {
- }
+ private final Looper mMainLooper;
- private boolean open() {
- // NOTE: this cannot synchronize on sListeners, since
- // it's held in the main thread at least until we
- // return from here.
- sQueue = sensors_create_queue();
- return true;
- }
+ // maps a SensorEventListener to a SensorEventQueue
+ private final Hashtable<SensorEventListener, SensorEventQueue> mSensorEventQueueMap;
- public void run() {
- //Log.d(TAG, "entering main sensor thread");
- final float[] values = new float[3];
- final int[] status = new int[1];
- final long timestamp[] = new long[1];
- Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
-
- if (!open()) {
- return;
- }
-
- synchronized (this) {
- // we've open the driver, we're ready to open the sensors
- mSensorsReady = true;
- this.notify();
- }
-
- while (true) {
- // wait for an event
- final int sensor = sensors_data_poll(sQueue, values, status, timestamp);
-
- int accuracy = status[0];
- synchronized (sListeners) {
- if (sensor == -1 || sListeners.isEmpty()) {
- // we lost the connection to the event stream. this happens
- // when the last listener is removed or if there is an error
- if (sensor == -1 && !sListeners.isEmpty()) {
- // log a warning in case of abnormal termination
- Log.e(TAG, "_sensors_data_poll() failed, we bail out: sensors=" + sensor);
- }
- // we have no more listeners or polling failed, terminate the thread
- sensors_destroy_queue(sQueue);
- sQueue = 0;
- mThread = null;
- break;
- }
- final Sensor sensorObject = sHandleToSensor.get(sensor);
- if (sensorObject != null) {
- // report the sensor event to all listeners that
- // care about it.
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate listener = sListeners.get(i);
- if (listener.hasSensor(sensorObject)) {
- // this is asynchronous (okay to call
- // with sListeners lock held).
- listener.onSensorChangedLocked(sensorObject,
- values, timestamp, accuracy);
- }
- }
- }
- }
- }
- //Log.d(TAG, "exiting main sensor thread");
- }
- }
- }
-
- /*-----------------------------------------------------------------------*/
-
- private class ListenerDelegate {
- private final SensorEventListener mSensorEventListener;
- private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>();
- private final Handler mHandler;
- public SparseBooleanArray mSensors = new SparseBooleanArray();
- public SparseBooleanArray mFirstEvent = new SparseBooleanArray();
- public SparseIntArray mSensorAccuracies = new SparseIntArray();
-
- ListenerDelegate(SensorEventListener listener, Sensor sensor, Handler handler) {
- mSensorEventListener = listener;
- Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
- // currently we create one Handler instance per listener, but we could
- // have one per looper (we'd need to pass the ListenerDelegate
- // instance to handleMessage and keep track of them separately).
- mHandler = new Handler(looper) {
- @Override
- public void handleMessage(Message msg) {
- final SensorEvent t = (SensorEvent)msg.obj;
- final int handle = t.sensor.getHandle();
-
- switch (t.sensor.getType()) {
- // Only report accuracy for sensors that support it.
- case Sensor.TYPE_MAGNETIC_FIELD:
- case Sensor.TYPE_ORIENTATION:
- // call onAccuracyChanged() only if the value changes
- final int accuracy = mSensorAccuracies.get(handle);
- if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
- mSensorAccuracies.put(handle, t.accuracy);
- mSensorEventListener.onAccuracyChanged(t.sensor, t.accuracy);
- }
- break;
- default:
- // For other sensors, just report the accuracy once
- if (mFirstEvent.get(handle) == false) {
- mFirstEvent.put(handle, true);
- mSensorEventListener.onAccuracyChanged(
- t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
- }
- break;
- }
-
- mSensorEventListener.onSensorChanged(t);
- sPool.returnToPool(t);
- }
- };
- addSensor(sensor);
- }
-
- Object getListener() {
- return mSensorEventListener;
- }
-
- void addSensor(Sensor sensor) {
- mSensors.put(sensor.getHandle(), true);
- mSensorList.add(sensor);
- }
- int removeSensor(Sensor sensor) {
- mSensors.delete(sensor.getHandle());
- mSensorList.remove(sensor);
- return mSensors.size();
- }
- boolean hasSensor(Sensor sensor) {
- return mSensors.get(sensor.getHandle());
- }
- List<Sensor> getSensors() {
- return mSensorList;
- }
-
- void onSensorChangedLocked(Sensor sensor, float[] values, long[] timestamp, int accuracy) {
- SensorEvent t = sPool.getFromPool();
- final float[] v = t.values;
- v[0] = values[0];
- v[1] = values[1];
- v[2] = values[2];
- t.timestamp = timestamp[0];
- t.accuracy = accuracy;
- t.sensor = sensor;
- Message msg = Message.obtain();
- msg.what = 0;
- msg.obj = t;
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- }
- }
-
- /**
- * {@hide}
- */
+ /** {@hide} */
public SystemSensorManager(Looper mainLooper) {
mMainLooper = mainLooper;
+ mSensorEventQueueMap = new Hashtable<SensorEventListener, SensorEventQueue>();
- synchronized(sListeners) {
+ synchronized(sSensorModuleLock) {
if (!sSensorModuleInitialized) {
sSensorModuleInitialized = true;
nativeClassInit();
// initialize the sensor list
- sensors_module_init();
final ArrayList<Sensor> fullList = sFullSensorsList;
int i = 0;
do {
Sensor sensor = new Sensor();
- i = sensors_module_get_next_sensor(sensor, i);
-
+ i = nativeGetNextSensor(sensor, i);
if (i>=0) {
//Log.d(TAG, "found sensor: " + sensor.getName() +
// ", handle=" + sensor.getHandle());
@@ -274,126 +82,304 @@ public class SystemSensorManager extends SensorManager {
} while (i>0);
sPool = new SensorEventPool( sFullSensorsList.size()*2 );
- sSensorThread = new SensorThread();
}
}
}
+
/** @hide */
@Override
protected List<Sensor> getFullSensorList() {
return sFullSensorsList;
}
- private boolean enableSensorLocked(Sensor sensor, int delay) {
- boolean result = false;
- for (ListenerDelegate i : sListeners) {
- if (i.hasSensor(sensor)) {
- String name = sensor.getName();
- int handle = sensor.getHandle();
- result = sensors_enable_sensor(sQueue, name, handle, delay);
- break;
+
+ /** @hide */
+ @Override
+ protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
+ int delay, Handler handler)
+ {
+ // Invariants to preserve:
+ // - one Looper per SensorEventListener
+ // - one Looper per SensorEventQueue
+ // We map SensorEventListeners to a SensorEventQueue, which holds the looper
+
+ if (sensor == null) throw new NullPointerException("sensor cannot be null");
+
+ boolean result;
+ synchronized (mSensorEventQueueMap) {
+ // check if we already have this SensorEventListener, Sensor pair
+ // registered -- if so, we ignore the register. This is not ideal
+ // but this is what the implementation has always been doing.
+ for (SensorEventListenerSensorPair l : mListenerDelegates) {
+ if (l.isSameListenerSensorPair(listener, sensor)) {
+ // already added, just return silently.
+ return true;
+ }
}
- }
- return result;
- }
- private boolean disableSensorLocked(Sensor sensor) {
- for (ListenerDelegate i : sListeners) {
- if (i.hasSensor(sensor)) {
- // not an error, it's just that this sensor is still in use
- return true;
+ // now find the SensorEventQueue associated to this listener
+ SensorEventQueue queue = mSensorEventQueueMap.get(listener);
+ if (queue != null) {
+ result = queue.addSensor(sensor, delay);
+ if (result) {
+ // create a new ListenerDelegate for this pair
+ mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
+ }
+ } else {
+ Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
+ queue = new SensorEventQueue(listener, looper.getQueue());
+ result = queue.addSensor(sensor, delay);
+ if (result) {
+ // create a new ListenerDelegate for this pair
+ mListenerDelegates.add(new SensorEventListenerSensorPair(listener, sensor));
+ mSensorEventQueueMap.put(listener, queue);
+ } else {
+ queue.dispose();
+ }
}
}
- String name = sensor.getName();
- int handle = sensor.getHandle();
- return sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE);
+ return result;
}
/** @hide */
@Override
- protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor,
- int delay, Handler handler) {
- boolean result = true;
- synchronized (sListeners) {
- // look for this listener in our list
- ListenerDelegate l = null;
- for (ListenerDelegate i : sListeners) {
- if (i.getListener() == listener) {
- l = i;
- break;
+ protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
+ synchronized (mSensorEventQueueMap) {
+
+ // remove this listener/sensor from our list
+ final ArrayList<SensorEventListenerSensorPair> copy =
+ new ArrayList<SensorEventListenerSensorPair>(mListenerDelegates);
+ int lastIndex = copy.size()-1;
+ for (int i=lastIndex ; i>= 0 ; i--) {
+ if (copy.get(i).isSameListenerSensorPair(listener, sensor)) {
+ mListenerDelegates.remove(i);
}
}
- // if we don't find it, add it to the list
- if (l == null) {
- l = new ListenerDelegate(listener, sensor, handler);
- sListeners.add(l);
- // if the list is not empty, start our main thread
- if (!sListeners.isEmpty()) {
- if (sSensorThread.startLocked()) {
- if (!enableSensorLocked(sensor, delay)) {
- // oops. there was an error
- sListeners.remove(l);
- result = false;
- }
- } else {
- // there was an error, remove the listener
- sListeners.remove(l);
- result = false;
- }
+ // find the SensorEventQueue associated to this SensorEventListener
+ SensorEventQueue queue = mSensorEventQueueMap.get(listener);
+ if (queue != null) {
+ if (sensor != null) {
+ queue.removeSensor(sensor);
} else {
- // weird, we couldn't add the listener
- result = false;
+ queue.removeAllSensors();
}
- } else if (!l.hasSensor(sensor)) {
- l.addSensor(sensor);
- if (!enableSensorLocked(sensor, delay)) {
- // oops. there was an error
- l.removeSensor(sensor);
- result = false;
+ if (!queue.hasSensors()) {
+ mSensorEventQueueMap.remove(listener);
+ queue.dispose();
}
}
}
+ }
- return result;
+
+ /*
+ * ListenerDelegate is essentially a SensorEventListener, Sensor pair
+ * and is associated with a single SensorEventQueue.
+ */
+ private static final class SensorEventListenerSensorPair {
+ private final SensorEventListener mSensorEventListener;
+ private final Sensor mSensor;
+ public SensorEventListenerSensorPair(SensorEventListener listener, Sensor sensor) {
+ mSensorEventListener = listener;
+ mSensor = sensor;
+ }
+ public boolean isSameListenerSensorPair(SensorEventListener listener, Sensor sensor) {
+ // if sensor is null, we match only on the listener
+ if (sensor != null) {
+ return (listener == mSensorEventListener) &&
+ (sensor.getHandle() == mSensor.getHandle());
+ } else {
+ return (listener == mSensorEventListener);
+ }
+ }
}
- /** @hide */
- @Override
- protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) {
- synchronized (sListeners) {
- final int size = sListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = sListeners.get(i);
- if (l.getListener() == listener) {
- if (sensor == null) {
- sListeners.remove(i);
- // disable all sensors for this listener
- for (Sensor s : l.getSensors()) {
- disableSensorLocked(s);
- }
- // Check if the ListenerDelegate has the sensor it is trying to unregister.
- } else if (l.hasSensor(sensor) && l.removeSensor(sensor) == 0) {
- // if we have no more sensors enabled on this listener,
- // take it off the list.
- sListeners.remove(i);
- disableSensorLocked(sensor);
+ /*
+ * SensorEventQueue is the communication channel with the sensor service,
+ * there is a one-to-one mapping between SensorEventQueue and
+ * SensorEventListener.
+ */
+ private static final class SensorEventQueue {
+ private static native int nativeInitSensorEventQueue(SensorEventQueue eventQ, MessageQueue msgQ, float[] scratch);
+ private static native int nativeEnableSensor(int eventQ, int handle, int us);
+ private static native int nativeDisableSensor(int eventQ, int handle);
+ private static native void nativeDestroySensorEventQueue(int eventQ);
+ private int nSensorEventQueue;
+ private final SensorEventListener mListener;
+ private final SparseBooleanArray mActiveSensors = new SparseBooleanArray();
+ private final SparseIntArray mSensorAccuracies = new SparseIntArray();
+ private final SparseBooleanArray mFirstEvent = new SparseBooleanArray();
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final float[] mScratch = new float[16];
+
+ public SensorEventQueue(SensorEventListener listener, MessageQueue msgQ) {
+ nSensorEventQueue = nativeInitSensorEventQueue(this, msgQ, mScratch);
+ mListener = listener;
+ mCloseGuard.open("dispose");
+ }
+ public void dispose() {
+ dispose(false);
+ }
+
+ public boolean addSensor(Sensor sensor, int delay) {
+ if (enableSensor(sensor, delay) == 0) {
+ mActiveSensors.put(sensor.getHandle(), true);
+ return true;
+ }
+ return false;
+ }
+
+ public void removeAllSensors() {
+ for (int i=0 ; i<mActiveSensors.size(); i++) {
+ if (mActiveSensors.valueAt(i) == true) {
+ int handle = mActiveSensors.keyAt(i);
+ Sensor sensor = sHandleToSensor.get(handle);
+ if (sensor != null) {
+ disableSensor(sensor);
+ mActiveSensors.put(handle, false);
+ } else {
+ // it should never happen -- just ignore.
}
- break;
}
}
}
+
+ public void removeSensor(Sensor sensor) {
+ final int handle = sensor.getHandle();
+ if (mActiveSensors.get(handle)) {
+ disableSensor(sensor);
+ mActiveSensors.put(sensor.getHandle(), false);
+ }
+ }
+
+ public boolean hasSensors() {
+ // no more sensors are set
+ return mActiveSensors.indexOfValue(true) >= 0;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void dispose(boolean finalized) {
+ if (mCloseGuard != null) {
+ if (finalized) {
+ mCloseGuard.warnIfOpen();
+ }
+ mCloseGuard.close();
+ }
+ if (nSensorEventQueue != 0) {
+ nativeDestroySensorEventQueue(nSensorEventQueue);
+ nSensorEventQueue = 0;
+ }
+ }
+
+ private int enableSensor(Sensor sensor, int us) {
+ if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (sensor == null) throw new NullPointerException();
+ return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), us);
+ }
+ private int disableSensor(Sensor sensor) {
+ if (nSensorEventQueue == 0) throw new NullPointerException();
+ if (sensor == null) throw new NullPointerException();
+ return nativeDisableSensor(nSensorEventQueue, sensor.getHandle());
+ }
+
+ // Called from native code.
+ @SuppressWarnings("unused")
+ private void dispatchSensorEvent(int handle, float[] values, int inAccuracy, long timestamp) {
+ // this is always called on the same thread.
+ final SensorEvent t = sPool.getFromPool();
+ try {
+ final Sensor sensor = sHandleToSensor.get(handle);
+ final SensorEventListener listener = mListener;
+ // FIXME: handle more than 3 values
+ System.arraycopy(values, 0, t.values, 0, 3);
+ t.timestamp = timestamp;
+ t.accuracy = inAccuracy;
+ t.sensor = sensor;
+ switch (t.sensor.getType()) {
+ // Only report accuracy for sensors that support it.
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ case Sensor.TYPE_ORIENTATION:
+ // call onAccuracyChanged() only if the value changes
+ final int accuracy = mSensorAccuracies.get(handle);
+ if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
+ mSensorAccuracies.put(handle, t.accuracy);
+ listener.onAccuracyChanged(t.sensor, t.accuracy);
+ }
+ break;
+ default:
+ // For other sensors, just report the accuracy once
+ if (mFirstEvent.get(handle) == false) {
+ mFirstEvent.put(handle, true);
+ listener.onAccuracyChanged(
+ t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
+ }
+ break;
+ }
+ listener.onSensorChanged(t);
+ } finally {
+ sPool.returnToPool(t);
+ }
+ }
}
- private static native void nativeClassInit();
+ /*
+ * A dumb pool of SensorEvent
+ */
+ private static final class SensorEventPool {
+ private final int mPoolSize;
+ private final SensorEvent mPool[];
+ private int mNumItemsInPool;
+
+ private SensorEvent createSensorEvent() {
+ // maximal size for all legacy events is 3
+ return new SensorEvent(3);
+ }
- private static native int sensors_module_init();
- private static native int sensors_module_get_next_sensor(Sensor sensor, int next);
+ SensorEventPool(int poolSize) {
+ mPoolSize = poolSize;
+ mNumItemsInPool = poolSize;
+ mPool = new SensorEvent[poolSize];
+ }
- // Used within this module from outside SensorManager, don't make private
- static native int sensors_create_queue();
- static native void sensors_destroy_queue(int queue);
- static native boolean sensors_enable_sensor(int queue, String name, int sensor, int enable);
- static native int sensors_data_poll(int queue, float[] values, int[] status, long[] timestamp);
+ SensorEvent getFromPool() {
+ SensorEvent t = null;
+ synchronized (this) {
+ if (mNumItemsInPool > 0) {
+ // remove the "top" item from the pool
+ final int index = mPoolSize - mNumItemsInPool;
+ t = mPool[index];
+ mPool[index] = null;
+ mNumItemsInPool--;
+ }
+ }
+ if (t == null) {
+ // the pool was empty or this item was removed from the pool for
+ // the first time. In any case, we need to create a new item.
+ t = createSensorEvent();
+ }
+ return t;
+ }
+
+ void returnToPool(SensorEvent t) {
+ synchronized (this) {
+ // is there space left in the pool?
+ if (mNumItemsInPool < mPoolSize) {
+ // if so, return the item to the pool
+ mNumItemsInPool++;
+ final int index = mPoolSize - mNumItemsInPool;
+ mPool[index] = t;
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 262d87d..761faaf 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -212,8 +212,10 @@ public final class InputManager {
} catch (RemoteException ex) {
throw new RuntimeException("Could not get input device information.", ex);
}
+ if (inputDevice != null) {
+ mInputDevices.setValueAt(index, inputDevice);
+ }
}
- mInputDevices.setValueAt(index, inputDevice);
return inputDevice;
}
}
@@ -241,6 +243,8 @@ public final class InputManager {
inputDevice = mIm.getInputDevice(id);
} catch (RemoteException ex) {
// Ignore the problem for the purposes of this method.
+ }
+ if (inputDevice == null) {
continue;
}
mInputDevices.setValueAt(i, inputDevice);
@@ -809,6 +813,22 @@ public final class InputManager {
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+ vibrate(milliseconds);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
+ vibrate(pattern, repeat);
+ }
+
@Override
public void cancel() {
try {
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 8286686..9bc967f 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -95,4 +95,7 @@ interface IUsbManager
/* Deny USB debugging from the attached host */
void denyUsbDebugging();
+
+ /* Clear public keys installed for secure USB debugging */
+ void clearUsbDebuggingKeys();
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 6f1cc94..99624cc 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1955,7 +1955,7 @@ public class InputMethodService extends AbstractInputMethodService {
ic.sendKeyEvent(new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
- ic.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ ic.sendKeyEvent(new KeyEvent(eventTime, SystemClock.uptimeMillis(),
KeyEvent.ACTION_UP, keyEventCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE));
}
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
index ce71e6b..21995c0 100644
--- a/core/java/android/net/CaptivePortalTracker.java
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -25,14 +25,15 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.os.Handler;
import android.os.UserHandle;
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
import android.telephony.TelephonyManager;
-import android.util.Log;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -81,15 +82,21 @@ public class CaptivePortalTracker extends StateMachine {
private State mActiveNetworkState = new ActiveNetworkState();
private State mDelayedCaptiveCheckState = new DelayedCaptiveCheckState();
+ private static final String SETUP_WIZARD_PACKAGE = "com.google.android.setupwizard";
+ private boolean mDeviceProvisioned = false;
+ private ProvisioningObserver mProvisioningObserver;
+
private CaptivePortalTracker(Context context, IConnectivityManager cs) {
super(TAG);
mContext = context;
mConnService = cs;
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mProvisioningObserver = new ProvisioningObserver();
IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
mContext.registerReceiver(mReceiver, filter);
mServer = Settings.Global.getString(mContext.getContentResolver(),
@@ -106,11 +113,31 @@ public class CaptivePortalTracker extends StateMachine {
setInitialState(mNoActiveNetworkState);
}
+ private class ProvisioningObserver extends ContentObserver {
+ ProvisioningObserver() {
+ super(new Handler());
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.DEVICE_PROVISIONED), false, this);
+ onChange(false); // load initial value
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ // Normally, we respond to CONNECTIVITY_ACTION, allowing time for the change in
+ // connectivity to stabilize, but if the device is not yet provisioned, respond
+ // immediately to speed up transit through the setup wizard.
+ if ((mDeviceProvisioned && action.equals(ConnectivityManager.CONNECTIVITY_ACTION))
+ || (!mDeviceProvisioned
+ && action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE))) {
NetworkInfo info = intent.getParcelableExtra(
ConnectivityManager.EXTRA_NETWORK_INFO);
sendMessage(obtainMessage(CMD_CONNECTIVITY_CHANGE, info));
@@ -222,8 +249,12 @@ public class CaptivePortalTracker extends StateMachine {
@Override
public void enter() {
if (DBG) log(getName() + "\n");
- sendMessageDelayed(obtainMessage(CMD_DELAYED_CAPTIVE_CHECK,
- ++mDelayedCheckToken, 0), DELAYED_CHECK_INTERVAL_MS);
+ Message message = obtainMessage(CMD_DELAYED_CAPTIVE_CHECK, ++mDelayedCheckToken, 0);
+ if (mDeviceProvisioned) {
+ sendMessageDelayed(message, DELAYED_CHECK_INTERVAL_MS);
+ } else {
+ sendMessage(message);
+ }
}
@Override
@@ -233,13 +264,26 @@ public class CaptivePortalTracker extends StateMachine {
case CMD_DELAYED_CAPTIVE_CHECK:
if (message.arg1 == mDelayedCheckToken) {
InetAddress server = lookupHost(mServer);
- if (server != null) {
- if (isCaptivePortal(server)) {
- if (DBG) log("Captive network " + mNetworkInfo);
+ boolean captive = server != null && isCaptivePortal(server);
+ if (captive) {
+ if (DBG) log("Captive network " + mNetworkInfo);
+ } else {
+ if (DBG) log("Not captive network " + mNetworkInfo);
+ }
+ if (mDeviceProvisioned) {
+ if (captive) {
+ // Setup Wizard will assist the user in connecting to a captive
+ // portal, so make the notification visible unless during setup
setNotificationVisible(true);
}
+ } else {
+ Intent intent = new Intent(
+ ConnectivityManager.ACTION_CAPTIVE_PORTAL_TEST_COMPLETED);
+ intent.putExtra(ConnectivityManager.EXTRA_IS_CAPTIVE_PORTAL, captive);
+ intent.setPackage(SETUP_WIZARD_PACKAGE);
+ mContext.sendBroadcast(intent);
}
- if (DBG) log("Not captive network " + mNetworkInfo);
+
transitionTo(mActiveNetworkState);
}
break;
@@ -370,13 +414,4 @@ public class CaptivePortalTracker extends StateMachine {
}
mNotificationShown = visible;
}
-
- private static void log(String s) {
- Log.d(TAG, s);
- }
-
- private static void loge(String s) {
- Log.e(TAG, s);
- }
-
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6ff1a33..3a04c27 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -62,7 +62,7 @@ public class ConnectivityManager {
* NetworkInfo for the new network is also passed as an extra. This lets
* any receivers of the broadcast know that they should not necessarily
* tell the user that no data traffic will be possible. Instead, the
- * reciever should expect another broadcast soon, indicating either that
+ * receiver should expect another broadcast soon, indicating either that
* the failover attempt succeeded (and so there is still overall data
* connectivity), or that the failover attempt failed, meaning that all
* connectivity has been lost.
@@ -70,6 +70,7 @@ public class ConnectivityManager {
* For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY
* is set to {@code true} if there are no connected networks at all.
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
/**
@@ -78,6 +79,7 @@ public class ConnectivityManager {
*
* @hide
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String CONNECTIVITY_ACTION_IMMEDIATE =
"android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE";
@@ -149,8 +151,8 @@ public class ConnectivityManager {
/**
* Broadcast action to indicate the change of data activity status
* (idle or active) on a network in a recent period.
- * The network becomes active when data transimission is started, or
- * idle if there is no data transimition for a period of time.
+ * The network becomes active when data transmission is started, or
+ * idle if there is no data transmission for a period of time.
* {@hide}
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -198,91 +200,119 @@ public class ConnectivityManager {
* the network and it's condition.
* @hide
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String INET_CONDITION_ACTION =
"android.net.conn.INET_CONDITION_ACTION";
/**
- * Broadcast Action: A tetherable connection has come or gone
- * TODO - finish the doc
+ * Broadcast Action: A tetherable connection has come or gone.
+ * Uses {@code ConnectivityManager.EXTRA_AVAILABLE_TETHER},
+ * {@code ConnectivityManager.EXTRA_ACTIVE_TETHER} and
+ * {@code ConnectivityManager.EXTRA_ERRORED_TETHER} to indicate
+ * the current state of tethering. Each include a list of
+ * interface names in that state (may be empty).
* @hide
*/
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TETHER_STATE_CHANGED =
"android.net.conn.TETHER_STATE_CHANGED";
/**
* @hide
- * gives a String[]
+ * gives a String[] listing all the interfaces configured for
+ * tethering and currently available for tethering.
*/
public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
/**
* @hide
- * gives a String[]
+ * gives a String[] listing all the interfaces currently tethered
+ * (ie, has dhcp support and packets potentially forwarded/NATed)
*/
public static final String EXTRA_ACTIVE_TETHER = "activeArray";
/**
* @hide
- * gives a String[]
+ * gives a String[] listing all the interfaces we tried to tether and
+ * failed. Use {@link #getLastTetherError} to find the error code
+ * for any interfaces listed here.
*/
public static final String EXTRA_ERRORED_TETHER = "erroredArray";
/**
- * The absence of APN..
+ * Broadcast Action: The captive portal tracker has finished its test.
+ * Sent only while running Setup Wizard, in lieu of showing a user
+ * notification.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_CAPTIVE_PORTAL_TEST_COMPLETED =
+ "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED";
+ /**
+ * The lookup key for a boolean that indicates whether a captive portal was detected.
+ * Retrieve it with {@link android.content.Intent#getBooleanExtra(String,boolean)}.
+ * @hide
+ */
+ public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal";
+
+ /**
+ * The absence of a connection type.
* @hide
*/
public static final int TYPE_NONE = -1;
/**
- * The Default Mobile data connection. When active, all data traffic
- * will use this connection by default.
+ * The Mobile data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route)
*/
public static final int TYPE_MOBILE = 0;
/**
- * The Default WIFI data connection. When active, all data traffic
- * will use this connection by default.
+ * The WIFI data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_WIFI = 1;
/**
- * An MMS-specific Mobile data connection. This connection may be the
- * same as {@link #TYPE_MOBILE} but it may be different. This is used
- * by applications needing to talk to the carrier's Multimedia Messaging
- * Service servers. It may coexist with default data connections.
+ * An MMS-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Multimedia Messaging Service servers.
*/
public static final int TYPE_MOBILE_MMS = 2;
/**
- * A SUPL-specific Mobile data connection. This connection may be the
- * same as {@link #TYPE_MOBILE} but it may be different. This is used
- * by applications needing to talk to the carrier's Secure User Plane
- * Location servers for help locating the device. It may coexist with
- * default data connections.
+ * A SUPL-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is used by applications needing to talk to the carrier's
+ * Secure User Plane Location servers for help locating the device.
*/
public static final int TYPE_MOBILE_SUPL = 3;
/**
- * A DUN-specific Mobile data connection. This connection may be the
- * same as {@link #TYPE_MOBILE} but it may be different. This is used
- * by applicaitons performing a Dial Up Networking bridge so that
- * the carrier is aware of DUN traffic. It may coexist with default data
- * connections.
+ * A DUN-specific Mobile data connection. This network type may use the
+ * same network interface as {@link #TYPE_MOBILE} or it may use a different
+ * one. This is sometimes by the system when setting up an upstream connection
+ * for tethering so that the carrier is aware of DUN traffic.
*/
public static final int TYPE_MOBILE_DUN = 4;
/**
- * A High Priority Mobile data connection. This connection is typically
- * the same as {@link #TYPE_MOBILE} but the routing setup is different.
- * Only requesting processes will have access to the Mobile DNS servers
- * and only IP's explicitly requested via {@link #requestRouteToHost}
- * will route over this interface if a default route exists.
+ * A High Priority Mobile data connection. This network type uses the
+ * same network interface as {@link #TYPE_MOBILE} but the routing setup
+ * is different. Only requesting processes will have access to the
+ * Mobile DNS servers and only IP's explicitly requested via {@link #requestRouteToHost}
+ * will route over this interface if no default route exists.
*/
public static final int TYPE_MOBILE_HIPRI = 5;
/**
- * The Default WiMAX data connection. When active, all data traffic
- * will use this connection by default.
+ * The WiMAX data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_WIMAX = 6;
/**
- * The Default Bluetooth data connection. When active, all data traffic
- * will use this connection by default.
+ * The Bluetooth data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_BLUETOOTH = 7;
@@ -292,25 +322,26 @@ public class ConnectivityManager {
public static final int TYPE_DUMMY = 8;
/**
- * The Default Ethernet data connection. When active, all data traffic
- * will use this connection by default.
+ * The Ethernet data connection. When active, all data traffic
+ * will use this network type's interface by default
+ * (it has a default route).
*/
public static final int TYPE_ETHERNET = 9;
/**
- * Over the air Adminstration.
+ * Over the air Administration.
* {@hide}
*/
public static final int TYPE_MOBILE_FOTA = 10;
/**
- * IP Multimedia Subsystem
+ * IP Multimedia Subsystem.
* {@hide}
*/
public static final int TYPE_MOBILE_IMS = 11;
/**
- * Carrier Branded Services
+ * Carrier Branded Services.
* {@hide}
*/
public static final int TYPE_MOBILE_CBS = 12;
@@ -328,11 +359,27 @@ public class ConnectivityManager {
/** {@hide} */
public static final int MAX_NETWORK_TYPE = TYPE_WIFI_P2P;
+ /**
+ * If you want to set the default network preference,you can directly
+ * change the networkAttributes array in framework's config.xml.
+ *
+ * @deprecated Since we support so many more networks now, the single
+ * network default network preference can't really express
+ * the hierarchy. Instead, the default is defined by the
+ * networkAttributes in config.xml. You can determine
+ * the current value by calling {@link #getNetworkPreference()}
+ * from an App.
+ */
+ @Deprecated
public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI;
/**
* Default value for {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY} in
- * milliseconds.
+ * milliseconds. This was introduced because IPv6 routes seem to take a
+ * moment to settle - trying network activity before the routes are adjusted
+ * can lead to packets using the wrong interface or having the wrong IP address.
+ * This delay is a bit crude, but in the future hopefully we will have kernel
+ * notifications letting us know when it's safe to use the new network.
*
* @hide
*/
@@ -340,11 +387,23 @@ public class ConnectivityManager {
private final IConnectivityManager mService;
+ /**
+ * Tests if a given integer represents a valid network type.
+ * @param networkType the type to be tested
+ * @return a boolean. {@code true} if the type is valid, else {@code false}
+ */
public static boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
}
- /** {@hide} */
+ /**
+ * Returns a non-localized string representing a given network type.
+ * ONLY used for debugging output.
+ * @param type the type needing naming
+ * @return a String for the given type, or a string version of the type ("87")
+ * if no name is known.
+ * {@hide}
+ */
public static String getNetworkTypeName(int type) {
switch (type) {
case TYPE_MOBILE:
@@ -380,7 +439,13 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Checks if a given type uses the cellular data connection.
+ * This should be replaced in the future by a network property.
+ * @param networkType the type to check
+ * @return a boolean - {@code true} if uses cellular network, else {@code false}
+ * {@hide}
+ */
public static boolean isNetworkTypeMobile(int networkType) {
switch (networkType) {
case TYPE_MOBILE:
@@ -397,6 +462,17 @@ public class ConnectivityManager {
}
}
+ /**
+ * Specifies the preferred network type. When the device has more
+ * than one type available the preferred network type will be used.
+ * Note that this made sense when we only had 2 network types,
+ * but with more and more default networks we need an array to list
+ * their ordering. This will be deprecated soon.
+ *
+ * @param preference the network type to prefer over all others. It is
+ * unspecified what happens to the old preferred network in the
+ * overall ordering.
+ */
public void setNetworkPreference(int preference) {
try {
mService.setNetworkPreference(preference);
@@ -404,6 +480,17 @@ public class ConnectivityManager {
}
}
+ /**
+ * Retrieves the current preferred network type.
+ * Note that this made sense when we only had 2 network types,
+ * but with more and more default networks we need an array to list
+ * their ordering. This will be deprecated soon.
+ *
+ * @return an integer representing the preferred network type
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
public int getNetworkPreference() {
try {
return mService.getNetworkPreference();
@@ -413,11 +500,16 @@ public class ConnectivityManager {
}
/**
- * Returns details about the currently active data network. When connected,
- * this network is the default route for outgoing connections. You should
- * always check {@link NetworkInfo#isConnected()} before initiating network
- * traffic. This may return {@code null} when no networks are available.
- * <p>This method requires the caller to hold the permission
+ * Returns details about the currently active default data network. When
+ * connected, this network is the default route for outgoing connections.
+ * You should always check {@link NetworkInfo#isConnected()} before initiating
+ * network traffic. This may return {@code null} when there is no default
+ * network.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * or {@code null} if no network default network is currently active
+ *
+ * <p>This method requires the call to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*/
public NetworkInfo getActiveNetworkInfo() {
@@ -428,7 +520,19 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Returns details about the currently active default data network
+ * for a given uid. This is for internal use only to avoid spying
+ * other apps.
+ *
+ * @return a {@link NetworkInfo} object for the current default network
+ * for the given uid or {@code null} if no default network is
+ * available for the specified uid.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
+ * {@hide}
+ */
public NetworkInfo getActiveNetworkInfoForUid(int uid) {
try {
return mService.getActiveNetworkInfoForUid(uid);
@@ -437,6 +541,19 @@ public class ConnectivityManager {
}
}
+ /**
+ * Returns connection status information about a particular
+ * network type.
+ *
+ * @param networkType integer specifying which networkType in
+ * which you're interested.
+ * @return a {@link NetworkInfo} object for the requested
+ * network type or {@code null} if the type is not
+ * supported by the device.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
public NetworkInfo getNetworkInfo(int networkType) {
try {
return mService.getNetworkInfo(networkType);
@@ -445,6 +562,16 @@ public class ConnectivityManager {
}
}
+ /**
+ * Returns connection status information about all network
+ * types supported by the device.
+ *
+ * @return an array of {@link NetworkInfo} objects. Check each
+ * {@link NetworkInfo#getType} for which type each applies.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ */
public NetworkInfo[] getAllNetworkInfo() {
try {
return mService.getAllNetworkInfo();
@@ -453,7 +580,17 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Returns the IP information for the current default network.
+ *
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the current default network, or {@code null} if there
+ * is no current default network.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -462,7 +599,18 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Returns the IP information for a given network type.
+ *
+ * @param networkType the network type of interest.
+ * @return a {@link LinkProperties} object describing the IP info
+ * for the given networkType, or {@code null} if there is
+ * no current default network.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * {@hide}
+ */
public LinkProperties getLinkProperties(int networkType) {
try {
return mService.getLinkProperties(networkType);
@@ -471,7 +619,18 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Tells each network type to set its radio power state as directed.
+ *
+ * @param turnOn a boolean, {@code true} to turn the radios on,
+ * {@code false} to turn them off.
+ * @return a boolean, {@code true} indicating success. All network types
+ * will be tried, even if some fail.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
public boolean setRadios(boolean turnOn) {
try {
return mService.setRadios(turnOn);
@@ -480,7 +639,18 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * Tells a given networkType to set its radio power state as directed.
+ *
+ * @param networkType the int networkType of interest.
+ * @param turnOn a boolean, {@code true} to turn the radio on,
+ * {@code} false to turn it off.
+ * @return a boolean, {@code true} indicating success.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
+ * {@hide}
+ */
public boolean setRadio(int networkType, boolean turnOn) {
try {
return mService.setRadio(networkType, turnOn);
@@ -615,6 +785,9 @@ public class ConnectivityManager {
* network is active. Quota status can change rapidly, so these values
* shouldn't be cached.
*
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
* @hide
*/
public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
@@ -629,6 +802,9 @@ public class ConnectivityManager {
* Gets the value of the setting for enabling Mobile data.
*
* @return Whether mobile data is enabled.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
*/
public boolean getMobileDataEnabled() {
@@ -642,8 +818,8 @@ public class ConnectivityManager {
/**
* Sets the persisted value for enabling/disabling Mobile data.
*
- * @param enabled Whether the mobile data connection should be
- * used or not.
+ * @param enabled Whether the user wants the mobile data connection used
+ * or not.
* @hide
*/
public void setMobileDataEnabled(boolean enabled) {
@@ -666,6 +842,13 @@ public class ConnectivityManager {
}
/**
+ * Get the set of tetherable, available interfaces. This list is limited by
+ * device configuration and current interface existence.
+ *
+ * @return an array of 0 or more Strings of tetherable interface names.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableIfaces() {
@@ -677,6 +860,12 @@ public class ConnectivityManager {
}
/**
+ * Get the set of tethered interfaces.
+ *
+ * @return an array of 0 or more String of currently tethered interface names.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetheredIfaces() {
@@ -688,6 +877,18 @@ public class ConnectivityManager {
}
/**
+ * Get the set of interface names which attempted to tether but
+ * failed. Re-attempting to tether may cause them to reset to the Tethered
+ * state. Alternatively, causing the interface to be destroyed and recreated
+ * may cause them to reset to the available state.
+ * {@link ConnectivityManager#getLastTetherError} can be used to get more
+ * information on the cause of the errors.
+ *
+ * @return an array of 0 or more String indicating the interface names
+ * which failed to tether.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetheringErroredIfaces() {
@@ -699,7 +900,19 @@ public class ConnectivityManager {
}
/**
- * @return error A TETHER_ERROR value indicating success or failure type
+ * Attempt to tether the named interface. This will setup a dhcp server
+ * on the interface, forward and NAT IP packets and forward DNS requests
+ * to the best active upstream network interface. Note that if no upstream
+ * IP network interface is available, dhcp will still run and traffic will be
+ * allowed between the tethered devices and this device, though upstream net
+ * access will of course fail until an upstream network interface becomes
+ * active.
+ *
+ * @param iface the interface name to tether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int tether(String iface) {
@@ -711,7 +924,13 @@ public class ConnectivityManager {
}
/**
- * @return error A TETHER_ERROR value indicating success or failure type
+ * Stop tethering the named interface.
+ *
+ * @param iface the interface name to untether.
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int untether(String iface) {
@@ -723,6 +942,14 @@ public class ConnectivityManager {
}
/**
+ * Check if the device allows for tethering. It may be disabled via
+ * {@code ro.tether.denied} system property, {@link Settings#TETHER_SUPPORTED} or
+ * due to device configuration.
+ *
+ * @return a boolean - {@code true} indicating Tethering is supported.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public boolean isTetheringSupported() {
@@ -734,6 +961,15 @@ public class ConnectivityManager {
}
/**
+ * Get the list of regular expressions that define any tetherable
+ * USB network interfaces. If USB tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable usb interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableUsbRegexs() {
@@ -745,6 +981,15 @@ public class ConnectivityManager {
}
/**
+ * Get the list of regular expressions that define any tetherable
+ * Wifi network interfaces. If Wifi tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable wifi interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableWifiRegexs() {
@@ -756,6 +1001,15 @@ public class ConnectivityManager {
}
/**
+ * Get the list of regular expressions that define any tetherable
+ * Bluetooth network interfaces. If Bluetooth tethering is not supported by the
+ * device, this list should be empty.
+ *
+ * @return an array of 0 or more regular expression Strings defining
+ * what interfaces are considered tetherable bluetooth interfaces.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableBluetoothRegexs() {
@@ -767,6 +1021,17 @@ public class ConnectivityManager {
}
/**
+ * Attempt to both alter the mode of USB and Tethering of USB. A
+ * utility method to deal with some of the complexity of USB - will
+ * attempt to switch to Rndis and subsequently tether the resulting
+ * interface on {@code true} or turn off tethering and switch off
+ * Rndis on {@code false}.
+ *
+ * @param enable a boolean - {@code true} to enable tethering
+ * @return error a {@code TETHER_ERROR} value indicating success or failure type
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int setUsbTethering(boolean enable) {
@@ -801,9 +1066,15 @@ public class ConnectivityManager {
public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10;
/**
- * @param iface The name of the interface we're interested in
+ * Get a more detailed error code after a Tethering or Untethering
+ * request asynchronously failed.
+ *
+ * @param iface The name of the interface of interest
* @return error The error code of the last error tethering or untethering the named
* interface
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public int getLastTetherError(String iface) {
@@ -815,9 +1086,16 @@ public class ConnectivityManager {
}
/**
- * Ensure the device stays awake until we connect with the next network
- * @param forWhome The name of the network going down for logging purposes
+ * Try to ensure the device stays awake until we connect with the next network.
+ * Actually just holds a wakelock for a number of seconds while we try to connect
+ * to any default networks. This will expire if the timeout passes or if we connect
+ * to a default after this is called. For internal use only.
+ *
+ * @param forWhom the name of the network going down for logging purposes
* @return {@code true} on success, {@code false} on failure
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* {@hide}
*/
public boolean requestNetworkTransitionWakelock(String forWhom) {
@@ -830,8 +1108,14 @@ public class ConnectivityManager {
}
/**
+ * Report network connectivity status. This is currently used only
+ * to alter status bar UI.
+ *
* @param networkType The type of network you want to report on
* @param percentage The quality of the connection 0 is bad, 100 is good
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#STATUS_BAR}.
* {@hide}
*/
public void reportInetCondition(int networkType, int percentage) {
@@ -842,7 +1126,16 @@ public class ConnectivityManager {
}
/**
- * @param proxyProperties The definition for the new global http proxy
+ * Set a network-independent global http proxy. This is not normally what you want
+ * for typical HTTP proxies - they are general network dependent. However if you're
+ * doing something unusual like general internal filtering this may be useful. On
+ * a private network where the proxy is not accessible, you may break HTTP using this.
+ *
+ * @param proxyProperties The a {@link ProxyProperites} object defining the new global
+ * HTTP proxy. A {@code null} value will clear the global HTTP proxy.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public void setGlobalProxy(ProxyProperties p) {
@@ -853,7 +1146,13 @@ public class ConnectivityManager {
}
/**
- * @return proxyProperties for the current global proxy
+ * Retrieve any network-independent global HTTP proxy.
+ *
+ * @return {@link ProxyProperties} for the current global HTTP proxy or {@code null}
+ * if no global HTTP proxy is set.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public ProxyProperties getGlobalProxy() {
@@ -865,7 +1164,14 @@ public class ConnectivityManager {
}
/**
- * @return proxyProperties for the current proxy (global if set, network specific if not)
+ * Get the HTTP proxy settings for the current default network. Note that
+ * if a global proxy is set, it will override any per-network setting.
+ *
+ * @return the {@link ProxyProperties} for the current HTTP proxy, or {@code null} if no
+ * HTTP proxy is active.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public ProxyProperties getProxy() {
@@ -877,8 +1183,15 @@ public class ConnectivityManager {
}
/**
+ * Sets a secondary requirement bit for the given networkType.
+ * This requirement bit is generally under the control of the carrier
+ * or its agents and is not directly controlled by the user.
+ *
* @param networkType The network who's dependence has changed
- * @param met Boolean - true if network use is ok, false if not
+ * @param met Boolean - true if network use is OK, false if not
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* {@hide}
*/
public void setDataDependency(int networkType, boolean met) {
@@ -892,11 +1205,15 @@ public class ConnectivityManager {
* Returns true if the hardware supports the given network type
* else it returns false. This doesn't indicate we have coverage
* or are authorized onto a network, just whether or not the
- * hardware supports it. For example a gsm phone without a sim
- * should still return true for mobile data, but a wifi only tablet
- * would return false.
- * @param networkType The nework type we'd like to check
- * @return true if supported, else false
+ * hardware supports it. For example a GSM phone without a SIM
+ * should still return {@code true} for mobile data, but a wifi only
+ * tablet would return {@code false}.
+ *
+ * @param networkType The network type we'd like to check
+ * @return {@code true} if supported, else {@code false}
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
*/
public boolean isNetworkSupported(int networkType) {
@@ -909,9 +1226,16 @@ public class ConnectivityManager {
/**
* Returns if the currently active data network is metered. A network is
* classified as metered when the user is sensitive to heavy data usage on
- * that connection. You should check this before doing large data transfers,
- * and warn the user or delay the operation until another network is
- * available.
+ * that connection due to monetary costs, data limitations or
+ * battery/performance issues. You should check this before doing large
+ * data transfers, and warn the user or delay the operation until another
+ * network is available.
+ *
+ * @return {@code true} if large transfers should be avoided, otherwise
+ * {@code false}.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*/
public boolean isActiveNetworkMetered() {
try {
@@ -921,7 +1245,15 @@ public class ConnectivityManager {
}
}
- /** {@hide} */
+ /**
+ * If the LockdownVpn mechanism is enabled, updates the vpn
+ * with a reload of its profile.
+ *
+ * @return a boolean with {@code} indicating success
+ *
+ * <p>This method can only be called by the system UID
+ * {@hide}
+ */
public boolean updateLockdownVpn() {
try {
return mService.updateLockdownVpn();
@@ -931,6 +1263,14 @@ public class ConnectivityManager {
}
/**
+ * Signal that the captive portal check on the indicated network
+ * is complete and we can turn the network on for general use.
+ *
+ * @param info the {@link NetworkInfo} object for the networkType
+ * in question.
+ *
+ * <p>This method requires the call to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* {@hide}
*/
public void captivePortalCheckComplete(NetworkInfo info) {
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index e2660e4..2b359eb 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -22,16 +22,17 @@ import java.net.InetAddress;
/**
* A simple object for retrieving the results of a DHCP request.
+ * @deprecated - use LinkProperties - To be removed 11/2013
+ * STOPSHIP - make sure we expose LinkProperties through ConnectivityManager
*/
public class DhcpInfo implements Parcelable {
public int ipAddress;
public int gateway;
public int netmask;
-
public int dns1;
public int dns2;
-
public int serverAddress;
+
public int leaseDuration;
public DhcpInfo() {
diff --git a/core/java/android/net/DhcpInfoInternal.java b/core/java/android/net/DhcpInfoInternal.java
deleted file mode 100644
index f3508c1..0000000
--- a/core/java/android/net/DhcpInfoInternal.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-
-/**
- * A simple object for retrieving the results of a DHCP request.
- * Replaces (internally) the IPv4-only DhcpInfo class.
- * @hide
- */
-public class DhcpInfoInternal {
- private final static String TAG = "DhcpInfoInternal";
- public String ipAddress;
- public int prefixLength;
-
- public String dns1;
- public String dns2;
-
- public String serverAddress;
- public int leaseDuration;
-
- /**
- * Vendor specific information (from RFC 2132).
- */
- public String vendorInfo;
-
- private Collection<RouteInfo> mRoutes;
-
- public DhcpInfoInternal() {
- mRoutes = new ArrayList<RouteInfo>();
- }
-
- public void addRoute(RouteInfo routeInfo) {
- mRoutes.add(routeInfo);
- }
-
- public Collection<RouteInfo> getRoutes() {
- return Collections.unmodifiableCollection(mRoutes);
- }
-
- private int convertToInt(String addr) {
- if (addr != null) {
- try {
- InetAddress inetAddress = NetworkUtils.numericToInetAddress(addr);
- if (inetAddress instanceof Inet4Address) {
- return NetworkUtils.inetAddressToInt(inetAddress);
- }
- } catch (IllegalArgumentException e) {}
- }
- return 0;
- }
-
- public DhcpInfo makeDhcpInfo() {
- DhcpInfo info = new DhcpInfo();
- info.ipAddress = convertToInt(ipAddress);
- for (RouteInfo route : mRoutes) {
- if (route.isDefaultRoute()) {
- info.gateway = convertToInt(route.getGateway().getHostAddress());
- break;
- }
- }
- try {
- InetAddress inetAddress = NetworkUtils.numericToInetAddress(ipAddress);
- info.netmask = NetworkUtils.prefixLengthToNetmaskInt(prefixLength);
- } catch (IllegalArgumentException e) {}
- info.dns1 = convertToInt(dns1);
- info.dns2 = convertToInt(dns2);
- info.serverAddress = convertToInt(serverAddress);
- info.leaseDuration = leaseDuration;
- return info;
- }
-
- public LinkAddress makeLinkAddress() {
- if (TextUtils.isEmpty(ipAddress)) {
- Log.e(TAG, "makeLinkAddress with empty ipAddress");
- return null;
- }
- return new LinkAddress(NetworkUtils.numericToInetAddress(ipAddress), prefixLength);
- }
-
- public LinkProperties makeLinkProperties() {
- LinkProperties p = new LinkProperties();
- p.addLinkAddress(makeLinkAddress());
- for (RouteInfo route : mRoutes) {
- p.addRoute(route);
- }
- //if empty, connectivity configures default DNS
- if (TextUtils.isEmpty(dns1) == false) {
- p.addDns(NetworkUtils.numericToInetAddress(dns1));
- } else {
- Log.d(TAG, "makeLinkProperties with empty dns1!");
- }
- if (TextUtils.isEmpty(dns2) == false) {
- p.addDns(NetworkUtils.numericToInetAddress(dns2));
- } else {
- Log.d(TAG, "makeLinkProperties with empty dns2!");
- }
- return p;
- }
-
- /* Updates the DHCP fields that need to be retained from
- * original DHCP request if the DHCP renewal shows them as
- * being empty
- */
- public void updateFromDhcpRequest(DhcpInfoInternal orig) {
- if (orig == null) return;
-
- if (TextUtils.isEmpty(dns1)) {
- dns1 = orig.dns1;
- }
-
- if (TextUtils.isEmpty(dns2)) {
- dns2 = orig.dns2;
- }
-
- if (mRoutes.size() == 0) {
- for (RouteInfo route : orig.getRoutes()) {
- addRoute(route);
- }
- }
- }
-
- /**
- * Test if this DHCP lease includes vendor hint that network link is
- * metered, and sensitive to heavy data transfers.
- */
- public boolean hasMeteredHint() {
- if (vendorInfo != null) {
- return vendorInfo.contains("ANDROID_METERED");
- } else {
- return false;
- }
- }
-
- public String toString() {
- String routeString = "";
- for (RouteInfo route : mRoutes) routeString += route.toString() + " | ";
- return "addr: " + ipAddress + "/" + prefixLength +
- " mRoutes: " + routeString +
- " dns: " + dns1 + "," + dns2 +
- " dhcpServer: " + serverAddress +
- " leaseDuration: " + leaseDuration;
- }
-}
diff --git a/core/java/android/util/PoolableManager.java b/core/java/android/net/DhcpResults.aidl
index 8773e63..f4db3c3 100644
--- a/core/java/android/util/PoolableManager.java
+++ b/core/java/android/net/DhcpResults.aidl
@@ -1,11 +1,11 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
+/**
+ * Copyright (c) 2012, 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
+ * 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,
@@ -14,14 +14,6 @@
* limitations under the License.
*/
-package android.util;
-
-/**
- * @hide
- */
-public interface PoolableManager<T extends Poolable<T>> {
- T newInstance();
+package android.net;
- void onAcquired(T element);
- void onReleased(T element);
-}
+parcelable DhcpResults;
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
new file mode 100644
index 0000000..a3f70da
--- /dev/null
+++ b/core/java/android/net/DhcpResults.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * A simple object for retrieving the results of a DHCP request.
+ * Optimized (attempted) for that jni interface
+ * TODO - remove when DhcpInfo is deprecated. Move the remaining api to LinkProperties.
+ * @hide
+ */
+public class DhcpResults implements Parcelable {
+ private static final String TAG = "DhcpResults";
+
+ public final LinkProperties linkProperties;
+
+ public InetAddress serverAddress;
+
+ /**
+ * Vendor specific information (from RFC 2132).
+ */
+ public String vendorInfo;
+
+ public int leaseDuration;
+
+ public DhcpResults() {
+ linkProperties = new LinkProperties();
+ }
+
+ /** copy constructor */
+ public DhcpResults(DhcpResults source) {
+ if (source != null) {
+ linkProperties = new LinkProperties(source.linkProperties);
+ serverAddress = source.serverAddress;
+ leaseDuration = source.leaseDuration;
+ vendorInfo = source.vendorInfo;
+ } else {
+ linkProperties = new LinkProperties();
+ }
+ }
+
+ public DhcpResults(LinkProperties lp) {
+ linkProperties = new LinkProperties(lp);
+ }
+
+ /**
+ * Updates the DHCP fields that need to be retained from
+ * original DHCP request if the current renewal shows them
+ * being empty.
+ */
+ public void updateFromDhcpRequest(DhcpResults orig) {
+ if (orig == null || orig.linkProperties == null) return;
+ if (linkProperties.getRoutes().size() == 0) {
+ for (RouteInfo r : orig.linkProperties.getRoutes()) linkProperties.addRoute(r);
+ }
+ if (linkProperties.getDnses().size() == 0) {
+ for (InetAddress d : orig.linkProperties.getDnses()) linkProperties.addDns(d);
+ }
+ }
+
+ /**
+ * Test if this DHCP lease includes vendor hint that network link is
+ * metered, and sensitive to heavy data transfers.
+ */
+ public boolean hasMeteredHint() {
+ if (vendorInfo != null) {
+ return vendorInfo.contains("ANDROID_METERED");
+ } else {
+ return false;
+ }
+ }
+
+ public void clear() {
+ linkProperties.clear();
+ serverAddress = null;
+ vendorInfo = null;
+ leaseDuration = 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer str = new StringBuffer(linkProperties.toString());
+
+ str.append(" DHCP server ").append(serverAddress);
+ str.append(" Vendor info ").append(vendorInfo);
+ str.append(" lease ").append(leaseDuration).append(" seconds");
+
+ return str.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof DhcpResults)) return false;
+
+ DhcpResults target = (DhcpResults)obj;
+
+ if (linkProperties == null) {
+ if (target.linkProperties != null) return false;
+ } else if (!linkProperties.equals(target.linkProperties)) return false;
+ if (serverAddress == null) {
+ if (target.serverAddress != null) return false;
+ } else if (!serverAddress.equals(target.serverAddress)) return false;
+ if (vendorInfo == null) {
+ if (target.vendorInfo != null) return false;
+ } else if (!vendorInfo.equals(target.vendorInfo)) return false;
+ if (leaseDuration != target.leaseDuration) return false;
+
+ return true;
+ }
+
+ /** Implement the Parcelable interface */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ public void writeToParcel(Parcel dest, int flags) {
+ linkProperties.writeToParcel(dest, flags);
+
+ dest.writeInt(leaseDuration);
+
+ if (serverAddress != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(serverAddress.getAddress());
+ } else {
+ dest.writeByte((byte)0);
+ }
+
+ dest.writeString(vendorInfo);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<DhcpResults> CREATOR =
+ new Creator<DhcpResults>() {
+ public DhcpResults createFromParcel(Parcel in) {
+ DhcpResults prop = new DhcpResults((LinkProperties)in.readParcelable(null));
+
+ prop.leaseDuration = in.readInt();
+
+ if (in.readByte() == 1) {
+ try {
+ prop.serverAddress = InetAddress.getByAddress(in.createByteArray());
+ } catch (UnknownHostException e) {}
+ }
+
+ prop.vendorInfo = in.readString();
+
+ return prop;
+ }
+
+ public DhcpResults[] newArray(int size) {
+ return new DhcpResults[size];
+ }
+ };
+
+ // Utils for jni population - false on success
+ public void setInterfaceName(String interfaceName) {
+ linkProperties.setInterfaceName(interfaceName);
+ }
+
+ public boolean addLinkAddress(String addrString, int prefixLength) {
+ InetAddress addr;
+ try {
+ addr = NetworkUtils.numericToInetAddress(addrString);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "addLinkAddress failed with addrString " + addrString);
+ return true;
+ }
+
+ LinkAddress linkAddress = new LinkAddress(addr, prefixLength);
+ linkProperties.addLinkAddress(linkAddress);
+
+ RouteInfo routeInfo = new RouteInfo(linkAddress);
+ linkProperties.addRoute(routeInfo);
+ return false;
+ }
+
+ public boolean addGateway(String addrString) {
+ try {
+ linkProperties.addRoute(new RouteInfo(NetworkUtils.numericToInetAddress(addrString)));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "addGateway failed with addrString " + addrString);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean addDns(String addrString) {
+ if (TextUtils.isEmpty(addrString) == false) {
+ try {
+ linkProperties.addDns(NetworkUtils.numericToInetAddress(addrString));
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "addDns failed with addrString " + addrString);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean setServerAddress(String addrString) {
+ try {
+ serverAddress = NetworkUtils.numericToInetAddress(addrString);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "setServerAddress failed with addrString " + addrString);
+ return true;
+ }
+ return false;
+ }
+
+ public void setLeaseDuration(int duration) {
+ leaseDuration = duration;
+ }
+
+ public void setVendorInfo(String info) {
+ vendorInfo = info;
+ }
+
+ public void setDomains(String domains) {
+ linkProperties.setDomains(domains);
+ }
+}
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index 8dc900e..fd22b10 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -26,7 +26,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.DhcpInfoInternal;
+import android.net.DhcpResults;
import android.net.NetworkUtils;
import android.os.Message;
import android.os.PowerManager;
@@ -64,7 +64,7 @@ public class DhcpStateMachine extends StateMachine {
private static final String WAKELOCK_TAG = "DHCP";
//Remember DHCP configuration from first request
- private DhcpInfoInternal mDhcpInfo;
+ private DhcpResults mDhcpResults;
private static final int DHCP_RENEW = 0;
private static final String ACTION_DHCP_RENEW = "android.net.wifi.DHCP_RENEW";
@@ -348,23 +348,21 @@ public class DhcpStateMachine extends StateMachine {
private boolean runDhcp(DhcpAction dhcpAction) {
boolean success = false;
- DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
+ DhcpResults dhcpResults = new DhcpResults();
if (dhcpAction == DhcpAction.START) {
/* Stop any existing DHCP daemon before starting new */
NetworkUtils.stopDhcp(mInterfaceName);
if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
- success = NetworkUtils.runDhcp(mInterfaceName, dhcpInfoInternal);
- mDhcpInfo = dhcpInfoInternal;
+ success = NetworkUtils.runDhcp(mInterfaceName, dhcpResults);
} else if (dhcpAction == DhcpAction.RENEW) {
if (DBG) Log.d(TAG, "DHCP renewal on " + mInterfaceName);
- success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpInfoInternal);
- dhcpInfoInternal.updateFromDhcpRequest(mDhcpInfo);
+ success = NetworkUtils.runDhcpRenew(mInterfaceName, dhcpResults);
+ dhcpResults.updateFromDhcpRequest(mDhcpResults);
}
-
if (success) {
if (DBG) Log.d(TAG, "DHCP succeeded on " + mInterfaceName);
- long leaseDuration = dhcpInfoInternal.leaseDuration; //int to long conversion
+ long leaseDuration = dhcpResults.leaseDuration; //int to long conversion
//Sanity check for renewal
if (leaseDuration >= 0) {
@@ -384,7 +382,8 @@ public class DhcpStateMachine extends StateMachine {
//infinite lease time, no renewal needed
}
- mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpInfoInternal)
+ mDhcpResults = dhcpResults;
+ mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults)
.sendToTarget();
} else {
Log.e(TAG, "DHCP failed on " + mInterfaceName + ": " +
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 37601fc..8947162 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -170,13 +170,12 @@ public class EthernetDataTracker implements NetworkStateTracker {
private void runDhcp() {
Thread dhcpThread = new Thread(new Runnable() {
public void run() {
- DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
- if (!NetworkUtils.runDhcp(mIface, dhcpInfoInternal)) {
+ DhcpResults dhcpResults = new DhcpResults();
+ if (!NetworkUtils.runDhcp(mIface, dhcpResults)) {
Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
return;
}
- mLinkProperties = dhcpInfoInternal.makeLinkProperties();
- mLinkProperties.setInterfaceName(mIface);
+ mLinkProperties = dhcpResults.linkProperties;
mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 75646fd..b9362da 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -51,9 +51,10 @@ import java.util.Collections;
*/
public class LinkProperties implements Parcelable {
- String mIfaceName;
+ private String mIfaceName;
private Collection<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
private Collection<InetAddress> mDnses = new ArrayList<InetAddress>();
+ private String mDomains;
private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
private ProxyProperties mHttpProxy;
@@ -82,9 +83,10 @@ public class LinkProperties implements Parcelable {
mIfaceName = source.getInterfaceName();
for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
for (InetAddress i : source.getDnses()) mDnses.add(i);
+ mDomains = source.getDomains();
for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
mHttpProxy = (source.getHttpProxy() == null) ?
- null : new ProxyProperties(source.getHttpProxy());
+ null : new ProxyProperties(source.getHttpProxy());
}
}
@@ -120,6 +122,14 @@ public class LinkProperties implements Parcelable {
return Collections.unmodifiableCollection(mDnses);
}
+ public String getDomains() {
+ return mDomains;
+ }
+
+ public void setDomains(String domains) {
+ mDomains = domains;
+ }
+
public void addRoute(RouteInfo route) {
if (route != null) mRoutes.add(route);
}
@@ -138,6 +148,7 @@ public class LinkProperties implements Parcelable {
mIfaceName = null;
mLinkAddresses.clear();
mDnses.clear();
+ mDomains = null;
mRoutes.clear();
mHttpProxy = null;
}
@@ -162,12 +173,14 @@ public class LinkProperties implements Parcelable {
for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
dns += "] ";
- String routes = "Routes: [";
+ String domainName = "Domains: " + mDomains;
+
+ String routes = " Routes: [";
for (RouteInfo route : mRoutes) routes += route.toString() + ",";
routes += "] ";
String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
- return ifaceName + linkAddresses + routes + dns + proxy;
+ return ifaceName + linkAddresses + routes + dns + domainName + proxy;
}
/**
@@ -181,7 +194,7 @@ public class LinkProperties implements Parcelable {
}
/**
- * Compares this {@code LinkProperties} interface name against the target
+ * Compares this {@code LinkProperties} interface addresses against the target
*
* @param target LinkProperties to compare.
* @return {@code true} if both are identical, {@code false} otherwise.
@@ -201,6 +214,12 @@ public class LinkProperties implements Parcelable {
*/
public boolean isIdenticalDnses(LinkProperties target) {
Collection<InetAddress> targetDnses = target.getDnses();
+ String targetDomains = target.getDomains();
+ if (mDomains == null) {
+ if (targetDomains != null) return false;
+ } else {
+ if (mDomains.equals(targetDomains) == false) return false;
+ }
return (mDnses.size() == targetDnses.size()) ?
mDnses.containsAll(targetDnses) : false;
}
@@ -359,13 +378,13 @@ public class LinkProperties implements Parcelable {
return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
+ mLinkAddresses.size() * 31
+ mDnses.size() * 37
+ + ((null == mDomains) ? 0 : mDomains.hashCode())
+ mRoutes.size() * 41
+ ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()));
}
/**
* Implement the Parcelable interface.
- * @hide
*/
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getInterfaceName());
@@ -378,6 +397,7 @@ public class LinkProperties implements Parcelable {
for(InetAddress d : mDnses) {
dest.writeByteArray(d.getAddress());
}
+ dest.writeString(mDomains);
dest.writeInt(mRoutes.size());
for(RouteInfo route : mRoutes) {
@@ -394,19 +414,15 @@ public class LinkProperties implements Parcelable {
/**
* Implement the Parcelable interface.
- * @hide
*/
public static final Creator<LinkProperties> CREATOR =
new Creator<LinkProperties>() {
public LinkProperties createFromParcel(Parcel in) {
LinkProperties netProp = new LinkProperties();
+
String iface = in.readString();
if (iface != null) {
- try {
- netProp.setInterfaceName(iface);
- } catch (Exception e) {
- return null;
- }
+ netProp.setInterfaceName(iface);
}
int addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
@@ -418,6 +434,7 @@ public class LinkProperties implements Parcelable {
netProp.addDns(InetAddress.getByAddress(in.createByteArray()));
} catch (UnknownHostException e) { }
}
+ netProp.setDomains(in.readString());
addressCount = in.readInt();
for (int i=0; i<addressCount; i++) {
netProp.addRoute((RouteInfo)in.readParcelable(null));
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 0b23cb7..689dae5 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -19,6 +19,8 @@ package android.net;
import android.os.Parcelable;
import android.os.Parcel;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.EnumMap;
/**
@@ -312,7 +314,9 @@ public class NetworkInfo implements Parcelable {
}
}
- void setRoaming(boolean isRoaming) {
+ /** {@hide} */
+ @VisibleForTesting
+ public void setRoaming(boolean isRoaming) {
synchronized (this) {
mIsRoaming = isRoaming;
}
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index c757605..9cb904d 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -135,6 +135,18 @@ public class NetworkStats implements Parcelable {
builder.append(" operations=").append(operations);
return builder.toString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof Entry) {
+ final Entry e = (Entry) o;
+ return uid == e.uid && set == e.set && tag == e.tag && rxBytes == e.rxBytes
+ && rxPackets == e.rxPackets && txBytes == e.txBytes
+ && txPackets == e.txPackets && operations == e.operations
+ && iface.equals(e.iface);
+ }
+ return false;
+ }
}
public NetworkStats(long elapsedRealtime, int initialSize) {
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index d3839ad..c189ba4 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -22,6 +22,7 @@ import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
import static android.net.ConnectivityManager.TYPE_WIMAX;
import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
import static android.net.NetworkIdentity.scrubSubscriberId;
+import static android.net.wifi.WifiInfo.removeDoubleQuotes;
import static android.telephony.TelephonyManager.NETWORK_CLASS_2_G;
import static android.telephony.TelephonyManager.NETWORK_CLASS_3_G;
import static android.telephony.TelephonyManager.NETWORK_CLASS_4_G;
@@ -279,7 +280,8 @@ public class NetworkTemplate implements Parcelable {
private boolean matchesWifi(NetworkIdentity ident) {
switch (ident.mType) {
case TYPE_WIFI:
- return Objects.equal(mNetworkId, ident.mNetworkId);
+ return Objects.equal(
+ removeDoubleQuotes(mNetworkId), removeDoubleQuotes(ident.mNetworkId));
default:
return false;
}
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d39e741..4ab479e 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -62,21 +62,21 @@ public class NetworkUtils {
* addresses. This call blocks until it obtains a result (either success
* or failure) from the daemon.
* @param interfaceName the name of the interface to configure
- * @param ipInfo if the request succeeds, this object is filled in with
+ * @param dhcpResults if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcp(String interfaceName, DhcpInfoInternal ipInfo);
+ public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults);
/**
* Initiate renewal on the Dhcp client daemon. This call blocks until it obtains
* a result (either success or failure) from the daemon.
* @param interfaceName the name of the interface to configure
- * @param ipInfo if the request succeeds, this object is filled in with
+ * @param dhcpResults if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcpRenew(String interfaceName, DhcpInfoInternal ipInfo);
+ public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults);
/**
* Shut down the DHCP client daemon.
@@ -124,12 +124,9 @@ public class NetworkUtils {
* @param inetAddr is an InetAddress corresponding to the IPv4 address
* @return the IP address as an integer in network byte order
*/
- public static int inetAddressToInt(InetAddress inetAddr)
+ public static int inetAddressToInt(Inet4Address inetAddr)
throws IllegalArgumentException {
byte [] addr = inetAddr.getAddress();
- if (addr.length != 4) {
- throw new IllegalArgumentException("Not an IPv4 address");
- }
return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) |
((addr[1] & 0xff) << 8) | (addr[0] & 0xff);
}
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 275f32a..112e143 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -76,6 +76,10 @@ public class RouteInfo implements Parcelable {
this(null, gateway);
}
+ public RouteInfo(LinkAddress host) {
+ this(host, null);
+ }
+
public static RouteInfo makeHostRoute(InetAddress host) {
return makeHostRoute(host, null);
}
diff --git a/core/java/android/net/ThrottleManager.java b/core/java/android/net/ThrottleManager.java
deleted file mode 100644
index 5fdac58..0000000
--- a/core/java/android/net/ThrottleManager.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.annotation.SdkConstant;
-import android.annotation.SdkConstant.SdkConstantType;
-import android.os.Binder;
-import android.os.RemoteException;
-
-/**
- * Class that handles throttling. It provides read/write numbers per interface
- * and methods to apply throttled rates.
- * {@hide}
- */
-public class ThrottleManager
-{
- /**
- * Broadcast each polling period to indicate new data counts.
- *
- * Includes four extras:
- * EXTRA_CYCLE_READ - a long of the read bytecount for the current cycle
- * EXTRA_CYCLE_WRITE -a long of the write bytecount for the current cycle
- * EXTRA_CYLCE_START -a long of MS for the cycle start time
- * EXTRA_CYCLE_END -a long of MS for the cycle stop time
- * {@hide}
- */
- public static final String THROTTLE_POLL_ACTION = "android.net.thrott.POLL_ACTION";
- /**
- * The lookup key for a long for the read bytecount for this period. Retrieve with
- * {@link android.content.Intent#getLongExtra(String)}.
- * {@hide}
- */
- public static final String EXTRA_CYCLE_READ = "cycleRead";
- /**
- * contains a long of the number of bytes written in the cycle
- * {@hide}
- */
- public static final String EXTRA_CYCLE_WRITE = "cycleWrite";
- /**
- * contains a long of the number of bytes read in the cycle
- * {@hide}
- */
- public static final String EXTRA_CYCLE_START = "cycleStart";
- /**
- * contains a long of the ms since 1970 used to init a calendar, etc for the end
- * of the cycle
- * {@hide}
- */
- public static final String EXTRA_CYCLE_END = "cycleEnd";
-
- /**
- * Broadcast when the thottle level changes.
- * {@hide}
- */
- public static final String THROTTLE_ACTION = "android.net.thrott.THROTTLE_ACTION";
- /**
- * int of the current bandwidth in TODO
- * {@hide}
- */
- public static final String EXTRA_THROTTLE_LEVEL = "level";
-
- /**
- * Broadcast on boot and whenever the settings change.
- * {@hide}
- */
- public static final String POLICY_CHANGED_ACTION = "android.net.thrott.POLICY_CHANGED_ACTION";
-
- // {@hide}
- public static final int DIRECTION_TX = 0;
- // {@hide}
- public static final int DIRECTION_RX = 1;
-
- // {@hide}
- public static final int PERIOD_CYCLE = 0;
- // {@hide}
- public static final int PERIOD_YEAR = 1;
- // {@hide}
- public static final int PERIOD_MONTH = 2;
- // {@hide}
- public static final int PERIOD_WEEK = 3;
- // @hide
- public static final int PERIOD_7DAY = 4;
- // @hide
- public static final int PERIOD_DAY = 5;
- // @hide
- public static final int PERIOD_24HOUR = 6;
- // @hide
- public static final int PERIOD_HOUR = 7;
- // @hide
- public static final int PERIOD_60MIN = 8;
- // @hide
- public static final int PERIOD_MINUTE = 9;
- // @hide
- public static final int PERIOD_60SEC = 10;
- // @hide
- public static final int PERIOD_SECOND = 11;
-
-
-
- /**
- * returns a long of the ms from the epoch to the time the current cycle ends for the
- * named interface
- * {@hide}
- */
- public long getResetTime(String iface) {
- try {
- return mService.getResetTime(iface);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns a long of the ms from the epoch to the time the current cycle started for the
- * named interface
- * {@hide}
- */
- public long getPeriodStartTime(String iface) {
- try {
- return mService.getPeriodStartTime(iface);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns a long of the byte count either read or written on the named interface
- * for the period described. Direction is either DIRECTION_RX or DIRECTION_TX and
- * period may only be PERIOD_CYCLE for the current cycle (other periods may be supported
- * in the future). Ago indicates the number of periods in the past to lookup - 0 means
- * the current period, 1 is the last one, 2 was two periods ago..
- * {@hide}
- */
- public long getByteCount(String iface, int direction, int period, int ago) {
- try {
- return mService.getByteCount(iface, direction, period, ago);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns the number of bytes read+written after which a particular cliff
- * takes effect on the named iface. Currently only cliff #1 is supported (1 step)
- * {@hide}
- */
- public long getCliffThreshold(String iface, int cliff) {
- try {
- return mService.getCliffThreshold(iface, cliff);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns the thottling bandwidth (bps) for a given cliff # on the named iface.
- * only cliff #1 is currently supported.
- * {@hide}
- */
- public int getCliffLevel(String iface, int cliff) {
- try {
- return mService.getCliffLevel(iface, cliff);
- } catch (RemoteException e) {
- return -1;
- }
- }
-
- /**
- * returns the help URI for throttling
- * {@hide}
- */
- public String getHelpUri() {
- try {
- return mService.getHelpUri();
- } catch (RemoteException e) {
- return null;
- }
- }
-
-
- private IThrottleManager mService;
-
- /**
- * Don't allow use of default constructor.
- */
- @SuppressWarnings({"UnusedDeclaration"})
- private ThrottleManager() {
- }
-
- /**
- * {@hide}
- */
- public ThrottleManager(IThrottleManager service) {
- if (service == null) {
- throw new IllegalArgumentException(
- "ThrottleManager() cannot be constructed with null service");
- }
- mService = service;
- }
-}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index e437d2e..ce1276f 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -315,6 +315,30 @@ public class TrafficStats {
return total;
}
+ /** {@hide} */
+ public static long getMobileTcpRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ final long stat = nativeGetIfaceStat(iface, TYPE_TCP_RX_PACKETS);
+ if (stat != UNSUPPORTED) {
+ total += stat;
+ }
+ }
+ return total;
+ }
+
+ /** {@hide} */
+ public static long getMobileTcpTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ final long stat = nativeGetIfaceStat(iface, TYPE_TCP_TX_PACKETS);
+ if (stat != UNSUPPORTED) {
+ total += stat;
+ }
+ }
+ return total;
+ }
+
/**
* Get the total number of packets transmitted through the specified interface.
*
@@ -400,161 +424,156 @@ public class TrafficStats {
}
/**
- * Get the number of bytes sent through the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of bytes transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidTxBytes(int uid);
+ public static long getUidTxBytes(int uid) {
+ return nativeGetUidStat(uid, TYPE_TX_BYTES);
+ }
/**
- * Get the number of bytes received through the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of bytes received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of bytes
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidRxBytes(int uid);
+ public static long getUidRxBytes(int uid) {
+ return nativeGetUidStat(uid, TYPE_RX_BYTES);
+ }
/**
- * Get the number of packets (TCP segments + UDP) sent through
- * the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of packets transmitted by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of packets.
- * If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidTxPackets(int uid);
+ public static long getUidTxPackets(int uid) {
+ return nativeGetUidStat(uid, TYPE_TX_PACKETS);
+ }
/**
- * Get the number of packets (TCP segments + UDP) received through
- * the network for this UID.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
+ * Return number of packets received by the given UID since device boot.
+ * Counts packets across all network interfaces, and always increases
+ * monotonically since device boot. Statistics are measured at the network
+ * layer, so they include both TCP and UDP usage.
+ * <p>
+ * Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may return
+ * {@link #UNSUPPORTED} on devices where statistics aren't available.
*
- * @param uid The UID of the process to examine.
- * @return number of packets
+ * @see android.os.Process#myUid()
+ * @see android.content.pm.ApplicationInfo#uid
*/
- public static native long getUidRxPackets(int uid);
+ public static long getUidRxPackets(int uid) {
+ return nativeGetUidStat(uid, TYPE_RX_PACKETS);
+ }
/**
- * Get the number of TCP payload bytes sent for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
*/
- public static native long getUidTcpTxBytes(int uid);
+ @Deprecated
+ public static long getUidTcpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of TCP payload bytes received for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
*/
- public static native long getUidTcpRxBytes(int uid);
+ @Deprecated
+ public static long getUidTcpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP payload bytes sent for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxBytes(int)
*/
- public static native long getUidUdpTxBytes(int uid);
+ @Deprecated
+ public static long getUidUdpTxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP payload bytes received for this UID.
- * This total does not include protocol and control overheads at
- * the transport and the lower layers of the networking stack.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of bytes. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxBytes(int)
*/
- public static native long getUidUdpRxBytes(int uid);
+ @Deprecated
+ public static long getUidUdpRxBytes(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of TCP segments sent for this UID.
- * Does not include TCP control packets (SYN/ACKs/FIN/..).
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of TCP segments. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
*/
- public static native long getUidTcpTxSegments(int uid);
+ @Deprecated
+ public static long getUidTcpTxSegments(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of TCP segments received for this UID.
- * Does not include TCP control packets (SYN/ACKs/FIN/..).
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of TCP segments. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
*/
- public static native long getUidTcpRxSegments(int uid);
+ @Deprecated
+ public static long getUidTcpRxSegments(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP packets sent for this UID.
- * Includes DNS requests.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidTxPackets(int)
*/
- public static native long getUidUdpTxPackets(int uid);
+ @Deprecated
+ public static long getUidUdpTxPackets(int uid) {
+ return UNSUPPORTED;
+ }
/**
- * Get the number of UDP packets received for this UID.
- * Includes DNS responses.
- * The statistics are across all interfaces.
- *
- * {@see android.os.Process#myUid()}.
- *
- * @param uid The UID of the process to examine.
- * @return number of packets. If the statistics are not supported by this device,
- * {@link #UNSUPPORTED} will be returned.
+ * @deprecated Starting in {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+ * transport layer statistics are no longer available, and will
+ * always return {@link #UNSUPPORTED}.
+ * @see #getUidRxPackets(int)
*/
- public static native long getUidUdpRxPackets(int uid);
+ @Deprecated
+ public static long getUidUdpRxPackets(int uid) {
+ return UNSUPPORTED;
+ }
/**
* Return detailed {@link NetworkStats} for the current UID. Requires no
@@ -587,7 +606,10 @@ public class TrafficStats {
private static final int TYPE_RX_PACKETS = 1;
private static final int TYPE_TX_BYTES = 2;
private static final int TYPE_TX_PACKETS = 3;
+ private static final int TYPE_TCP_RX_PACKETS = 4;
+ private static final int TYPE_TCP_TX_PACKETS = 5;
private static native long nativeGetTotalStat(int type);
private static native long nativeGetIfaceStat(String iface, int type);
+ private static native long nativeGetUidStat(int uid, int type);
}
diff --git a/core/java/android/net/http/AndroidHttpClient.java b/core/java/android/net/http/AndroidHttpClient.java
index fabe018..04f3974 100644
--- a/core/java/android/net/http/AndroidHttpClient.java
+++ b/core/java/android/net/http/AndroidHttpClient.java
@@ -17,6 +17,7 @@
package android.net.http;
import com.android.internal.http.HttpDateTime;
+
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -25,18 +26,18 @@ import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
-import org.apache.http.entity.AbstractHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
+import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
@@ -44,26 +45,26 @@ import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.BasicHttpContext;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-import java.net.URI;
-
-import android.content.Context;
import android.content.ContentResolver;
+import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.net.SSLSessionCache;
import android.os.Looper;
import android.util.Base64;
import android.util.Log;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
/**
* Implementation of the Apache {@link DefaultHttpClient} that is configured with
* reasonable default settings and registered schemes for Android.
@@ -266,7 +267,7 @@ public final class AndroidHttpClient implements HttpClient {
return delegate.execute(target, request, context);
}
- public <T> T execute(HttpUriRequest request,
+ public <T> T execute(HttpUriRequest request,
ResponseHandler<? extends T> responseHandler)
throws IOException, ClientProtocolException {
return delegate.execute(request, responseHandler);
@@ -404,6 +405,11 @@ public final class AndroidHttpClient implements HttpClient {
builder.append("curl ");
+ // add in the method
+ builder.append("-X ");
+ builder.append(request.getMethod());
+ builder.append(" ");
+
for (Header header: request.getAllHeaders()) {
if (!logAuthToken
&& (header.getName().equals("Authorization") ||
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 53b41d5..7c3123f 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -197,7 +197,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
@@ -211,7 +211,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
@@ -223,7 +223,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
@@ -236,7 +236,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
@@ -249,18 +249,17 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
isResumed = state.resumed;
}
if (isResumed) {
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
}
/**
* Request or unrequest NFC service callbacks for NDEF push.
* Makes IPC call - do not hold lock.
- * TODO: Do not do IPC on every onPause/onResume
*/
- void requestNfcServiceCallback(boolean request) {
+ void requestNfcServiceCallback() {
try {
- NfcAdapter.sService.setNdefPushCallback(request ? this : null);
+ NfcAdapter.sService.setNdefPushCallback(this);
} catch (RemoteException e) {
mAdapter.attemptDeadServiceRecovery(e);
}
@@ -355,7 +354,7 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
if (state == null) return;
state.resumed = true;
}
- requestNfcServiceCallback(true);
+ requestNfcServiceCallback();
}
/** Callback from Activity life-cycle, on main thread */
@@ -367,7 +366,6 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
if (state == null) return;
state.resumed = false;
}
- requestNfcServiceCallback(false);
}
/** Callback from Activity life-cycle, on main thread */
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 4baceed..6ad382b 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -174,31 +174,25 @@ public final class NfcAdapter {
* Broadcast Action: The state of the local NFC adapter has been
* changed.
* <p>For example, NFC has been turned on or off.
- * <p>Always contains the extra field {@link #EXTRA_STATE}
- * @hide
+ * <p>Always contains the extra field {@link #EXTRA_ADAPTER_STATE}
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ADAPTER_STATE_CHANGED =
"android.nfc.action.ADAPTER_STATE_CHANGED";
/**
- * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
+ * Used as an int extra field in {@link #ACTION_ADAPTER_STATE_CHANGED}
* intents to request the current power state. Possible values are:
* {@link #STATE_OFF},
* {@link #STATE_TURNING_ON},
* {@link #STATE_ON},
* {@link #STATE_TURNING_OFF},
- * @hide
*/
public static final String EXTRA_ADAPTER_STATE = "android.nfc.extra.ADAPTER_STATE";
- /** @hide */
public static final int STATE_OFF = 1;
- /** @hide */
public static final int STATE_TURNING_ON = 2;
- /** @hide */
public static final int STATE_ON = 3;
- /** @hide */
public static final int STATE_TURNING_OFF = 4;
/** @hide */
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9821824..499ec77 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -93,6 +93,11 @@ public abstract class BatteryStats implements Parcelable {
public static final int VIDEO_TURNED_ON = 8;
/**
+ * A constant indicating a vibrator on timer
+ */
+ public static final int VIBRATOR_ON = 9;
+
+ /**
* Include all of the data in the stats, including previously saved data.
*/
public static final int STATS_SINCE_CHARGED = 0;
@@ -131,6 +136,7 @@ public abstract class BatteryStats implements Parcelable {
private static final String APK_DATA = "apk";
private static final String PROCESS_DATA = "pr";
private static final String SENSOR_DATA = "sr";
+ private static final String VIBRATOR_DATA = "vib";
private static final String WAKELOCK_DATA = "wl";
private static final String KERNEL_WAKELOCK_DATA = "kwl";
private static final String NETWORK_DATA = "nt";
@@ -277,6 +283,7 @@ public abstract class BatteryStats implements Parcelable {
int which);
public abstract long getAudioTurnedOnTime(long batteryRealtime, int which);
public abstract long getVideoTurnedOnTime(long batteryRealtime, int which);
+ public abstract Timer getVibratorOnTimer();
/**
* Note that these must match the constants in android.os.PowerManager.
@@ -294,6 +301,11 @@ public abstract class BatteryStats implements Parcelable {
public abstract int getUserActivityCount(int type, int which);
public static abstract class Sensor {
+ /*
+ * FIXME: it's not correct to use this magic value because it
+ * could clash with a sensor handle (which are defined by
+ * the sensor HAL, and therefore out of our control
+ */
// Magic sensor number for the GPS.
public static final int GPS = -10000;
@@ -1395,6 +1407,16 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ Timer vibTimer = u.getVibratorOnTimer();
+ if (vibTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (vibTimer.getTotalTimeLocked(batteryRealtime, which) + 500) / 1000;
+ int count = vibTimer.getCountLocked(which);
+ if (totalTime != 0) {
+ dumpLine(pw, uid, category, VIBRATOR_DATA, totalTime, count);
+ }
+ }
+
Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
if (processStats.size() > 0) {
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
@@ -1919,6 +1941,26 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ Timer vibTimer = u.getVibratorOnTimer();
+ if (vibTimer != null) {
+ // Convert from microseconds to milliseconds with rounding
+ long totalTime = (vibTimer.getTotalTimeLocked(
+ batteryRealtime, which) + 500) / 1000;
+ int count = vibTimer.getCountLocked(which);
+ //timer.logState();
+ if (totalTime != 0) {
+ sb.setLength(0);
+ sb.append(prefix);
+ sb.append(" Vibrator: ");
+ formatTimeMs(sb, totalTime);
+ sb.append("realtime (");
+ sb.append(count);
+ sb.append(" times)");
+ pw.println(sb.toString());
+ uidActivity = true;
+ }
+ }
+
Map<String, ? extends BatteryStats.Uid.Proc> processStats = u.getProcessStats();
if (processStats.size() > 0) {
for (Map.Entry<String, ? extends BatteryStats.Uid.Proc> ent
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a7f39d5..97ac862 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -431,6 +431,11 @@ public class Build {
* </ul>
*/
public static final int JELLY_BEAN_MR1 = 17;
+
+ /**
+ * Android 4.X: Jelly Bean MR2, the revenge of the beans.
+ */
+ public static final int JELLY_BEAN_MR2 = 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 460a5fe..18a0018 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -742,6 +742,25 @@ public final class Bundle implements Parcelable, Cloneable {
}
/**
+ * Inserts an {@link IBinder} value into the mapping of this Bundle, replacing
+ * any existing value for the given key. Either key or value may be null.
+ *
+ * <p class="note">You should be very careful when using this function. In many
+ * places where Bundles are used (such as inside of Intent objects), the Bundle
+ * can live longer inside of another process than the process that had originally
+ * created it. In that case, the IBinder you supply here will become invalid
+ * when your process goes away, and no longer usable, even if a new process is
+ * created for you later on.</p>
+ *
+ * @param key a String, or null
+ * @param value an IBinder object, or null
+ */
+ public void putBinder(String key, IBinder value) {
+ unparcel();
+ mMap.put(key, value);
+ }
+
+ /**
* Inserts an IBinder value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -749,7 +768,7 @@ public final class Bundle implements Parcelable, Cloneable {
* @param value an IBinder object, or null
*
* @deprecated
- * @hide
+ * @hide This is the old name of the function.
*/
@Deprecated
public void putIBinder(String key, IBinder value) {
@@ -1061,10 +1080,7 @@ public final class Bundle implements Parcelable, Cloneable {
*/
public String getString(String key) {
unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
+ final Object o = mMap.get(key);
try {
return (String) o;
} catch (ClassCastException e) {
@@ -1079,20 +1095,12 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @param key a String, or null
* @param defaultValue Value to return if key does not exist
- * @return a String value, or null
+ * @return the String value associated with the given key, or defaultValue
+ * if no valid String object is currently mapped to that key.
*/
public String getString(String key, String defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (String) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "String", e);
- return defaultValue;
- }
+ final String s = getString(key);
+ return (s == null) ? defaultValue : s;
}
/**
@@ -1105,10 +1113,7 @@ public final class Bundle implements Parcelable, Cloneable {
*/
public CharSequence getCharSequence(String key) {
unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
+ final Object o = mMap.get(key);
try {
return (CharSequence) o;
} catch (ClassCastException e) {
@@ -1123,20 +1128,12 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @param key a String, or null
* @param defaultValue Value to return if key does not exist
- * @return a CharSequence value, or null
+ * @return the CharSequence value associated with the given key, or defaultValue
+ * if no valid CharSequence object is currently mapped to that key.
*/
public CharSequence getCharSequence(String key, CharSequence defaultValue) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return defaultValue;
- }
- try {
- return (CharSequence) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "CharSequence", e);
- return defaultValue;
- }
+ final CharSequence cs = getCharSequence(key);
+ return (cs == null) ? defaultValue : cs;
}
/**
@@ -1565,9 +1562,31 @@ public final class Bundle implements Parcelable, Cloneable {
*
* @param key a String, or null
* @return an IBinder value, or null
+ */
+ public IBinder getBinder(String key) {
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (IBinder) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "IBinder", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the value associated with the given key, or null if
+ * no mapping of the desired type exists for the given key or a null
+ * value is explicitly associated with the key.
+ *
+ * @param key a String, or null
+ * @return an IBinder value, or null
*
* @deprecated
- * @hide
+ * @hide This is the old name of the function.
*/
@Deprecated
public IBinder getIBinder(String key) {
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 2179fa1..c765457 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -306,23 +306,6 @@ interface INetworkManagementService
boolean isBandwidthControlEnabled();
/**
- * Configures bandwidth throttling on an interface.
- */
- void setInterfaceThrottle(String iface, int rxKbps, int txKbps);
-
- /**
- * Returns the currently configured RX throttle values
- * for the specified interface
- */
- int getInterfaceRxThrottle(String iface);
-
- /**
- * Returns the currently configured TX throttle values
- * for the specified interface
- */
- int getInterfaceTxThrottle(String iface);
-
- /**
* Sets idletimer for an interface.
*
* This either initializes a new idletimer or increases its
@@ -351,7 +334,7 @@ interface INetworkManagementService
/**
* Bind name servers to an interface in the DNS resolver.
*/
- void setDnsServersForInterface(String iface, in String[] servers);
+ void setDnsServersForInterface(String iface, in String[] servers, String domains);
/**
* Flush the DNS cache associated with the default interface.
@@ -369,4 +352,14 @@ interface INetworkManagementService
void setFirewallEgressSourceRule(String addr, boolean allow);
void setFirewallEgressDestRule(String addr, int port, boolean allow);
void setFirewallUidRule(int uid, boolean allow);
+
+ /**
+ * Set a process (pid) to use the name servers associated with the specified interface.
+ */
+ void setDnsInterfaceForPid(String iface, int pid);
+
+ /**
+ * Clear a process (pid) from being associated with an interface.
+ */
+ void clearDnsInterfaceForPid(int pid);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index ec02ae0..34c9740 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -17,6 +17,7 @@
package android.os;
+import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
@@ -37,4 +38,6 @@ interface IUserManager {
void wipeUser(int userHandle);
int getUserSerialNumber(int userHandle);
int getUserHandle(int userSerialNumber);
+ Bundle getUserRestrictions(int userHandle);
+ void setUserRestrictions(in Bundle restrictions, int userHandle);
}
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 2c2fe8a..456ffb1 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -20,8 +20,8 @@ package android.os;
interface IVibratorService
{
boolean hasVibrator();
- void vibrate(long milliseconds, IBinder token);
- void vibratePattern(in long[] pattern, int repeat, IBinder token);
+ void vibrate(int uid, String packageName, long milliseconds, IBinder token);
+ void vibratePattern(int uid, String packageName, in long[] pattern, int repeat, IBinder token);
void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 5ad60ec..222578a 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -48,10 +48,10 @@ public class MessageQueue {
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
private int mNextBarrierToken;
- private native void nativeInit();
- private native void nativeDestroy();
- private native void nativePollOnce(int ptr, int timeoutMillis);
- private native void nativeWake(int ptr);
+ private native static int nativeInit();
+ private native static void nativeDestroy(int ptr);
+ private native static void nativePollOnce(int ptr, int timeoutMillis);
+ private native static void nativeWake(int ptr);
/**
* Callback interface for discovering when a thread is going to block
@@ -102,18 +102,25 @@ public class MessageQueue {
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
- nativeInit();
+ mPtr = nativeInit();
}
@Override
protected void finalize() throws Throwable {
try {
- nativeDestroy();
+ dispose();
} finally {
super.finalize();
}
}
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
+
final Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
@@ -126,6 +133,7 @@ public class MessageQueue {
synchronized (this) {
if (mQuiting) {
+ dispose();
return null;
}
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index 8de4e06..ac6027f 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -49,6 +49,22 @@ public class NullVibrator extends Vibrator {
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+ vibrate(milliseconds);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
+ vibrate(pattern, repeat);
+ }
+
@Override
public void cancel() {
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 788ab74..31d323b 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1254,6 +1254,12 @@ public final class Parcel {
p.writeToParcel(this, parcelableFlags);
}
+ /** @hide */
+ public final void writeParcelableCreator(Parcelable p) {
+ String name = p.getClass().getName();
+ writeString(name);
+ }
+
/**
* Write a generic serializable object in to a Parcel. It is strongly
* recommended that this method be avoided, since the serialization
@@ -2046,6 +2052,28 @@ public final class Parcel {
* was an error trying to instantiate the Parcelable.
*/
public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
+ Parcelable.Creator<T> creator = readParcelableCreator(loader);
+ if (creator == null) {
+ return null;
+ }
+ if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+ return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
+ }
+ return creator.createFromParcel(this);
+ }
+
+ /** @hide */
+ public final <T extends Parcelable> T readCreator(Parcelable.Creator<T> creator,
+ ClassLoader loader) {
+ if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
+ return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
+ }
+ return creator.createFromParcel(this);
+ }
+
+ /** @hide */
+ public final <T extends Parcelable> Parcelable.Creator<T> readParcelableCreator(
+ ClassLoader loader) {
String name = readString();
if (name == null) {
return null;
@@ -2087,6 +2115,10 @@ public final class Parcel {
+ "Parcelable.Creator object called "
+ " CREATOR on class " + name);
}
+ catch (NullPointerException e) {
+ throw new BadParcelableException("Parcelable protocol requires "
+ + "the CREATOR object to be static on class " + name);
+ }
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "Parcelable.Creator object called "
@@ -2097,10 +2129,7 @@ public final class Parcel {
}
}
- if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
- return ((Parcelable.ClassLoaderCreator<T>)creator).createFromParcel(this, loader);
- }
- return creator.createFromParcel(this);
+ return creator;
}
/**
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index ec660ee..3de362c 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -108,13 +108,6 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
public static ParcelFileDescriptor open(File file, int mode)
throws FileNotFoundException {
String path = file.getPath();
- SecurityManager security = System.getSecurityManager();
- if (security != null) {
- security.checkRead(path);
- if ((mode&MODE_WRITE_ONLY) != 0) {
- security.checkWrite(path);
- }
- }
if ((mode&MODE_READ_WRITE) == 0) {
throw new IllegalArgumentException(
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 05099fb..facab4c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -806,7 +806,15 @@ public class Process {
*/
public static final native void setProcessGroup(int pid, int group)
throws IllegalArgumentException, SecurityException;
-
+
+ /**
+ * Return the scheduling group of requested process.
+ *
+ * @hide
+ */
+ public static final native int getProcessGroup(int pid)
+ throws IllegalArgumentException, SecurityException;
+
/**
* Set the priority of the calling thread, based on Linux priorities. See
* {@link #setThreadPriority(int, int)} for more information.
diff --git a/core/java/android/os/SchedulingPolicyService.java b/core/java/android/os/SchedulingPolicyService.java
deleted file mode 100644
index a3fede6..0000000
--- a/core/java/android/os/SchedulingPolicyService.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Binder;
-import android.os.Process;
-import android.util.Log;
-
-/**
- * The implementation of the scheduling policy service interface.
- *
- * @hide
- */
-public class SchedulingPolicyService extends ISchedulingPolicyService.Stub {
-
- private static final String TAG = "SchedulingPolicyService";
-
- // Minimum and maximum values allowed for requestPriority parameter prio
- private static final int PRIORITY_MIN = 1;
- private static final int PRIORITY_MAX = 3;
-
- public SchedulingPolicyService() {
- }
-
- public int requestPriority(int pid, int tid, int prio) {
- //Log.i(TAG, "requestPriority(pid=" + pid + ", tid=" + tid + ", prio=" + prio + ")");
-
- // Verify that caller is mediaserver, priority is in range, and that the
- // callback thread specified by app belongs to the app that called mediaserver.
- // Once we've verified that the caller is mediaserver, we can trust the pid but
- // we can't trust the tid. No need to explicitly check for pid == 0 || tid == 0,
- // since if not the case then the getThreadGroupLeader() test will also fail.
- if (Binder.getCallingUid() != Process.MEDIA_UID || prio < PRIORITY_MIN ||
- prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) {
- return PackageManager.PERMISSION_DENIED;
- }
- try {
- // make good use of our CAP_SYS_NICE capability
- Process.setThreadGroup(tid, Binder.getCallingPid() == pid ?
- Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_AUDIO_APP);
- // must be in this order or it fails the schedulability constraint
- Process.setThreadScheduler(tid, Process.SCHED_FIFO, prio);
- } catch (RuntimeException e) {
- return PackageManager.PERMISSION_DENIED;
- }
- return PackageManager.PERMISSION_GRANTED;
- }
-
-}
diff --git a/core/java/android/os/StatFs.java b/core/java/android/os/StatFs.java
index ca7fdba..60ec0d7 100644
--- a/core/java/android/os/StatFs.java
+++ b/core/java/android/os/StatFs.java
@@ -65,6 +65,14 @@ public class StatFs {
}
/**
+ * The size, in bytes, of a block on the file system. This corresponds to
+ * the Unix {@code statfs.f_bsize} field.
+ */
+ public long getBlockSizeLong() {
+ return mStat.f_bsize;
+ }
+
+ /**
* The total number of blocks on the file system. This corresponds to the
* Unix {@code statfs.f_blocks} field.
*/
@@ -73,6 +81,14 @@ public class StatFs {
}
/**
+ * The size, in bytes, of a block on the file system. This corresponds to
+ * the Unix {@code statfs.f_bsize} field.
+ */
+ public long getBlockCountLong() {
+ return mStat.f_blocks;
+ }
+
+ /**
* The total number of blocks that are free on the file system, including
* reserved blocks (that are not available to normal applications). This
* corresponds to the Unix {@code statfs.f_bfree} field. Most applications
@@ -83,10 +99,44 @@ public class StatFs {
}
/**
+ * The total number of blocks that are free on the file system, including
+ * reserved blocks (that are not available to normal applications). This
+ * corresponds to the Unix {@code statfs.f_bfree} field. Most applications
+ * will want to use {@link #getAvailableBlocks()} instead.
+ */
+ public long getFreeBlocksLong() {
+ return mStat.f_bfree;
+ }
+
+ /**
+ * The number of bytes that are free on the file system, including
+ * reserved blocks (that are not available to normal applications).
+ */
+ public long getFreeBytes() {
+ return mStat.f_bfree * mStat.f_bsize;
+ }
+
+ /**
* The number of blocks that are free on the file system and available to
* applications. This corresponds to the Unix {@code statfs.f_bavail} field.
*/
public int getAvailableBlocks() {
return (int) mStat.f_bavail;
}
+
+ /**
+ * The number of blocks that are free on the file system and available to
+ * applications. This corresponds to the Unix {@code statfs.f_bavail} field.
+ */
+ public long getAvailableBlocksLong() {
+ return mStat.f_bavail;
+ }
+
+ /**
+ * The number of bytes that are free on the file system and available to
+ * applications.
+ */
+ public long getAvailableBytes() {
+ return mStat.f_bavail * mStat.f_bsize;
+ }
}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 7c5a47e..e66fb28 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,6 +16,8 @@
package android.os;
+import android.app.ActivityThread;
+import android.content.Context;
import android.util.Log;
/**
@@ -26,10 +28,18 @@ import android.util.Log;
public class SystemVibrator extends Vibrator {
private static final String TAG = "Vibrator";
+ private final String mPackageName;
private final IVibratorService mService;
private final Binder mToken = new Binder();
public SystemVibrator() {
+ mPackageName = ActivityThread.currentPackageName();
+ mService = IVibratorService.Stub.asInterface(
+ ServiceManager.getService("vibrator"));
+ }
+
+ public SystemVibrator(Context context) {
+ mPackageName = context.getBasePackageName();
mService = IVibratorService.Stub.asInterface(
ServiceManager.getService("vibrator"));
}
@@ -49,19 +59,35 @@ public class SystemVibrator extends Vibrator {
@Override
public void vibrate(long milliseconds) {
+ vibrate(Process.myUid(), mPackageName, milliseconds);
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ vibrate(Process.myUid(), mPackageName, pattern, repeat);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void vibrate(int owningUid, String owningPackage, long milliseconds) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
}
try {
- mService.vibrate(milliseconds, mToken);
+ mService.vibrate(owningUid, owningPackage, milliseconds, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
}
+ /**
+ * @hide
+ */
@Override
- public void vibrate(long[] pattern, int repeat) {
+ public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
if (mService == null) {
Log.w(TAG, "Failed to vibrate; no vibrator service.");
return;
@@ -71,7 +97,7 @@ public class SystemVibrator extends Vibrator {
// anyway
if (repeat < pattern.length) {
try {
- mService.vibratePattern(pattern, repeat, mToken);
+ mService.vibratePattern(owningUid, owningPackage, pattern, repeat, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 0ca9183..27ed6b6 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -31,7 +31,7 @@ import android.util.Log;
public final class Trace {
private static final String TAG = "Trace";
- // These tags must be kept in sync with frameworks/native/include/utils/Trace.h.
+ // These tags must be kept in sync with system/core/include/cutils/trace.h.
public static final long TRACE_TAG_NEVER = 0;
public static final long TRACE_TAG_ALWAYS = 1L << 0;
public static final long TRACE_TAG_GRAPHICS = 1L << 1;
@@ -44,12 +44,13 @@ public final class Trace {
public static final long TRACE_TAG_AUDIO = 1L << 8;
public static final long TRACE_TAG_VIDEO = 1L << 9;
public static final long TRACE_TAG_CAMERA = 1L << 10;
+ public static final long TRACE_TAG_HAL = 1L << 11;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
public static final int TRACE_FLAGS_START_BIT = 1;
public static final String[] TRACE_TAGS = {
"Graphics", "Input", "View", "WebView", "Window Manager",
- "Activity Manager", "Sync Manager", "Audio", "Video", "Camera",
+ "Activity Manager", "Sync Manager", "Audio", "Video", "Camera", "HAL",
};
public static final String PROPERTY_TRACE_TAG_ENABLEFLAGS = "debug.atrace.tags.enableflags";
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index cc96152..d205253 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -16,6 +16,8 @@
package android.os;
+import java.io.PrintWriter;
+
/**
* Representation of a user on the device.
*/
@@ -152,6 +154,50 @@ public final class UserHandle implements Parcelable {
}
/**
+ * Generate a text representation of the uid, breaking out its individual
+ * components -- user, app, isolated, etc.
+ * @hide
+ */
+ public static void formatUid(StringBuilder sb, int uid) {
+ if (uid < Process.FIRST_APPLICATION_UID) {
+ sb.append(uid);
+ } else {
+ sb.append('u');
+ sb.append(getUserId(uid));
+ final int appId = getAppId(uid);
+ if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
+ sb.append('i');
+ sb.append(appId - Process.FIRST_ISOLATED_UID);
+ } else {
+ sb.append('a');
+ sb.append(appId);
+ }
+ }
+ }
+
+ /**
+ * Generate a text representation of the uid, breaking out its individual
+ * components -- user, app, isolated, etc.
+ * @hide
+ */
+ public static void formatUid(PrintWriter pw, int uid) {
+ if (uid < Process.FIRST_APPLICATION_UID) {
+ pw.print(uid);
+ } else {
+ pw.print('u');
+ pw.print(getUserId(uid));
+ final int appId = getAppId(uid);
+ if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) {
+ pw.print('i');
+ pw.print(appId - Process.FIRST_ISOLATED_UID);
+ } else {
+ pw.print('a');
+ pw.print(appId);
+ }
+ }
+ }
+
+ /**
* Returns the user id of the current process
* @return user id of the current process
* @hide
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d73f99a..51e3e7c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -15,15 +15,15 @@
*/
package android.os;
-import com.android.internal.R;
-
import android.app.ActivityManagerNative;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.graphics.Bitmap;
import android.content.res.Resources;
+import android.graphics.Bitmap;
import android.util.Log;
+import com.android.internal.R;
+
import java.util.List;
/**
@@ -35,6 +35,50 @@ public class UserManager {
private final IUserManager mService;
private final Context mContext;
+ /**
+ * @hide
+ * Key for user restrictions. Specifies if a user is allowed to add or remove accounts.
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String ALLOW_MODIFY_ACCOUNTS = "modify_accounts";
+
+ /**
+ * @hide
+ * Key for user restrictions. Specifies if a user is allowed to change Wi-Fi access points.
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String ALLOW_CONFIG_WIFI = "config_wifi";
+
+ /**
+ * @hide
+ * Key for user restrictions. Specifies if a user is allowed to install applications.
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String ALLOW_INSTALL_APPS = "install_apps";
+
+ /**
+ * @hide
+ * Key for user restrictions. Specifies if a user is allowed to uninstall applications.
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String ALLOW_UNINSTALL_APPS = "uninstall_apps";
+
+ /** @hide *
+ * Key for user restrictions. Specifies if a user is allowed to toggle location sharing.
+ * Type: Boolean
+ * @see #setUserRestrictions(Bundle)
+ * @see #getUserRestrictions()
+ */
+ public static final String ALLOW_CONFIG_LOCATION_ACCESS = "config_location_access";
+
/** @hide */
public UserManager(Context context, IUserManager service) {
mService = service;
@@ -50,11 +94,11 @@ public class UserManager {
return getMaxSupportedUsers() > 1;
}
- /**
+ /**
* Returns the user handle for the user that this application is running for.
* @return the user handle of the user making this call.
* @hide
- * */
+ */
public int getUserHandle() {
return UserHandle.myUserId();
}
@@ -132,6 +176,42 @@ public class UserManager {
}
}
+ /** @hide */
+ public Bundle getUserRestrictions() {
+ return getUserRestrictions(Process.myUserHandle());
+ }
+
+ /** @hide */
+ public Bundle getUserRestrictions(UserHandle userHandle) {
+ try {
+ return mService.getUserRestrictions(userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get user restrictions", re);
+ return Bundle.EMPTY;
+ }
+ }
+
+ /** @hide */
+ public void setUserRestrictions(Bundle restrictions) {
+ setUserRestrictions(restrictions, Process.myUserHandle());
+ }
+
+ /** @hide */
+ public void setUserRestrictions(Bundle restrictions, UserHandle userHandle) {
+ try {
+ mService.setUserRestrictions(restrictions, userHandle.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set user restrictions", re);
+ }
+ }
+
+ /** @hide */
+ public void setUserRestriction(String key, boolean value, UserHandle userHandle) {
+ Bundle bundle = getUserRestrictions(userHandle);
+ bundle.putBoolean(key, value);
+ setUserRestrictions(bundle, userHandle);
+ }
+
/**
* Return the serial number for a user. This is a device-unique
* number assigned to that user; if the user is deleted and then a new
@@ -368,4 +448,12 @@ public class UserManager {
}
return -1;
}
+
+ /**
+ * Returns whether the current user is allow to toggle location sharing settings.
+ * @hide
+ */
+ public boolean isLocationSharingToggleAllowed() {
+ return getUserRestrictions().getBoolean(ALLOW_CONFIG_LOCATION_ACCESS);
+ }
}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index b67be4b..6650fca 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -73,6 +73,20 @@ public abstract class Vibrator {
public abstract void vibrate(long[] pattern, int repeat);
/**
+ * @hide
+ * Like {@link #vibrate(long)}, but allowing the caller to specify that
+ * the vibration is owned by someone else.
+ */
+ public abstract void vibrate(int owningUid, String owningPackage, long milliseconds);
+
+ /**
+ * @hide
+ * Like {@link #vibrate(long[], int)}, but allowing the caller to specify that
+ * the vibration is owned by someone else.
+ */
+ public abstract void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat);
+
+ /**
* Turn the vibrator off.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#VIBRATE}.
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index ba77df7..b79bdee 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,6 +1,6 @@
package android.os;
-import com.android.internal.util.ArrayUtils;
+import android.util.Log;
import java.util.Arrays;
@@ -10,8 +10,12 @@ import java.util.Arrays;
* defined; this is an opaque container.
*/
public class WorkSource implements Parcelable {
+ static final String TAG = "WorkSource";
+ static final boolean DEBUG = false;
+
int mNum;
int[] mUids;
+ String[] mNames;
/**
* Internal statics to avoid object allocations in some operations.
@@ -47,8 +51,10 @@ public class WorkSource implements Parcelable {
mNum = orig.mNum;
if (orig.mUids != null) {
mUids = orig.mUids.clone();
+ mNames = orig.mNames != null ? orig.mNames.clone() : null;
} else {
mUids = null;
+ mNames = null;
}
}
@@ -56,11 +62,23 @@ public class WorkSource implements Parcelable {
public WorkSource(int uid) {
mNum = 1;
mUids = new int[] { uid, 0 };
+ mNames = null;
+ }
+
+ /** @hide */
+ public WorkSource(int uid, String name) {
+ if (name == null) {
+ throw new NullPointerException("Name can't be null");
+ }
+ mNum = 1;
+ mUids = new int[] { uid, 0 };
+ mNames = new String[] { name, null };
}
WorkSource(Parcel in) {
mNum = in.readInt();
mUids = in.createIntArray();
+ mNames = in.createStringArray();
}
/** @hide */
@@ -73,6 +91,11 @@ public class WorkSource implements Parcelable {
return mUids[index];
}
+ /** @hide */
+ public String getName(int index) {
+ return mNames != null ? mNames[index] : null;
+ }
+
/**
* Clear this WorkSource to be empty.
*/
@@ -91,6 +114,11 @@ public class WorkSource implements Parcelable {
for (int i = 0; i < mNum; i++) {
result = ((result << 4) | (result >>> 28)) ^ mUids[i];
}
+ if (mNames != null) {
+ for (int i = 0; i < mNum; i++) {
+ result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
+ }
+ }
return result;
}
@@ -106,10 +134,15 @@ public class WorkSource implements Parcelable {
}
final int[] uids1 = mUids;
final int[] uids2 = other.mUids;
+ final String[] names1 = mNames;
+ final String[] names2 = other.mNames;
for (int i=0; i<N; i++) {
if (uids1[i] != uids2[i]) {
return true;
}
+ if (names1 != null && names2 != null && !names1[i].equals(names2[i])) {
+ return true;
+ }
}
return false;
}
@@ -131,8 +164,18 @@ public class WorkSource implements Parcelable {
} else {
mUids = other.mUids.clone();
}
+ if (other.mNames != null) {
+ if (mNames != null && mNames.length >= mNum) {
+ System.arraycopy(other.mNames, 0, mNames, 0, mNum);
+ } else {
+ mNames = other.mNames.clone();
+ }
+ } else {
+ mNames = null;
+ }
} else {
mUids = null;
+ mNames = null;
}
}
@@ -141,6 +184,22 @@ public class WorkSource implements Parcelable {
mNum = 1;
if (mUids == null) mUids = new int[2];
mUids[0] = uid;
+ mNames = null;
+ }
+
+ /** @hide */
+ public void set(int uid, String name) {
+ if (name == null) {
+ throw new NullPointerException("Name can't be null");
+ }
+ mNum = 1;
+ if (mUids == null) {
+ mUids = new int[2];
+ mNames = new String[2];
+ }
+ mUids[0] = uid;
+ mNames[0] = name;
+ mNames = null;
}
/** @hide */
@@ -182,10 +241,49 @@ public class WorkSource implements Parcelable {
/** @hide */
public boolean add(int uid) {
- synchronized (sTmpWorkSource) {
- sTmpWorkSource.mUids[0] = uid;
- return updateLocked(sTmpWorkSource, false, false);
+ if (mNum <= 0) {
+ mNames = null;
+ insert(0, uid);
+ return true;
+ }
+ if (mNames != null) {
+ throw new IllegalArgumentException("Adding without name to named " + this);
+ }
+ int i = Arrays.binarySearch(mUids, 0, mNum, uid);
+ if (DEBUG) Log.d(TAG, "Adding uid " + uid + " to " + this + ": binsearch res = " + i);
+ if (i >= 0) {
+ return false;
}
+ insert(-i-1, uid);
+ return true;
+ }
+
+ /** @hide */
+ public boolean add(int uid, String name) {
+ if (mNum <= 0) {
+ insert(0, uid, name);
+ return true;
+ }
+ if (mNames == null) {
+ throw new IllegalArgumentException("Adding name to unnamed " + this);
+ }
+ int i;
+ for (i=0; i<mNum; i++) {
+ if (mUids[i] > uid) {
+ break;
+ }
+ if (mUids[i] == uid) {
+ int diff = mNames[i].compareTo(name);
+ if (diff > 0) {
+ break;
+ }
+ if (diff == 0) {
+ return false;
+ }
+ }
+ }
+ insert(i, uid, name);
+ return true;
}
/** @hide */
@@ -199,19 +297,102 @@ public class WorkSource implements Parcelable {
}
public boolean remove(WorkSource other) {
+ if (mNum <= 0 || other.mNum <= 0) {
+ return false;
+ }
+ if (mNames == null && other.mNames == null) {
+ return removeUids(other);
+ } else {
+ if (mNames == null) {
+ throw new IllegalArgumentException("Other " + other + " has names, but target "
+ + this + " does not");
+ }
+ if (other.mNames == null) {
+ throw new IllegalArgumentException("Target " + this + " has names, but other "
+ + other + " does not");
+ }
+ return removeUidsAndNames(other);
+ }
+ }
+
+ /** @hide */
+ public WorkSource stripNames() {
+ if (mNum <= 0) {
+ return new WorkSource();
+ }
+ WorkSource result = new WorkSource();
+ int lastUid = -1;
+ for (int i=0; i<mNum; i++) {
+ int uid = mUids[i];
+ if (i == 0 || lastUid != uid) {
+ result.add(uid);
+ }
+ }
+ return result;
+ }
+
+ private boolean removeUids(WorkSource other) {
int N1 = mNum;
final int[] uids1 = mUids;
final int N2 = other.mNum;
final int[] uids2 = other.mUids;
boolean changed = false;
- int i1 = 0;
- for (int i2=0; i2<N2 && i1<N1; i2++) {
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
+ while (i1 < N1 && i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+ + " of " + N2);
if (uids2[i2] == uids1[i1]) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+ + ": remove " + uids1[i1]);
N1--;
+ changed = true;
if (i1 < N1) System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
+ i2++;
+ } else if (uids2[i2] > uids1[i1]) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
+ i1++;
+ } else {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
+ i2++;
}
- while (i1 < N1 && uids2[i2] > uids1[i1]) {
+ }
+
+ mNum = N1;
+
+ return changed;
+ }
+
+ private boolean removeUidsAndNames(WorkSource other) {
+ int N1 = mNum;
+ final int[] uids1 = mUids;
+ final String[] names1 = mNames;
+ final int N2 = other.mNum;
+ final int[] uids2 = other.mUids;
+ final String[] names2 = other.mNames;
+ boolean changed = false;
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Remove " + other + " from " + this);
+ while (i1 < N1 && i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+ + " of " + N2 + ": " + uids1[i1] + " " + names1[i1]);
+ if (uids2[i2] == uids1[i1] && names2[i2].equals(names1[i1])) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+ + ": remove " + uids1[i1] + " " + names1[i1]);
+ N1--;
+ changed = true;
+ if (i1 < N1) {
+ System.arraycopy(uids1, i1+1, uids1, i1, N1-i1);
+ System.arraycopy(names1, i1+1, names1, i1, N1-i1);
+ }
+ i2++;
+ } else if (uids2[i2] > uids1[i1]
+ || (uids2[i2] == uids1[i1] && names2[i2].compareTo(names1[i1]) > 0)) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i1");
i1++;
+ } else {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip i2");
+ i2++;
}
}
@@ -221,20 +402,50 @@ public class WorkSource implements Parcelable {
}
private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) {
+ if (mNames == null && other.mNames == null) {
+ return updateUidsLocked(other, set, returnNewbs);
+ } else {
+ if (mNum > 0 && mNames == null) {
+ throw new IllegalArgumentException("Other " + other + " has names, but target "
+ + this + " does not");
+ }
+ if (other.mNum > 0 && other.mNames == null) {
+ throw new IllegalArgumentException("Target " + this + " has names, but other "
+ + other + " does not");
+ }
+ return updateUidsAndNamesLocked(other, set, returnNewbs);
+ }
+ }
+
+ private static WorkSource addWork(WorkSource cur, int newUid) {
+ if (cur == null) {
+ return new WorkSource(newUid);
+ }
+ cur.insert(cur.mNum, newUid);
+ return cur;
+ }
+
+ private boolean updateUidsLocked(WorkSource other, boolean set, boolean returnNewbs) {
int N1 = mNum;
int[] uids1 = mUids;
final int N2 = other.mNum;
final int[] uids2 = other.mUids;
boolean changed = false;
- int i1 = 0;
- for (int i2=0; i2<N2; i2++) {
- if (i1 >= N1 || uids2[i2] < uids1[i1]) {
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
+ + " returnNewbs=" + returnNewbs);
+ while (i1 < N1 || i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + N1 + ", other @ " + i2
+ + " of " + N2);
+ if (i1 >= N1 || (i2 < N2 && uids2[i2] < uids1[i1])) {
// Need to insert a new uid.
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1
+ + ": insert " + uids2[i2]);
changed = true;
if (uids1 == null) {
uids1 = new int[4];
uids1[0] = uids2[i2];
- } else if (i1 >= uids1.length) {
+ } else if (N1 >= uids1.length) {
int[] newuids = new int[(uids1.length*3)/2];
if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1);
if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1);
@@ -245,39 +456,37 @@ public class WorkSource implements Parcelable {
uids1[i1] = uids2[i2];
}
if (returnNewbs) {
- if (sNewbWork == null) {
- sNewbWork = new WorkSource(uids2[i2]);
- } else {
- sNewbWork.addLocked(uids2[i2]);
- }
+ sNewbWork = addWork(sNewbWork, uids2[i2]);
}
N1++;
i1++;
+ i2++;
} else {
if (!set) {
// Skip uids that already exist or are not in 'other'.
- do {
- i1++;
- } while (i1 < N1 && uids2[i2] >= uids1[i1]);
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
+ if (i2 < N2 && uids2[i2] == uids1[i1]) {
+ i2++;
+ }
+ i1++;
} else {
// Remove any uids that don't exist in 'other'.
int start = i1;
- while (i1 < N1 && uids2[i2] > uids1[i1]) {
- if (sGoneWork == null) {
- sGoneWork = new WorkSource(uids1[i1]);
- } else {
- sGoneWork.addLocked(uids1[i1]);
- }
+ while (i1 < N1 && (i2 >= N2 || uids2[i2] > uids1[i1])) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + uids1[i1]);
+ sGoneWork = addWork(sGoneWork, uids1[i1]);
i1++;
}
if (start < i1) {
- System.arraycopy(uids1, i1, uids1, start, i1-start);
+ System.arraycopy(uids1, i1, uids1, start, N1-i1);
N1 -= i1-start;
i1 = start;
}
// If there is a matching uid, skip it.
- if (i1 < N1 && uids2[i1] == uids1[i1]) {
+ if (i1 < N1 && i2 < N2 && uids2[i2] == uids1[i1]) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + N1 + ": skip");
i1++;
+ i2++;
}
}
}
@@ -289,21 +498,145 @@ public class WorkSource implements Parcelable {
return changed;
}
- private void addLocked(int uid) {
+ /**
+ * Returns 0 if equal, negative if 'this' is before 'other', positive if 'this' is after 'other'.
+ */
+ private int compare(WorkSource other, int i1, int i2) {
+ final int diff = mUids[i1] - other.mUids[i2];
+ if (diff != 0) {
+ return diff;
+ }
+ return mNames[i1].compareTo(other.mNames[i2]);
+ }
+
+ private static WorkSource addWork(WorkSource cur, int newUid, String newName) {
+ if (cur == null) {
+ return new WorkSource(newUid, newName);
+ }
+ cur.insert(cur.mNum, newUid, newName);
+ return cur;
+ }
+
+ private boolean updateUidsAndNamesLocked(WorkSource other, boolean set, boolean returnNewbs) {
+ final int N2 = other.mNum;
+ final int[] uids2 = other.mUids;
+ String[] names2 = other.mNames;
+ boolean changed = false;
+ int i1 = 0, i2 = 0;
+ if (DEBUG) Log.d(TAG, "Update " + this + " with " + other + " set=" + set
+ + " returnNewbs=" + returnNewbs);
+ while (i1 < mNum || i2 < N2) {
+ if (DEBUG) Log.d(TAG, "Step: target @ " + i1 + " of " + mNum + ", other @ " + i2
+ + " of " + N2);
+ int diff = -1;
+ if (i1 >= mNum || (i2 < N2 && (diff=compare(other, i1, i2)) > 0)) {
+ // Need to insert a new uid.
+ changed = true;
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum
+ + ": insert " + uids2[i2] + " " + names2[i2]);
+ insert(i1, uids2[i2], names2[i2]);
+ if (returnNewbs) {
+ sNewbWork = addWork(sNewbWork, uids2[i2], names2[i2]);
+ }
+ i1++;
+ i2++;
+ } else {
+ if (!set) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
+ if (i2 < N2 && diff == 0) {
+ i2++;
+ }
+ i1++;
+ } else {
+ // Remove any uids that don't exist in 'other'.
+ int start = i1;
+ while (diff < 0) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + ": remove " + mUids[i1]
+ + " " + mNames[i1]);
+ sGoneWork = addWork(sGoneWork, mUids[i1], mNames[i1]);
+ i1++;
+ if (i1 >= mNum) {
+ break;
+ }
+ diff = i2 < N2 ? compare(other, i1, i2) : -1;
+ }
+ if (start < i1) {
+ System.arraycopy(mUids, i1, mUids, start, mNum-i1);
+ System.arraycopy(mNames, i1, mNames, start, mNum-i1);
+ mNum -= i1-start;
+ i1 = start;
+ }
+ // If there is a matching uid, skip it.
+ if (i1 < mNum && diff == 0) {
+ if (DEBUG) Log.d(TAG, "i1=" + i1 + " i2=" + i2 + " N1=" + mNum + ": skip");
+ i1++;
+ i2++;
+ }
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ private void insert(int index, int uid) {
+ if (DEBUG) Log.d(TAG, "Insert in " + this + " @ " + index + " uid " + uid);
if (mUids == null) {
mUids = new int[4];
mUids[0] = uid;
mNum = 1;
- return;
- }
- if (mNum >= mUids.length) {
+ } else if (mNum >= mUids.length) {
int[] newuids = new int[(mNum*3)/2];
- System.arraycopy(mUids, 0, newuids, 0, mNum);
+ if (index > 0) {
+ System.arraycopy(mUids, 0, newuids, 0, index);
+ }
+ if (index < mNum) {
+ System.arraycopy(mUids, index, newuids, index+1, mNum-index);
+ }
mUids = newuids;
+ mUids[index] = uid;
+ mNum++;
+ } else {
+ if (index < mNum) {
+ System.arraycopy(mUids, index, mUids, index+1, mNum-index);
+ }
+ mUids[index] = uid;
+ mNum++;
}
+ }
- mUids[mNum] = uid;
- mNum++;
+ private void insert(int index, int uid, String name) {
+ if (mUids == null) {
+ mUids = new int[4];
+ mUids[0] = uid;
+ mNames = new String[4];
+ mNames[0] = name;
+ mNum = 1;
+ } else if (mNum >= mUids.length) {
+ int[] newuids = new int[(mNum*3)/2];
+ String[] newnames = new String[(mNum*3)/2];
+ if (index > 0) {
+ System.arraycopy(mUids, 0, newuids, 0, index);
+ System.arraycopy(mNames, 0, newnames, 0, index);
+ }
+ if (index < mNum) {
+ System.arraycopy(mUids, index, newuids, index+1, mNum-index);
+ System.arraycopy(mNames, index, newnames, index+1, mNum-index);
+ }
+ mUids = newuids;
+ mNames = newnames;
+ mUids[index] = uid;
+ mNames[index] = name;
+ mNum++;
+ } else {
+ if (index < mNum) {
+ System.arraycopy(mUids, index, mUids, index+1, mNum-index);
+ System.arraycopy(mNames, index, mNames, index+1, mNum-index);
+ }
+ mUids[index] = uid;
+ mNames[index] = name;
+ mNum++;
+ }
}
@Override
@@ -315,19 +648,24 @@ public class WorkSource implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mNum);
dest.writeIntArray(mUids);
+ dest.writeStringArray(mNames);
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
- result.append("{WorkSource: uids=[");
+ result.append("WorkSource{");
for (int i = 0; i < mNum; i++) {
if (i != 0) {
result.append(", ");
}
result.append(mUids[i]);
+ if (mNames != null) {
+ result.append(" ");
+ result.append(mNames[i]);
+ }
}
- result.append("]}");
+ result.append("}");
return result.toString();
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 862a95c..f5e728d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,7 +16,9 @@
package android.os.storage;
-import android.app.NotificationManager;
+import static android.net.TrafficStats.MB_IN_BYTES;
+
+import android.content.ContentResolver;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
@@ -25,6 +27,7 @@ import android.os.Message;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
@@ -54,20 +57,20 @@ import java.util.concurrent.atomic.AtomicInteger;
* {@link android.content.Context#getSystemService(java.lang.String)} with an
* argument of {@link android.content.Context#STORAGE_SERVICE}.
*/
-
-public class StorageManager
-{
+public class StorageManager {
private static final String TAG = "StorageManager";
+ private final ContentResolver mResolver;
+
/*
* Our internal MountService binder reference
*/
- final private IMountService mMountService;
+ private final IMountService mMountService;
/*
* The looper target for callbacks
*/
- Looper mTgtLooper;
+ private final Looper mTgtLooper;
/*
* Target listener for binder callbacks
@@ -308,16 +311,16 @@ public class StorageManager
*
* @hide
*/
- public StorageManager(Looper tgtLooper) throws RemoteException {
+ public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
+ mResolver = resolver;
+ mTgtLooper = tgtLooper;
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
if (mMountService == null) {
Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
return;
}
- mTgtLooper = tgtLooper;
}
-
/**
* Registers a {@link android.os.storage.StorageEventListener StorageEventListener}.
*
@@ -610,4 +613,36 @@ public class StorageManager
Log.w(TAG, "No primary storage defined");
return null;
}
+
+ private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
+ private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
+ private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
+
+ /**
+ * Return the number of available bytes at which the given path is
+ * considered running low on storage.
+ *
+ * @hide
+ */
+ public long getStorageLowBytes(File path) {
+ final long lowPercent = Settings.Global.getInt(mResolver,
+ Settings.Global.SYS_STORAGE_THRESHOLD_PERCENTAGE, DEFAULT_THRESHOLD_PERCENTAGE);
+ final long lowBytes = (path.getTotalSpace() * lowPercent) / 100;
+
+ final long maxLowBytes = Settings.Global.getLong(mResolver,
+ Settings.Global.SYS_STORAGE_THRESHOLD_MAX_BYTES, DEFAULT_THRESHOLD_MAX_BYTES);
+
+ return Math.min(lowBytes, maxLowBytes);
+ }
+
+ /**
+ * Return the number of available bytes at which the given path is
+ * considered full.
+ *
+ * @hide
+ */
+ public long getStorageFullBytes(File path) {
+ return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
+ DEFAULT_FULL_THRESHOLD_BYTES);
+ }
}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
index 09ff7be..028317f 100644
--- a/core/java/android/preference/PreferenceActivity.java
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -703,7 +703,13 @@ public abstract class PreferenceActivity extends ListActivity implements
* show for the initial UI.
*/
public Header onGetInitialHeader() {
- return mHeaders.get(0);
+ for (int i=0; i<mHeaders.size(); i++) {
+ Header h = mHeaders.get(i);
+ if (h.fragment != null) {
+ return h;
+ }
+ }
+ throw new IllegalStateException("Must have at least one header with a fragment");
}
/**
@@ -1167,6 +1173,9 @@ public abstract class PreferenceActivity extends ListActivity implements
getFragmentManager().popBackStack(BACK_STACK_PREFS,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
+ if (header.fragment == null) {
+ throw new IllegalStateException("can't switch to header that has no fragment");
+ }
int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader);
switchToHeaderInner(header.fragment, header.fragmentArguments, direction);
setSelectedHeader(header);
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index af6e88e9..2dd27f8 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -302,9 +302,16 @@ public final class CalendarContract {
* Used to indicate that local, unsynced, changes are present.
* <P>Type: INTEGER (long)</P>
*/
+
public static final String DIRTY = "dirty";
/**
+ * Used in conjunction with {@link #DIRTY} to indicate what packages wrote local changes.
+ * <P>Type: TEXT</P>
+ */
+ public static final String MUTATORS = "mutators";
+
+ /**
* Whether the row has been deleted but not synced to the server. A
* deleted row should be ignored.
* <P>
@@ -525,6 +532,7 @@ public final class CalendarContract {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, MUTATORS);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC1);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, CAL_SYNC2);
@@ -542,6 +550,8 @@ public final class CalendarContract {
Calendars.CALENDAR_DISPLAY_NAME);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
Calendars.CALENDAR_COLOR);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
+ Calendars.CALENDAR_COLOR_KEY);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, CALENDAR_ACCESS_LEVEL);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
@@ -647,6 +657,7 @@ public final class CalendarContract {
* <li>{@link #CALENDAR_COLOR}</li>
* <li>{@link #_SYNC_ID}</li>
* <li>{@link #DIRTY}</li>
+ * <li>{@link #MUTATORS}</li>
* <li>{@link #OWNER_ACCOUNT}</li>
* <li>{@link #MAX_REMINDERS}</li>
* <li>{@link #ALLOWED_REMINDERS}</li>
@@ -714,6 +725,7 @@ public final class CalendarContract {
ACCOUNT_TYPE,
_SYNC_ID,
DIRTY,
+ MUTATORS,
OWNER_ACCOUNT,
MAX_REMINDERS,
ALLOWED_REMINDERS,
@@ -1368,6 +1380,8 @@ public final class CalendarContract {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, ALL_DAY);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, AVAILABILITY);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, EVENT_COLOR);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, EVENT_COLOR_KEY);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, HAS_ALARM);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
HAS_EXTENDED_PROPERTIES);
@@ -1393,6 +1407,7 @@ public final class CalendarContract {
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, IS_ORGANIZER);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DIRTY);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, MUTATORS);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, LAST_SYNCED);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, SYNC_DATA1);
@@ -1599,6 +1614,7 @@ public final class CalendarContract {
* The following Events columns are writable only by a sync adapter
* <ul>
* <li>{@link #DIRTY}</li>
+ * <li>{@link #MUTATORS}</li>
* <li>{@link #_SYNC_ID}</li>
* <li>{@link #SYNC_DATA1}</li>
* <li>{@link #SYNC_DATA2}</li>
@@ -1688,6 +1704,7 @@ public final class CalendarContract {
public static final String[] SYNC_WRITABLE_COLUMNS = new String[] {
_SYNC_ID,
DIRTY,
+ MUTATORS,
SYNC_DATA1,
SYNC_DATA2,
SYNC_DATA3,
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 31ad12b..9999760 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -407,6 +407,9 @@ public final class Downloads {
*/
public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc";
+ /** The column that is used to count retries */
+ public static final String COLUMN_FAILED_CONNECTIONS = "numfailed";
+
/**
* default value for {@link #COLUMN_LAST_UPDATESRC}.
* This value is used when this column's value is not relevant.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4dbc4b4..266d0d3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -50,7 +50,6 @@ import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
-import android.view.WindowOrientationListener;
import com.android.internal.widget.ILockSettings;
@@ -456,6 +455,18 @@ public final class Settings {
"android.settings.APPLICATION_DETAILS_SETTINGS";
/**
+ * @hide
+ * Activity Action: Show the "app ops" settings screen.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_APP_OPS_SETTINGS =
+ "android.settings.APP_OPS_SETTINGS";
+
+ /**
* Activity Action: Show settings for system update functionality.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -500,6 +511,9 @@ public final class Settings {
* extra to the Intent with one or more syncable content provider's authorities. Only account
* types which can sync with that content provider will be offered to the user.
* <p>
+ * Account types can also be filtered by adding an {@link #EXTRA_ACCOUNT_TYPES} extra to the
+ * Intent with one or more account types.
+ * <p>
* Input: Nothing.
* <p>
* Output: Nothing.
@@ -682,8 +696,9 @@ public final class Settings {
* Example: The {@link #ACTION_ADD_ACCOUNT} intent restricts the account types available based
* on the authority given.
*/
- public static final String EXTRA_AUTHORITIES =
- "authorities";
+ public static final String EXTRA_AUTHORITIES = "authorities";
+
+ public static final String EXTRA_ACCOUNT_TYPES = "account_types";
public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
@@ -774,7 +789,7 @@ public final class Settings {
arg.putString(Settings.NameValueTable.VALUE, value);
arg.putInt(CALL_METHOD_USER_KEY, userHandle);
IContentProvider cp = lazyGetProvider(cr);
- cp.call(mCallSetCommand, name, arg);
+ cp.call(cr.getPackageName(), mCallSetCommand, name, arg);
} catch (RemoteException e) {
Log.w(TAG, "Can't set key " + name + " in " + mUri, e);
return false;
@@ -821,7 +836,7 @@ public final class Settings {
args = new Bundle();
args.putInt(CALL_METHOD_USER_KEY, userHandle);
}
- Bundle b = cp.call(mCallGetCommand, name, args);
+ Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
if (b != null) {
String value = b.getPairValue();
// Don't update our cache for reads of other users' data
@@ -846,7 +861,7 @@ public final class Settings {
Cursor c = null;
try {
- c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+ c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
new String[]{name}, null, null);
if (c == null) {
Log.w(TAG, "Can't get key " + name + " from " + mUri);
@@ -2610,6 +2625,7 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.ASSISTED_GPS_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.BLUETOOTH_ON);
+ MOVED_TO_GLOBAL.add(Settings.Global.BUGREPORT_IN_POWER_MENU);
MOVED_TO_GLOBAL.add(Settings.Global.CDMA_CELL_BROADCAST_SMS);
MOVED_TO_GLOBAL.add(Settings.Global.CDMA_ROAMING_MODE);
MOVED_TO_GLOBAL.add(Settings.Global.CDMA_SUBSCRIPTION_MODE);
@@ -2659,13 +2675,6 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.TETHER_DUN_APN);
MOVED_TO_GLOBAL.add(Settings.Global.TETHER_DUN_REQUIRED);
MOVED_TO_GLOBAL.add(Settings.Global.TETHER_SUPPORTED);
- MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_HELP_URI);
- MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_MAX_NTP_CACHE_AGE_SEC);
- MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_NOTIFICATION_TYPE);
- MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_POLLING_SEC);
- MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_RESET_DAY);
- MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_THRESHOLD_BYTES);
- MOVED_TO_GLOBAL.add(Settings.Global.THROTTLE_VALUE_KBITSPS);
MOVED_TO_GLOBAL.add(Settings.Global.USB_MASS_STORAGE_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.USE_GOOGLE_MAIL);
MOVED_TO_GLOBAL.add(Settings.Global.WEB_AUTOFILL_QUERY_URL);
@@ -3081,8 +3090,10 @@ public final class Settings {
/**
* When the user has enable the option to have a "bug report" command
* in the power menu.
+ * @deprecated Use {@link android.provider.Settings.Global#BUGREPORT_IN_POWER_MENU} instead
* @hide
*/
+ @Deprecated
public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
/**
@@ -4037,7 +4048,7 @@ public final class Settings {
* @hide
*/
public static final String[] SETTINGS_TO_BACKUP = {
- BUGREPORT_IN_POWER_MENU,
+ BUGREPORT_IN_POWER_MENU, // moved to global
ALLOW_MOCK_LOCATION,
PARENTAL_CONTROL_ENABLED,
PARENTAL_CONTROL_REDIRECT_URL,
@@ -4316,6 +4327,13 @@ public final class Settings {
public static final String STAY_ON_WHILE_PLUGGED_IN = "stay_on_while_plugged_in";
/**
+ * When the user has enable the option to have a "bug report" command
+ * in the power menu.
+ * @hide
+ */
+ public static final String BUGREPORT_IN_POWER_MENU = "bugreport_in_power_menu";
+
+ /**
* Whether ADB is enabled.
*/
public static final String ADB_ENABLED = "adb_enabled";
@@ -4685,50 +4703,6 @@ public final class Settings {
public static final String TETHER_DUN_APN = "tether_dun_apn";
/**
- * The bandwidth throttle polling freqency in seconds
- * @hide
- */
- public static final String THROTTLE_POLLING_SEC = "throttle_polling_sec";
-
- /**
- * The bandwidth throttle threshold (long)
- * @hide
- */
- public static final String THROTTLE_THRESHOLD_BYTES = "throttle_threshold_bytes";
-
- /**
- * The bandwidth throttle value (kbps)
- * @hide
- */
- public static final String THROTTLE_VALUE_KBITSPS = "throttle_value_kbitsps";
-
- /**
- * The bandwidth throttle reset calendar day (1-28)
- * @hide
- */
- public static final String THROTTLE_RESET_DAY = "throttle_reset_day";
-
- /**
- * The throttling notifications we should send
- * @hide
- */
- public static final String THROTTLE_NOTIFICATION_TYPE = "throttle_notification_type";
-
- /**
- * Help URI for data throttling policy
- * @hide
- */
- public static final String THROTTLE_HELP_URI = "throttle_help_uri";
-
- /**
- * The length of time in Sec that we allow our notion of NTP time
- * to be cached before we refresh it
- * @hide
- */
- public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC =
- "throttle_max_ntp_cache_age_sec";
-
- /**
* USB Mass Storage Enabled
*/
public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
@@ -5359,6 +5333,7 @@ public final class Settings {
* @hide
*/
public static final String[] SETTINGS_TO_BACKUP = {
+ BUGREPORT_IN_POWER_MENU,
STAY_ON_WHILE_PLUGGED_IN,
MODE_RINGER,
AUTO_TIME,
diff --git a/core/java/android/server/package.html b/core/java/android/server/package.html
deleted file mode 100644
index c9f96a6..0000000
--- a/core/java/android/server/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-{@hide}
-
-</body>
diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java
deleted file mode 100644
index 1a10644..0000000
--- a/core/java/android/server/search/SearchManagerService.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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.server.search;
-
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.util.IndentingPrintWriter;
-
-import android.app.ActivityManager;
-import android.app.ActivityManagerNative;
-import android.app.AppGlobals;
-import android.app.ISearchManager;
-import android.app.SearchManager;
-import android.app.SearchableInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.ContentObserver;
-import android.os.Binder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-
-/**
- * The search manager service handles the search UI, and maintains a registry of searchable
- * activities.
- */
-public class SearchManagerService extends ISearchManager.Stub {
-
- // general debugging support
- private static final String TAG = "SearchManagerService";
-
- // Context that the service is running in.
- private final Context mContext;
-
- // This field is initialized lazily in getSearchables(), and then never modified.
- private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>();
-
- /**
- * 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) {
- mContext = context;
- mContext.registerReceiver(new BootCompletedReceiver(),
- new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
- mContext.registerReceiver(new UserReceiver(),
- new IntentFilter(Intent.ACTION_USER_REMOVED));
- new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
- }
-
- private Searchables getSearchables(int userId) {
- long origId = Binder.clearCallingIdentity();
- try {
- boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
- .getUserInfo(userId) != null;
- if (!userExists) return null;
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- synchronized (mSearchables) {
- Searchables searchables = mSearchables.get(userId);
-
- if (searchables == null) {
- //Log.i(TAG, "Building list of searchable activities for userId=" + userId);
- searchables = new Searchables(mContext, userId);
- searchables.buildSearchableList();
- mSearchables.append(userId, searchables);
- }
- return searchables;
- }
- }
-
- private void onUserRemoved(int userId) {
- if (userId != UserHandle.USER_OWNER) {
- synchronized (mSearchables) {
- mSearchables.remove(userId);
- }
- }
- }
-
- /**
- * Creates the initial searchables list after boot.
- */
- private final class BootCompletedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- new Thread() {
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- mContext.unregisterReceiver(BootCompletedReceiver.this);
- getSearchables(0);
- }
- }.start();
- }
- }
-
- private final class UserReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_OWNER));
- }
- }
-
- /**
- * Refreshes the "searchables" list when packages are added/removed.
- */
- class MyPackageMonitor extends PackageMonitor {
-
- @Override
- public void onSomePackagesChanged() {
- updateSearchables();
- }
-
- @Override
- public void onPackageModified(String pkg) {
- updateSearchables();
- }
-
- private void updateSearchables() {
- final int changingUserId = getChangingUserId();
- synchronized (mSearchables) {
- // Update list of searchable activities
- for (int i = 0; i < mSearchables.size(); i++) {
- if (changingUserId == mSearchables.keyAt(i)) {
- getSearchables(mSearchables.keyAt(i)).buildSearchableList();
- break;
- }
- }
- }
- // Inform all listeners that the list of searchables has been updated.
- Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
- }
- }
-
- class GlobalSearchProviderObserver extends ContentObserver {
- private final ContentResolver mResolver;
-
- public GlobalSearchProviderObserver(ContentResolver resolver) {
- super(null);
- mResolver = resolver;
- mResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY),
- false /* notifyDescendants */,
- this);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- synchronized (mSearchables) {
- for (int i = 0; i < mSearchables.size(); i++) {
- getSearchables(mSearchables.keyAt(i)).buildSearchableList();
- }
- }
- Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- }
-
- }
-
- //
- // Searchable activities API
- //
-
- /**
- * Returns the SearchableInfo for a given activity.
- *
- * @param launchActivity The activity from which we're launching this search.
- * @return Returns a SearchableInfo record describing the parameters of the search,
- * or null if no searchable metadata was available.
- */
- public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
- if (launchActivity == null) {
- Log.e(TAG, "getSearchableInfo(), activity == null");
- return null;
- }
- return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity);
- }
-
- /**
- * Returns a list of the searchable activities that can be included in global search.
- */
- public List<SearchableInfo> getSearchablesInGlobalSearch() {
- return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
- }
-
- public List<ResolveInfo> getGlobalSearchActivities() {
- return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
- }
-
- /**
- * Gets the name of the global search activity.
- */
- public ComponentName getGlobalSearchActivity() {
- return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
- }
-
- /**
- * Gets the name of the web search activity.
- */
- public ComponentName getWebSearchActivity() {
- return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
- }
-
- @Override
- public ComponentName getAssistIntent(int userHandle) {
- try {
- userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userHandle, true, false, "getAssistIntent", null);
- IPackageManager pm = AppGlobals.getPackageManager();
- Intent assistIntent = new Intent(Intent.ACTION_ASSIST);
- ResolveInfo info =
- pm.resolveIntent(assistIntent,
- assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
- PackageManager.MATCH_DEFAULT_ONLY, userHandle);
- if (info != null) {
- return new ComponentName(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- }
- } catch (RemoteException re) {
- // Local call
- Log.e(TAG, "RemoteException in getAssistIntent: " + re);
- } catch (Exception e) {
- Log.e(TAG, "Exception in getAssistIntent: " + e);
- }
- return null;
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
-
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- synchronized (mSearchables) {
- for (int i = 0; i < mSearchables.size(); i++) {
- ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i));
- ipw.increaseIndent();
- mSearchables.valueAt(i).dump(fd, ipw, args);
- ipw.decreaseIndent();
- }
- }
- }
-}
diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java
deleted file mode 100644
index a0095d6..0000000
--- a/core/java/android/server/search/Searchables.java
+++ /dev/null
@@ -1,464 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.search;
-
-import android.app.AppGlobals;
-import android.app.SearchManager;
-import android.app.SearchableInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * This class maintains the information about all searchable activities.
- * This is a hidden class.
- */
-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
- // 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;
- // Contains all installed activities that handle the global search
- // intent.
- private List<ResolveInfo> mGlobalSearchActivities;
- private ComponentName mCurrentGlobalSearchActivity = null;
- private ComponentName mWebSearchActivity = 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";
-
- // Cache the package manager instance
- final private IPackageManager mPm;
- // User for which this Searchables caches information
- private int mUserId;
-
- /**
- *
- * @param context Context to use for looking up activities etc.
- */
- public Searchables (Context context, int userId) {
- mContext = context;
- mUserId = userId;
- mPm = AppGlobals.getPackageManager();
- }
-
- /**
- * Look up, or construct, based on the activity.
- *
- * 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
- * presence of a "android.app.searchable" meta-data attribute.
- * The value is a reference to an XML file containing search information.</li>
- * <li>A related activity implements search. This is indicated by the
- * presence of a "android.app.default_searchable" meta-data attribute.
- * The value is a string naming the activity implementing search. In this
- * 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
- * automatic return? Probably not, but it's one way to do it.
- *
- * @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) {
- // Step 1. Is the result already hashed? (case 1)
- SearchableInfo result;
- synchronized (this) {
- 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.
- // References must point directly to searchable activities.
-
- ActivityInfo ai = null;
- try {
- ai = mPm.getActivityInfo(activity, PackageManager.GET_META_DATA, mUserId);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error getting activity info " + re);
- return null;
- }
- String refActivityName = null;
-
- // First look for activity-specific reference
- Bundle md = ai.metaData;
- if (md != null) {
- refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
- }
- // If not found, try for app-wide reference
- if (refActivityName == null) {
- md = ai.applicationInfo.metaData;
- if (md != null) {
- refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
- }
- }
-
- // Irrespective of source, if a reference was found, follow it.
- if (refActivityName != null)
- {
- // This value is deprecated, return null
- if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
- return null;
- }
- String pkg = activity.getPackageName();
- ComponentName referredActivity;
- if (refActivityName.charAt(0) == '.') {
- referredActivity = new ComponentName(pkg, pkg + refActivityName);
- } else {
- referredActivity = new ComponentName(pkg, refActivityName);
- }
-
- // Now try the referred activity, and if found, cache
- // it against the original name so we can skip the check
- synchronized (this) {
- result = mSearchablesMap.get(referredActivity);
- if (result != null) {
- mSearchablesMap.put(activity, result);
- return result;
- }
- }
- }
-
- // Step 3. None found. Return null.
- return null;
-
- }
-
- /**
- * 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
- = new HashMap<ComponentName, SearchableInfo>();
- ArrayList<SearchableInfo> newSearchablesList
- = new ArrayList<SearchableInfo>();
- ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
- = new ArrayList<SearchableInfo>();
-
- // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
- List<ResolveInfo> searchList;
- final Intent intent = new Intent(Intent.ACTION_SEARCH);
-
- long ident = Binder.clearCallingIdentity();
- try {
- searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);
-
- List<ResolveInfo> webSearchInfoList;
- final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
- webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
-
- // analyze each one, generate a Searchables record, and record
- 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 = (ii < search_count)
- ? searchList.get(ii)
- : webSearchInfoList.get(ii - search_count);
- ActivityInfo ai = info.activityInfo;
- // Check first to avoid duplicate entries.
- if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
- SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai,
- mUserId);
- if (searchable != null) {
- newSearchablesList.add(searchable);
- newSearchablesMap.put(searchable.getSearchActivity(), searchable);
- if (searchable.shouldIncludeInGlobalSearch()) {
- newSearchablesInGlobalSearchList.add(searchable);
- }
- }
- }
- }
- }
-
- List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities();
-
- // Find the global search activity
- ComponentName newGlobalSearchActivity = findGlobalSearchActivity(
- newGlobalSearchActivities);
-
- // Find the web search activity
- ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
-
- // Store a consistent set of new values
- synchronized (this) {
- mSearchablesMap = newSearchablesMap;
- mSearchablesList = newSearchablesList;
- mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
- mGlobalSearchActivities = newGlobalSearchActivities;
- mCurrentGlobalSearchActivity = newGlobalSearchActivity;
- mWebSearchActivity = newWebSearchActivity;
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Returns a sorted list of installed search providers as per
- * the following heuristics:
- *
- * (a) System apps are given priority over non system apps.
- * (b) Among system apps and non system apps, the relative ordering
- * is defined by their declared priority.
- */
- private List<ResolveInfo> findGlobalSearchActivities() {
- // Step 1 : Query the package manager for a list
- // of activities that can handle the GLOBAL_SEARCH intent.
- Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- List<ResolveInfo> activities =
- queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
- if (activities != null && !activities.isEmpty()) {
- // Step 2: Rank matching activities according to our heuristics.
- Collections.sort(activities, GLOBAL_SEARCH_RANKER);
- }
-
- return activities;
- }
-
- /**
- * Finds the global search activity.
- */
- private ComponentName findGlobalSearchActivity(List<ResolveInfo> installed) {
- // Fetch the global search provider from the system settings,
- // and if it's still installed, return it.
- final String searchProviderSetting = getGlobalSearchProviderSetting();
- if (!TextUtils.isEmpty(searchProviderSetting)) {
- final ComponentName globalSearchComponent = ComponentName.unflattenFromString(
- searchProviderSetting);
- if (globalSearchComponent != null && isInstalled(globalSearchComponent)) {
- return globalSearchComponent;
- }
- }
-
- return getDefaultGlobalSearchProvider(installed);
- }
-
- /**
- * Checks whether the global search provider with a given
- * component name is installed on the system or not. This deals with
- * cases such as the removal of an installed provider.
- */
- private boolean isInstalled(ComponentName globalSearch) {
- Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
- intent.setComponent(globalSearch);
-
- List<ResolveInfo> activities = queryIntentActivities(intent,
- PackageManager.MATCH_DEFAULT_ONLY);
- if (activities != null && !activities.isEmpty()) {
- return true;
- }
-
- return false;
- }
-
- private static final Comparator<ResolveInfo> GLOBAL_SEARCH_RANKER =
- new Comparator<ResolveInfo>() {
- @Override
- public int compare(ResolveInfo lhs, ResolveInfo rhs) {
- if (lhs == rhs) {
- return 0;
- }
- boolean lhsSystem = isSystemApp(lhs);
- boolean rhsSystem = isSystemApp(rhs);
-
- if (lhsSystem && !rhsSystem) {
- return -1;
- } else if (rhsSystem && !lhsSystem) {
- return 1;
- } else {
- // Either both system engines, or both non system
- // engines.
- //
- // Note, this isn't a typo. Higher priority numbers imply
- // higher priority, but are "lower" in the sort order.
- return rhs.priority - lhs.priority;
- }
- }
- };
-
- /**
- * @return true iff. the resolve info corresponds to a system application.
- */
- private static final boolean isSystemApp(ResolveInfo res) {
- return (res.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- /**
- * Returns the highest ranked search provider as per the
- * ranking defined in {@link #getGlobalSearchActivities()}.
- */
- private ComponentName getDefaultGlobalSearchProvider(List<ResolveInfo> providerList) {
- if (providerList != null && !providerList.isEmpty()) {
- ActivityInfo ai = providerList.get(0).activityInfo;
- return new ComponentName(ai.packageName, ai.name);
- }
-
- Log.w(LOG_TAG, "No global search activity found");
- return null;
- }
-
- private String getGlobalSearchProviderSetting() {
- return Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY);
- }
-
- /**
- * Finds the web search activity.
- *
- * Only looks in the package of the global search activity.
- */
- private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
- if (globalSearchActivity == null) {
- return null;
- }
- Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
- intent.setPackage(globalSearchActivity.getPackageName());
- List<ResolveInfo> activities =
- queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
- if (activities != null && !activities.isEmpty()) {
- ActivityInfo ai = activities.get(0).activityInfo;
- // TODO: do some sanity checks here?
- return new ComponentName(ai.packageName, ai.name);
- }
- Log.w(LOG_TAG, "No web search activity found");
- return null;
- }
-
- private List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
- List<ResolveInfo> activities = null;
- try {
- activities =
- mPm.queryIntentActivities(intent,
- intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- flags, mUserId);
- } catch (RemoteException re) {
- // Local call
- }
- return activities;
- }
-
- /**
- * Returns the list of searchable activities.
- */
- public synchronized ArrayList<SearchableInfo> getSearchablesList() {
- 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 activities that handle the global search intent.
- */
- public synchronized ArrayList<ResolveInfo> getGlobalSearchActivities() {
- return new ArrayList<ResolveInfo>(mGlobalSearchActivities);
- }
-
- /**
- * Gets the name of the global search activity.
- */
- public synchronized ComponentName getGlobalSearchActivity() {
- return mCurrentGlobalSearchActivity;
- }
-
- /**
- * Gets the name of the web search activity.
- */
- public synchronized ComponentName getWebSearchActivity() {
- return mWebSearchActivity;
- }
-
- void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Searchable authorities:");
- synchronized (this) {
- if (mSearchablesList != null) {
- for (SearchableInfo info: mSearchablesList) {
- pw.print(" "); pw.println(info.getSuggestAuthority());
- }
- }
- }
- }
-}
diff --git a/core/java/android/server/search/package.html b/core/java/android/server/search/package.html
deleted file mode 100644
index c9f96a6..0000000
--- a/core/java/android/server/search/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<body>
-
-{@hide}
-
-</body>
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d1b23e4..71d8fb6 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -84,7 +84,7 @@ public abstract class WallpaperService extends Service {
* tag.
*/
public static final String SERVICE_META_DATA = "android.service.wallpaper";
-
+
static final String TAG = "WallpaperService";
static final boolean DEBUG = false;
@@ -100,7 +100,6 @@ public abstract class WallpaperService extends Service {
private static final int MSG_WINDOW_MOVED = 10035;
private static final int MSG_TOUCH_EVENT = 10040;
- private Looper mCallbackLooper;
private final ArrayList<Engine> mActiveEngines
= new ArrayList<Engine>();
@@ -154,6 +153,7 @@ public abstract class WallpaperService extends Service {
int mCurWindowPrivateFlags = mWindowPrivateFlags;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
+ final Rect mOverscanInsets = new Rect();
final Rect mContentInsets = new Rect();
final Configuration mConfiguration = new Configuration();
@@ -253,7 +253,7 @@ public abstract class WallpaperService extends Service {
final BaseIWindow mWindow = new BaseIWindow() {
@Override
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0);
@@ -628,7 +628,7 @@ public abstract class WallpaperService extends Service {
final int relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, mWinFrame, mContentInsets,
+ View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mConfiguration, mSurfaceHolder.mSurface);
if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
@@ -1099,13 +1099,14 @@ public abstract class WallpaperService extends Service {
mTarget = context;
}
+ @Override
public void attach(IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight) {
new IWallpaperEngineWrapper(mTarget, conn, windowToken,
windowType, isPreview, reqWidth, reqHeight);
}
}
-
+
@Override
public void onCreate() {
super.onCreate();
@@ -1128,20 +1129,7 @@ public abstract class WallpaperService extends Service {
public final IBinder onBind(Intent intent) {
return new IWallpaperServiceWrapper(this);
}
-
- /**
- * This allows subclasses to change the thread that most callbacks
- * occur on. Currently hidden because it is mostly needed for the
- * image wallpaper (which runs in the system process and doesn't want
- * to get stuck running on that seriously in use main thread). Not
- * exposed right now because the semantics of this are not totally
- * well defined and some callbacks can still happen on the main thread).
- * @hide
- */
- public void setCallbackLooper(Looper looper) {
- mCallbackLooper = looper;
- }
-
+
/**
* Must be implemented to return a new instance of the wallpaper's engine.
* Note that multiple instances may be active at the same time, such as
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index 3e33e8e..ab8f82f 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -20,10 +20,12 @@ import android.os.FileUtils;
import android.util.Log;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
/**
* Speech synthesis request that writes the audio to a WAV file.
@@ -39,16 +41,19 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
private static final short WAV_FORMAT_PCM = 0x0001;
private final Object mStateLock = new Object();
- private final File mFileName;
+
private int mSampleRateInHz;
private int mAudioFormat;
private int mChannelCount;
- private RandomAccessFile mFile;
+
+ private FileChannel mFileChannel;
+
+ private boolean mStarted = false;
private boolean mStopped = false;
private boolean mDone = false;
- FileSynthesisCallback(File fileName) {
- mFileName = fileName;
+ FileSynthesisCallback(FileChannel fileChannel) {
+ mFileChannel = fileChannel;
}
@Override
@@ -63,54 +68,23 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
* Must be called while holding the monitor on {@link #mStateLock}.
*/
private void cleanUp() {
- closeFileAndWidenPermissions();
- if (mFile != null) {
- mFileName.delete();
- }
+ closeFile();
}
/**
* Must be called while holding the monitor on {@link #mStateLock}.
*/
- private void closeFileAndWidenPermissions() {
+ private void closeFile() {
try {
- if (mFile != null) {
- mFile.close();
- mFile = null;
+ if (mFileChannel != null) {
+ mFileChannel.close();
+ mFileChannel = null;
}
} catch (IOException ex) {
- Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
- }
-
- try {
- // Make the written file readable and writeable by everyone.
- // This allows the app that requested synthesis to read the file.
- //
- // Note that the directory this file was written must have already
- // been world writeable in order it to have been
- // written to in the first place.
- FileUtils.setPermissions(mFileName.getAbsolutePath(), 0666, -1, -1); //-rw-rw-rw
- } catch (SecurityException se) {
- Log.e(TAG, "Security exception setting rw permissions on : " + mFileName);
- }
- }
-
- /**
- * Checks whether a given file exists, and deletes it if it does.
- */
- private boolean maybeCleanupExistingFile(File file) {
- if (file.exists()) {
- Log.v(TAG, "File " + file + " exists, deleting.");
- if (!file.delete()) {
- Log.e(TAG, "Failed to delete " + file);
- return false;
- }
+ Log.e(TAG, "Failed to close output file descriptor", ex);
}
-
- return true;
}
-
@Override
public int getMaxBufferSize() {
return MAX_AUDIO_BUFFER_SIZE;
@@ -132,25 +106,20 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
- if (mFile != null) {
+ if (mStarted) {
cleanUp();
throw new IllegalArgumentException("FileSynthesisRequest.start() called twice");
}
-
- if (!maybeCleanupExistingFile(mFileName)) {
- return TextToSpeech.ERROR;
- }
-
+ mStarted = true;
mSampleRateInHz = sampleRateInHz;
mAudioFormat = audioFormat;
mChannelCount = channelCount;
+
try {
- mFile = new RandomAccessFile(mFileName, "rw");
- // Reserve space for WAV header
- mFile.write(new byte[WAV_HEADER_LENGTH]);
+ mFileChannel.write(ByteBuffer.allocate(WAV_HEADER_LENGTH));
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
- Log.e(TAG, "Failed to open " + mFileName + ": " + ex);
+ Log.e(TAG, "Failed to write wav header to output file descriptor" + ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -168,15 +137,15 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
- if (mFile == null) {
+ if (mFileChannel == null) {
Log.e(TAG, "File not open");
return TextToSpeech.ERROR;
}
try {
- mFile.write(buffer, offset, length);
+ mFileChannel.write(ByteBuffer.wrap(buffer, offset, length));
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
- Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ Log.e(TAG, "Failed to write to output file descriptor", ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -197,21 +166,21 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
if (DBG) Log.d(TAG, "Request has been aborted.");
return TextToSpeech.ERROR;
}
- if (mFile == null) {
+ if (mFileChannel == null) {
Log.e(TAG, "File not open");
return TextToSpeech.ERROR;
}
try {
// Write WAV header at start of file
- mFile.seek(0);
- int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH);
- mFile.write(
+ mFileChannel.position(0);
+ int dataLength = (int) (mFileChannel.size() - WAV_HEADER_LENGTH);
+ mFileChannel.write(
makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
- closeFileAndWidenPermissions();
+ closeFile();
mDone = true;
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
- Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ Log.e(TAG, "Failed to write to output file descriptor", ex);
cleanUp();
return TextToSpeech.ERROR;
}
@@ -226,7 +195,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
}
}
- private byte[] makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
+ private ByteBuffer makeWavHeader(int sampleRateInHz, int audioFormat, int channelCount,
int dataLength) {
// TODO: is AudioFormat.ENCODING_DEFAULT always the same as ENCODING_PCM_16BIT?
int sampleSizeInBytes = (audioFormat == AudioFormat.ENCODING_PCM_8BIT ? 1 : 2);
@@ -251,8 +220,9 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
header.putShort(bitsPerSample);
header.put(new byte[]{ 'd', 'a', 't', 'a' });
header.putInt(dataLength);
+ header.flip();
- return headerBuf;
+ return header;
}
}
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
index ab63187..b7bc70c 100644
--- a/core/java/android/speech/tts/ITextToSpeechService.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -18,6 +18,7 @@ package android.speech.tts;
import android.net.Uri;
import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
import android.speech.tts.ITextToSpeechCallback;
/**
@@ -44,11 +45,12 @@ interface ITextToSpeechService {
* @param callingInstance a binder representing the identity of the calling
* TextToSpeech object.
* @param text The text to synthesize.
- * @param filename The file to write the synthesized audio to.
+ * @param fileDescriptor The file descriptor to write the synthesized audio to. Has to be
+ writable.
* @param param Request parameters.
*/
- int synthesizeToFile(in IBinder callingInstance, in String text,
- in String filename, in Bundle params);
+ int synthesizeToFileDescriptor(in IBinder callingInstance, in String text,
+ in ParcelFileDescriptor fileDescriptor, in Bundle params);
/**
* Plays an existing audio resource.
@@ -97,7 +99,19 @@ interface ITextToSpeechService {
* be empty too.
*/
String[] getLanguage();
-
+
+ /**
+ * Returns a default TTS language, country and variant as set by the user.
+ *
+ * Can be called from multiple threads.
+ *
+ * @return A 3-element array, containing language (ISO 3-letter code),
+ * country (ISO 3-letter code) and variant used by the engine.
+ * The country and variant may be {@code ""}. If country is empty, then variant must
+ * be empty too.
+ */
+ String[] getClientDefaultLanguage();
+
/**
* Checks whether the engine supports a given language.
*
@@ -131,6 +145,8 @@ interface ITextToSpeechService {
/**
* Notifies the engine that it should load a speech synthesis language.
*
+ * @param caller a binder representing the identity of the calling
+ * TextToSpeech object.
* @param lang ISO-3 language code.
* @param country ISO-3 country code. May be empty or null.
* @param variant Language variant. May be empty or null.
@@ -141,13 +157,14 @@ interface ITextToSpeechService {
* {@link TextToSpeech#LANG_MISSING_DATA}
* {@link TextToSpeech#LANG_NOT_SUPPORTED}.
*/
- int loadLanguage(in String lang, in String country, in String variant);
+ int loadLanguage(in IBinder caller, in String lang, in String country, in String variant);
/**
* Sets the callback that will be notified when playback of utterance from the
* given app are completed.
*
- * @param callingApp Package name for the app whose utterance the callback will handle.
+ * @param caller Instance a binder representing the identity of the calling
+ * TextToSpeech object.
* @param cb The callback.
*/
void setCallback(in IBinder caller, ITextToSpeechCallback cb);
diff --git a/core/java/android/speech/tts/SynthesisCallback.java b/core/java/android/speech/tts/SynthesisCallback.java
index d70c371..f98bb09 100644
--- a/core/java/android/speech/tts/SynthesisCallback.java
+++ b/core/java/android/speech/tts/SynthesisCallback.java
@@ -22,10 +22,11 @@ package android.speech.tts;
* {@link #start}, then {@link #audioAvailable} until all audio has been provided, then finally
* {@link #done}.
*
- *
* {@link #error} can be called at any stage in the synthesis process to
* indicate that an error has occurred, but if the call is made after a call
* to {@link #done}, it might be discarded.
+ *
+ * After {@link #start} been called, {@link #done} must be called regardless of errors.
*/
public interface SynthesisCallback {
/**
@@ -72,6 +73,8 @@ public interface SynthesisCallback {
* This method should only be called on the synthesis thread,
* while in {@link TextToSpeechService#onSynthesizeText}.
*
+ * This method has to be called if {@link #start} was called.
+ *
* @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
*/
public int done();
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 5e367cb..73d400e 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -24,13 +24,18 @@ import android.content.Intent;
import android.content.ServiceConnection;
import android.media.AudioManager;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -139,7 +144,10 @@ public class TextToSpeech {
* Listener that will be called when the TTS service has
* completed synthesizing an utterance. This is only called if the utterance
* has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
+ *
+ * @deprecated Use {@link UtteranceProgressListener} instead.
*/
+ @Deprecated
public interface OnUtteranceCompletedListener {
/**
* Called when an utterance has been synthesized.
@@ -235,19 +243,28 @@ public class TextToSpeech {
/**
* Indicates erroneous data when checking the installation status of the resources used by
* the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
+ *
+ * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
*/
+ @Deprecated
public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
/**
* Indicates missing resources when checking the installation status of the resources used
* by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
+ *
+ * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
*/
+ @Deprecated
public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
/**
* Indicates missing storage volume when checking the installation status of the resources
* used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
+ *
+ * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
*/
+ @Deprecated
public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
/**
@@ -283,9 +300,8 @@ public class TextToSpeech {
"android.speech.tts.engine.INSTALL_TTS_DATA";
/**
- * Broadcast Action: broadcast to signal the completion of the installation of
- * the data files used by the synthesis engine. Success or failure is indicated in the
- * {@link #EXTRA_TTS_DATA_INSTALLED} extra.
+ * Broadcast Action: broadcast to signal the change in the list of available
+ * languages or/and their features.
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TTS_DATA_INSTALLED =
@@ -298,20 +314,16 @@ public class TextToSpeech {
* return one of the following codes:
* {@link #CHECK_VOICE_DATA_PASS},
* {@link #CHECK_VOICE_DATA_FAIL},
- * {@link #CHECK_VOICE_DATA_BAD_DATA},
- * {@link #CHECK_VOICE_DATA_MISSING_DATA}, or
- * {@link #CHECK_VOICE_DATA_MISSING_VOLUME}.
* <p> Moreover, the data received in the activity result will contain the following
* fields:
* <ul>
- * <li>{@link #EXTRA_VOICE_DATA_ROOT_DIRECTORY} which
- * indicates the path to the location of the resource files,</li>
- * <li>{@link #EXTRA_VOICE_DATA_FILES} which contains
- * the list of all the resource files,</li>
- * <li>and {@link #EXTRA_VOICE_DATA_FILES_INFO} which
- * contains, for each resource file, the description of the language covered by
- * the file in the xxx-YYY format, where xxx is the 3-letter ISO language code,
- * and YYY is the 3-letter ISO country code.</li>
+ * <li>{@link #EXTRA_AVAILABLE_VOICES} which contains an ArrayList<String> of all the
+ * available voices. The format of each voice is: lang-COUNTRY-variant where COUNTRY and
+ * variant are optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").</li>
+ * <li>{@link #EXTRA_UNAVAILABLE_VOICES} which contains an ArrayList<String> of all the
+ * unavailable voices (ones that user can install). The format of each voice is:
+ * lang-COUNTRY-variant where COUNTRY and variant are optional (ie, "eng" or
+ * "eng-USA" or "eng-USA-FEMALE").</li>
* </ul>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
@@ -319,37 +331,33 @@ public class TextToSpeech {
"android.speech.tts.engine.CHECK_TTS_DATA";
/**
- * Activity intent for getting some sample text to use for demonstrating TTS.
+ * Activity intent for getting some sample text to use for demonstrating TTS. Specific
+ * locale have to be requested by passing following extra parameters:
+ * <ul>
+ * <li>language</li>
+ * <li>country</li>
+ * <li>variant</li>
+ * </ul>
*
- * @hide This intent was used by engines written against the old API.
- * Not sure if it should be exposed.
+ * Upon completion, the activity result may contain the following fields:
+ * <ul>
+ * <li>{@link #EXTRA_SAMPLE_TEXT} which contains an String with sample text.</li>
+ * </ul>
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_GET_SAMPLE_TEXT =
"android.speech.tts.engine.GET_SAMPLE_TEXT";
- // extras for a TTS engine's check data activity
/**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
- * the TextToSpeech engine specifies the path to its resources.
+ * Extra information received with the {@link #ACTION_GET_SAMPLE_TEXT} intent result where
+ * the TextToSpeech engine returns an String with sample text for requested voice
*/
- public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+ public static final String EXTRA_SAMPLE_TEXT = "sampleText";
- /**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
- * the TextToSpeech engine specifies the file names of its resources under the
- * resource path.
- */
- public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
-
- /**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
- * the TextToSpeech engine specifies the locale associated with each resource file.
- */
- public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
+ // extras for a TTS engine's check data activity
/**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
* the TextToSpeech engine returns an ArrayList<String> of all the available voices.
* The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
@@ -357,7 +365,7 @@ public class TextToSpeech {
public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
/**
- * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
* the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
* The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
@@ -365,22 +373,63 @@ public class TextToSpeech {
public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
/**
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
+ * the TextToSpeech engine specifies the path to its resources.
+ *
+ * It may be used by language packages to find out where to put their data.
+ *
+ * @deprecated TTS engine implementation detail, this information has no use for
+ * text-to-speech API client.
+ */
+ @Deprecated
+ public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
+
+ /**
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
+ * the TextToSpeech engine specifies the file names of its resources under the
+ * resource path.
+ *
+ * @deprecated TTS engine implementation detail, this information has no use for
+ * text-to-speech API client.
+ */
+ @Deprecated
+ public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
+
+ /**
+ * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
+ * the TextToSpeech engine specifies the locale associated with each resource file.
+ *
+ * @deprecated TTS engine implementation detail, this information has no use for
+ * text-to-speech API client.
+ */
+ @Deprecated
+ public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
+
+ /**
* Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
* caller indicates to the TextToSpeech engine which specific sets of voice data to
* check for by sending an ArrayList<String> of the voices that are of interest.
* The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
* optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
+ *
+ * @deprecated Redundant functionality, checking for existence of specific sets of voice
+ * data can be done on client side.
*/
+ @Deprecated
public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
// extras for a TTS engine's data installation
/**
- * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent.
+ * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent result.
* It indicates whether the data files for the synthesis engine were successfully
* installed. The installation was initiated with the {@link #ACTION_INSTALL_TTS_DATA}
* intent. The possible values for this extra are
* {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}.
+ *
+ * @deprecated No longer in use. If client ise interested in information about what
+ * changed, is should send ACTION_CHECK_TTS_DATA intent to discover available voices.
*/
+ @Deprecated
public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
// keys for the parameters passed with speak commands. Hidden keys are used internally
@@ -473,11 +522,16 @@ public class TextToSpeech {
* for a description of how feature keys work. If set and supported by the engine
* as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
* text on-device (without making network requests).
+ *
+ * @see TextToSpeech#speak(String, int, java.util.HashMap)
+ * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
+ * @see TextToSpeech#getFeatures(java.util.Locale)
*/
public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
}
private final Context mContext;
+ private Connection mConnectingServiceConnection;
private Connection mServiceConnection;
private OnInitListener mInitListener;
// Written from an unspecified application thread, read from
@@ -553,21 +607,24 @@ public class TextToSpeech {
initTts();
}
- private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
- return runAction(action, errorResult, method, false);
+ private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
+ boolean onlyEstablishedConnection) {
+ return runAction(action, errorResult, method, false, onlyEstablishedConnection);
}
private <R> R runAction(Action<R> action, R errorResult, String method) {
- return runAction(action, errorResult, method, true);
+ return runAction(action, errorResult, method, true, true);
}
- private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+ private <R> R runAction(Action<R> action, R errorResult, String method,
+ boolean reconnect, boolean onlyEstablishedConnection) {
synchronized (mStartLock) {
if (mServiceConnection == null) {
Log.w(TAG, method + " failed: not bound to TTS engine");
return errorResult;
}
- return mServiceConnection.runAction(action, errorResult, method, reconnect);
+ return mServiceConnection.runAction(action, errorResult, method, reconnect,
+ onlyEstablishedConnection);
}
}
@@ -630,6 +687,7 @@ public class TextToSpeech {
return false;
} else {
Log.i(TAG, "Sucessfully bound to " + engine);
+ mConnectingServiceConnection = connection;
return true;
}
}
@@ -653,6 +711,16 @@ public class TextToSpeech {
* so the TextToSpeech engine can be cleanly stopped.
*/
public void shutdown() {
+ // Special case, we are asked to shutdown connection that did finalize its connection.
+ synchronized (mStartLock) {
+ if (mConnectingServiceConnection != null) {
+ mContext.unbindService(mConnectingServiceConnection);
+ mConnectingServiceConnection = null;
+ return;
+ }
+ }
+
+ // Post connection case
runActionNoReconnect(new Action<Void>() {
@Override
public Void run(ITextToSpeechService service) throws RemoteException {
@@ -670,7 +738,7 @@ public class TextToSpeech {
mCurrentEngine = null;
return null;
}
- }, null, "shutdown");
+ }, null, "shutdown", false);
}
/**
@@ -793,10 +861,16 @@ public class TextToSpeech {
}
/**
- * Speaks the string using the specified queuing strategy and speech
- * parameters.
+ * Speaks the string using the specified queuing strategy and speech parameters.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
- * @param text The string of text to be spoken.
+ * @param text The string of text to be spoken. No longer than
+ * {@link #getMaxSpeechInputLength()} characters.
* @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
* @param params Parameters for the request. Can be null.
* Supported parameter names:
@@ -809,7 +883,7 @@ public class TextToSpeech {
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
* engine named "com.svox.pico" if it is being used.
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
*/
public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
return runAction(new Action<Integer>() {
@@ -830,6 +904,12 @@ public class TextToSpeech {
* Plays the earcon using the specified queueing mode and parameters.
* The earcon must already have been added with {@link #addEarcon(String, String)} or
* {@link #addEarcon(String, String, int)}.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
* @param earcon The earcon that should be played
* @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
@@ -842,7 +922,7 @@ public class TextToSpeech {
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
* engine named "com.svox.pico" if it is being used.
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
*/
public int playEarcon(final String earcon, final int queueMode,
final HashMap<String, String> params) {
@@ -862,6 +942,12 @@ public class TextToSpeech {
/**
* Plays silence for the specified amount of time using the specified
* queue mode.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
* @param durationInMs The duration of the silence.
* @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
@@ -873,7 +959,7 @@ public class TextToSpeech {
* the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
* engine named "com.svox.pico" if it is being used.
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
*/
public int playSilence(final long durationInMs, final int queueMode,
final HashMap<String, String> params) {
@@ -1005,6 +1091,24 @@ public class TextToSpeech {
}
/**
+ * Returns a Locale instance describing the language currently being used as the default
+ * Text-to-speech language.
+ *
+ * @return language, country (if any) and variant (if any) used by the client stored in a
+ * Locale instance, or {@code null} on error.
+ */
+ public Locale getDefaultLanguage() {
+ return runAction(new Action<Locale>() {
+ @Override
+ public Locale run(ITextToSpeechService service) throws RemoteException {
+ String[] defaultLanguage = service.getClientDefaultLanguage();
+
+ return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
+ }
+ }, null, "getDefaultLanguage");
+ }
+
+ /**
* Sets the text-to-speech language.
* The TTS engine will try to use the closest match to the specified
* language as represented by the Locale, but there is no guarantee that the exact same Locale
@@ -1031,7 +1135,8 @@ public class TextToSpeech {
// the available parts.
// Note that the language is not actually set here, instead it is cached so it
// will be associated with all upcoming utterances.
- int result = service.loadLanguage(language, country, variant);
+
+ int result = service.loadLanguage(getCallerIdentity(), language, country, variant);
if (result >= LANG_AVAILABLE){
if (result < LANG_COUNTRY_VAR_AVAILABLE) {
variant = "";
@@ -1049,21 +1154,30 @@ public class TextToSpeech {
}
/**
- * Returns a Locale instance describing the language currently being used by the TextToSpeech
- * engine.
+ * Returns a Locale instance describing the language currently being used for synthesis
+ * requests sent to the TextToSpeech engine.
+ *
+ * In Android 4.2 and before (API <= 17) this function returns the language that is currently
+ * being used by the TTS engine. That is the last language set by this or any other
+ * client by a {@link TextToSpeech#setLanguage} call to the same engine.
*
- * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
- * instance, or {@code null} on error.
+ * In Android versions after 4.2 this function returns the language that is currently being
+ * used for the synthesis requests sent from this client. That is the last language set
+ * by a {@link TextToSpeech#setLanguage} call on this instance.
+ *
+ * @return language, country (if any) and variant (if any) used by the client stored in a
+ * Locale instance, or {@code null} on error.
*/
public Locale getLanguage() {
return runAction(new Action<Locale>() {
@Override
- public Locale run(ITextToSpeechService service) throws RemoteException {
- String[] locStrings = service.getLanguage();
- if (locStrings != null && locStrings.length == 3) {
- return new Locale(locStrings[0], locStrings[1], locStrings[2]);
- }
- return null;
+ public Locale run(ITextToSpeechService service) {
+ /* No service call, but we're accessing mParams, hence need for
+ wrapping it as an Action instance */
+ String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
+ String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
+ String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
+ return new Locale(lang, country, variant);
}
}, null, "getLanguage");
}
@@ -1089,8 +1203,15 @@ public class TextToSpeech {
/**
* Synthesizes the given text to a file using the specified parameters.
+ * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even started!) at the
+ * time when this method returns. In order to reliably detect errors during synthesis,
+ * we recommend setting an utterance progress listener (see
+ * {@link #setOnUtteranceProgressListener}) and using the
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
*
- * @param text The text that should be synthesized
+ * @param text The text that should be synthesized. No longer than
+ * {@link #getMaxSpeechInputLength()} characters.
* @param params Parameters for the request. Can be null.
* Supported parameter names:
* {@link Engine#KEY_PARAM_UTTERANCE_ID}.
@@ -1101,15 +1222,36 @@ public class TextToSpeech {
* @param filename Absolute file filename to write the generated audio data to.It should be
* something like "/sdcard/myappsounds/mysound.wav".
*
- * @return {@link #ERROR} or {@link #SUCCESS}.
+ * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
*/
public int synthesizeToFile(final String text, final HashMap<String, String> params,
final String filename) {
return runAction(new Action<Integer>() {
@Override
public Integer run(ITextToSpeechService service) throws RemoteException {
- return service.synthesizeToFile(getCallerIdentity(), text, filename,
- getParams(params));
+ ParcelFileDescriptor fileDescriptor;
+ int returnValue;
+ try {
+ File file = new File(filename);
+ if(file.exists() && !file.canWrite()) {
+ Log.e(TAG, "Can't write to " + filename);
+ return ERROR;
+ }
+ fileDescriptor = ParcelFileDescriptor.open(file,
+ ParcelFileDescriptor.MODE_WRITE_ONLY |
+ ParcelFileDescriptor.MODE_CREATE |
+ ParcelFileDescriptor.MODE_TRUNCATE);
+ returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
+ fileDescriptor, getParams(params));
+ fileDescriptor.close();
+ return returnValue;
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Opening file " + filename + " failed", e);
+ return ERROR;
+ } catch (IOException e) {
+ Log.e(TAG, "Closing file " + filename + " failed", e);
+ return ERROR;
+ }
}
}, ERROR, "synthesizeToFile");
}
@@ -1253,9 +1395,13 @@ public class TextToSpeech {
return mEnginesHelper.getEngines();
}
-
private class Connection implements ServiceConnection {
private ITextToSpeechService mService;
+
+ private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
+
+ private boolean mEstablished;
+
private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
@Override
public void onDone(String utteranceId) {
@@ -1282,23 +1428,66 @@ public class TextToSpeech {
}
};
+ private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
+ private final ComponentName mName;
+
+ public SetupConnectionAsyncTask(ComponentName name) {
+ mName = name;
+ }
+
+ @Override
+ protected Integer doInBackground(Void... params) {
+ synchronized(mStartLock) {
+ if (isCancelled()) {
+ return null;
+ }
+
+ try {
+ mService.setCallback(getCallerIdentity(), mCallback);
+ String[] defaultLanguage = mService.getClientDefaultLanguage();
+
+ mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
+ mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
+ mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
+
+ Log.i(TAG, "Set up connection to " + mName);
+ return SUCCESS;
+ } catch (RemoteException re) {
+ Log.e(TAG, "Error connecting to service, setCallback() failed");
+ return ERROR;
+ }
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ synchronized(mStartLock) {
+ if (mOnSetupConnectionAsyncTask == this) {
+ mOnSetupConnectionAsyncTask = null;
+ }
+ mEstablished = true;
+ dispatchOnInit(result);
+ }
+ }
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- Log.i(TAG, "Connected to " + name);
synchronized(mStartLock) {
- if (mServiceConnection != null) {
- // Disconnect any previous service connection
- mServiceConnection.disconnect();
+ mConnectingServiceConnection = null;
+
+ Log.i(TAG, "Connected to " + name);
+
+ if (mOnSetupConnectionAsyncTask != null) {
+ mOnSetupConnectionAsyncTask.cancel(false);
}
- mServiceConnection = this;
+
mService = ITextToSpeechService.Stub.asInterface(service);
- try {
- mService.setCallback(getCallerIdentity(), mCallback);
- dispatchOnInit(SUCCESS);
- } catch (RemoteException re) {
- Log.e(TAG, "Error connecting to service, setCallback() failed");
- dispatchOnInit(ERROR);
- }
+ mServiceConnection = Connection.this;
+
+ mEstablished = false;
+ mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
+ mOnSetupConnectionAsyncTask.execute();
}
}
@@ -1306,37 +1495,63 @@ public class TextToSpeech {
return mCallback;
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
+ /**
+ * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
+ *
+ * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
+ */
+ private boolean clearServiceConnection() {
synchronized(mStartLock) {
+ boolean result = false;
+ if (mOnSetupConnectionAsyncTask != null) {
+ result = mOnSetupConnectionAsyncTask.cancel(false);
+ mOnSetupConnectionAsyncTask = null;
+ }
+
mService = null;
// If this is the active connection, clear it
if (mServiceConnection == this) {
mServiceConnection = null;
}
+ return result;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "Asked to disconnect from " + name);
+ if (clearServiceConnection()) {
+ /* We need to protect against a rare case where engine
+ * dies just after successful connection - and we process onServiceDisconnected
+ * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
+ * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
+ * with ERROR as argument.
+ */
+ dispatchOnInit(ERROR);
}
}
public void disconnect() {
mContext.unbindService(this);
+ clearServiceConnection();
+ }
- synchronized (mStartLock) {
- mService = null;
- // If this is the active connection, clear it
- if (mServiceConnection == this) {
- mServiceConnection = null;
- }
-
- }
+ public boolean isEstablished() {
+ return mService != null && mEstablished;
}
- public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
+ public <R> R runAction(Action<R> action, R errorResult, String method,
+ boolean reconnect, boolean onlyEstablishedConnection) {
synchronized (mStartLock) {
try {
if (mService == null) {
Log.w(TAG, method + " failed: not connected to TTS engine");
return errorResult;
}
+ if (onlyEstablishedConnection && !isEstablished()) {
+ Log.w(TAG, method + " failed: TTS engine connection not fully set up");
+ return errorResult;
+ }
return action.run(mService);
} catch (RemoteException ex) {
Log.e(TAG, method + " failed", ex);
@@ -1394,4 +1609,13 @@ public class TextToSpeech {
}
+ /**
+ * Limit of length of input string passed to speak and synthesizeToFile.
+ *
+ * @see #speak
+ * @see #synthesizeToFile
+ */
+ public static int getMaxSpeechInputLength() {
+ return 4000;
+ }
}
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index d124e68..1bcf3e0 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -26,6 +26,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.provider.Settings;
@@ -33,8 +34,8 @@ import android.speech.tts.TextToSpeech.Engine;
import android.text.TextUtils;
import android.util.Log;
-import java.io.File;
-import java.io.IOException;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Locale;
import java.util.Set;
@@ -74,7 +75,7 @@ public abstract class TextToSpeechService extends Service {
private static final boolean DBG = false;
private static final String TAG = "TextToSpeechService";
- private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
+
private static final String SYNTH_THREAD_NAME = "SynthThread";
private SynthHandler mSynthHandler;
@@ -129,6 +130,8 @@ public abstract class TextToSpeechService extends Service {
*
* Can be called on multiple threads.
*
+ * Its return values HAVE to be consistent with onLoadLanguage.
+ *
* @param lang ISO-3 language code.
* @param country ISO-3 country code. May be empty or null.
* @param variant Language variant. May be empty or null.
@@ -163,6 +166,8 @@ public abstract class TextToSpeechService extends Service {
* at some point in the future.
*
* Can be called on multiple threads.
+ * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
+ * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
*
* @param lang ISO-3 language code.
* @param country ISO-3 country code. May be empty or null.
@@ -256,7 +261,6 @@ public abstract class TextToSpeechService extends Service {
}
private class SynthHandler extends Handler {
-
private SpeechItem mCurrentSpeechItem = null;
public SynthHandler(Looper looper) {
@@ -275,7 +279,7 @@ public abstract class TextToSpeechService extends Service {
private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
if (mCurrentSpeechItem != null &&
- mCurrentSpeechItem.getCallerIdentity() == callerIdentity) {
+ (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
SpeechItem current = mCurrentSpeechItem;
mCurrentSpeechItem = null;
return current;
@@ -296,7 +300,6 @@ public abstract class TextToSpeechService extends Service {
if (current != null) {
current.stop();
}
-
// The AudioPlaybackHandler will be destroyed by the caller.
}
@@ -306,8 +309,15 @@ public abstract class TextToSpeechService extends Service {
* Called on a service binder thread.
*/
public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
+ UtteranceProgressDispatcher utterenceProgress = null;
+ if (speechItem instanceof UtteranceProgressDispatcher) {
+ utterenceProgress = (UtteranceProgressDispatcher) speechItem;
+ }
+
if (!speechItem.isValid()) {
- speechItem.dispatchOnError();
+ if (utterenceProgress != null) {
+ utterenceProgress.dispatchOnError();
+ }
return TextToSpeech.ERROR;
}
@@ -325,6 +335,7 @@ public abstract class TextToSpeechService extends Service {
}
};
Message msg = Message.obtain(this, runnable);
+
// The obj is used to remove all callbacks from the given app in
// stopForApp(String).
//
@@ -334,7 +345,9 @@ public abstract class TextToSpeechService extends Service {
return TextToSpeech.SUCCESS;
} else {
Log.w(TAG, "SynthThread has quit");
- speechItem.dispatchOnError();
+ if (utterenceProgress != null) {
+ utterenceProgress.dispatchOnError();
+ }
return TextToSpeech.ERROR;
}
}
@@ -370,7 +383,7 @@ public abstract class TextToSpeechService extends Service {
}
public int stopAll() {
- // Stop the current speech item unconditionally.
+ // Stop the current speech item unconditionally .
SpeechItem current = setCurrentSpeechItem(null);
if (current != null) {
current.stop();
@@ -393,7 +406,7 @@ public abstract class TextToSpeechService extends Service {
/**
* An item in the synth thread queue.
*/
- private abstract class SpeechItem implements UtteranceProgressDispatcher {
+ private abstract class SpeechItem {
private final Object mCallerIdentity;
protected final Bundle mParams;
private final int mCallerUid;
@@ -412,6 +425,15 @@ public abstract class TextToSpeechService extends Service {
return mCallerIdentity;
}
+
+ public int getCallerUid() {
+ return mCallerUid;
+ }
+
+ public int getCallerPid() {
+ return mCallerPid;
+ }
+
/**
* Checker whether the item is valid. If this method returns false, the item should not
* be played.
@@ -436,6 +458,8 @@ public abstract class TextToSpeechService extends Service {
return playImpl();
}
+ protected abstract int playImpl();
+
/**
* Stops the speech item.
* Must not be called more than once.
@@ -452,6 +476,23 @@ public abstract class TextToSpeechService extends Service {
stopImpl();
}
+ protected abstract void stopImpl();
+
+ protected synchronized boolean isStopped() {
+ return mStopped;
+ }
+ }
+
+ /**
+ * An item in the synth thread queue that process utterance.
+ */
+ private abstract class UtteranceSpeechItem extends SpeechItem
+ implements UtteranceProgressDispatcher {
+
+ public UtteranceSpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
+ super(caller, callerUid, callerPid, params);
+ }
+
@Override
public void dispatchOnDone() {
final String utteranceId = getUtteranceId();
@@ -476,22 +517,6 @@ public abstract class TextToSpeechService extends Service {
}
}
- public int getCallerUid() {
- return mCallerUid;
- }
-
- public int getCallerPid() {
- return mCallerPid;
- }
-
- protected synchronized boolean isStopped() {
- return mStopped;
- }
-
- protected abstract int playImpl();
-
- protected abstract void stopImpl();
-
public int getStreamType() {
return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
}
@@ -519,9 +544,10 @@ public abstract class TextToSpeechService extends Service {
protected float getFloatParam(String key, float defaultValue) {
return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
}
+
}
- class SynthesisSpeechItem extends SpeechItem {
+ class SynthesisSpeechItem extends UtteranceSpeechItem {
// Never null.
private final String mText;
private final SynthesisRequest mSynthesisRequest;
@@ -552,7 +578,7 @@ public abstract class TextToSpeechService extends Service {
Log.e(TAG, "null synthesis text");
return false;
}
- if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) {
+ if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
Log.w(TAG, "Text too long: " + mText.length() + " chars");
return false;
}
@@ -630,19 +656,19 @@ public abstract class TextToSpeechService extends Service {
}
}
- private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
- private final File mFile;
+ private class SynthesisToFileSpeechDescriptorItem extends SynthesisSpeechItem {
+ private final FileDescriptor mFileDescriptor;
- public SynthesisToFileSpeechItem(Object callerIdentity, int callerUid, int callerPid,
- Bundle params, String text,
- File file) {
+ public SynthesisToFileSpeechDescriptorItem(Object callerIdentity, int callerUid,
+ int callerPid, Bundle params, String text, FileDescriptor fileDescriptor) {
super(callerIdentity, callerUid, callerPid, params, text);
- mFile = file;
+ mFileDescriptor = fileDescriptor;
}
@Override
protected AbstractSynthesisCallback createSynthesisCallback() {
- return new FileSynthesisCallback(mFile);
+ FileOutputStream fileOutputStream = new FileOutputStream(mFileDescriptor);
+ return new FileSynthesisCallback(fileOutputStream.getChannel());
}
@Override
@@ -658,7 +684,7 @@ public abstract class TextToSpeechService extends Service {
}
}
- private class AudioSpeechItem extends SpeechItem {
+ private class AudioSpeechItem extends UtteranceSpeechItem {
private final AudioPlaybackQueueItem mItem;
public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
Bundle params, Uri uri) {
@@ -684,7 +710,7 @@ public abstract class TextToSpeechService extends Service {
}
}
- private class SilenceSpeechItem extends SpeechItem {
+ private class SilenceSpeechItem extends UtteranceSpeechItem {
private final long mDuration;
public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
@@ -711,6 +737,41 @@ public abstract class TextToSpeechService extends Service {
}
}
+ private class LoadLanguageItem extends SpeechItem {
+ private final String mLanguage;
+ private final String mCountry;
+ private final String mVariant;
+
+ public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
+ Bundle params, String language, String country, String variant) {
+ super(callerIdentity, callerUid, callerPid, params);
+ mLanguage = language;
+ mCountry = country;
+ mVariant = variant;
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ protected int playImpl() {
+ int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
+ if (result == TextToSpeech.LANG_AVAILABLE ||
+ result == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
+ result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
+ return TextToSpeech.SUCCESS;
+ }
+ return TextToSpeech.ERROR;
+ }
+
+ @Override
+ protected void stopImpl() {
+ // No-op
+ }
+ }
+
@Override
public IBinder onBind(Intent intent) {
if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
@@ -738,15 +799,15 @@ public abstract class TextToSpeechService extends Service {
}
@Override
- public int synthesizeToFile(IBinder caller, String text, String filename,
- Bundle params) {
- if (!checkNonNull(caller, text, filename, params)) {
+ public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
+ fileDescriptor, Bundle params) {
+ if (!checkNonNull(caller, text, fileDescriptor, params)) {
return TextToSpeech.ERROR;
}
- File file = new File(filename);
- SpeechItem item = new SynthesisToFileSpeechItem(caller, Binder.getCallingUid(),
- Binder.getCallingPid(), params, text, file);
+ SpeechItem item = new SynthesisToFileSpeechDescriptorItem(caller, Binder.getCallingUid(),
+ Binder.getCallingPid(), params, text,
+ fileDescriptor.getFileDescriptor());
return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
}
@@ -791,6 +852,11 @@ public abstract class TextToSpeechService extends Service {
return onGetLanguage();
}
+ @Override
+ public String[] getClientDefaultLanguage() {
+ return getSettingsLocale();
+ }
+
/*
* If defaults are enforced, then no language is "available" except
* perhaps the default language selected by the user.
@@ -822,12 +888,25 @@ public abstract class TextToSpeechService extends Service {
* are enforced.
*/
@Override
- public int loadLanguage(String lang, String country, String variant) {
+ public int loadLanguage(IBinder caller, String lang, String country, String variant) {
if (!checkNonNull(lang)) {
return TextToSpeech.ERROR;
}
+ int retVal = onIsLanguageAvailable(lang, country, variant);
+
+ if (retVal == TextToSpeech.LANG_AVAILABLE ||
+ retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
+ retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
- return onLoadLanguage(lang, country, variant);
+ SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
+ Binder.getCallingPid(), null, lang, country, variant);
+
+ if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
+ TextToSpeech.SUCCESS) {
+ return TextToSpeech.ERROR;
+ }
+ }
+ return retVal;
}
@Override
diff --git a/core/java/android/test/AndroidTestCase.java b/core/java/android/test/AndroidTestCase.java
index 0c8cbe6..0635559 100644
--- a/core/java/android/test/AndroidTestCase.java
+++ b/core/java/android/test/AndroidTestCase.java
@@ -20,9 +20,11 @@ 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 java.lang.reflect.Modifier;
/**
* Extend this if you need to access Resources or other things that depend on Activity Context.
@@ -152,11 +154,11 @@ public class AndroidTestCase extends TestCase {
* @throws IllegalAccessException
*/
protected void scrubClass(final Class<?> testCaseClass)
- throws IllegalAccessException {
+ throws IllegalAccessException {
final Field[] fields = getClass().getDeclaredFields();
for (Field field : fields) {
- final Class<?> fieldClass = field.getDeclaringClass();
- if (testCaseClass.isAssignableFrom(fieldClass) && !field.getType().isPrimitive()) {
+ if (!field.getType().isPrimitive() &&
+ !Modifier.isStatic(field.getModifiers())) {
try {
field.setAccessible(true);
field.set(this, null);
@@ -170,6 +172,4 @@ public class AndroidTestCase extends TestCase {
}
}
}
-
-
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index d909362..122f8a1 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -503,8 +503,15 @@ public class DynamicLayout extends Layout
mNumberOfBlocks = newNumberOfBlocks;
final int deltaLines = newLineCount - (endLine - startLine + 1);
- for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) {
- mBlockEndLines[i] += deltaLines;
+ if (deltaLines != 0) {
+ // Display list whose index is >= mIndexFirstChangedBlock is valid
+ // but it needs to update its drawing location.
+ mIndexFirstChangedBlock = firstBlock + numAddedBlocks;
+ for (int i = mIndexFirstChangedBlock; i < mNumberOfBlocks; i++) {
+ mBlockEndLines[i] += deltaLines;
+ }
+ } else {
+ mIndexFirstChangedBlock = mNumberOfBlocks;
}
int blockIndex = firstBlock;
@@ -559,6 +566,20 @@ public class DynamicLayout extends Layout
return mNumberOfBlocks;
}
+ /**
+ * @hide
+ */
+ public int getIndexFirstChangedBlock() {
+ return mIndexFirstChangedBlock;
+ }
+
+ /**
+ * @hide
+ */
+ public void setIndexFirstChangedBlock(int i) {
+ mIndexFirstChangedBlock = i;
+ }
+
@Override
public int getLineCount() {
return mInts.size() - 1;
@@ -697,6 +718,8 @@ public class DynamicLayout extends Layout
private int[] mBlockIndices;
// Number of items actually currently being used in the above 2 arrays
private int mNumberOfBlocks;
+ // The first index of the blocks whose locations are changed
+ private int mIndexFirstChangedBlock;
private int mTopPadding, mBottomPadding;
diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java
index 831ccc5..60545e5 100644
--- a/core/java/android/text/GraphicsOperations.java
+++ b/core/java/android/text/GraphicsOperations.java
@@ -38,7 +38,7 @@ extends CharSequence
* {@hide}
*/
void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
- float x, float y, int flags, Paint p);
+ float x, float y, Paint p);
/**
* Just like {@link Paint#measureText}.
@@ -55,19 +55,12 @@ extends CharSequence
* @hide
*/
float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
- int flags, float[] advances, int advancesIndex, Paint paint);
-
- /**
- * Just like {@link Paint#getTextRunAdvances}.
- * @hide
- */
- float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
- int flags, float[] advances, int advancesIndex, Paint paint, int reserved);
+ float[] advances, int advancesIndex, Paint paint);
/**
* Just like {@link Paint#getTextRunCursor}.
* @hide
*/
- int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
+ int getTextRunCursor(int contextStart, int contextEnd, int offset,
int cursorOpt, Paint p);
}
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 1aab911..160c630 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -16,6 +16,7 @@
package android.text;
+import android.graphics.Color;
import com.android.internal.util.ArrayUtils;
import org.ccil.cowan.tagsoup.HTMLSchema;
import org.ccil.cowan.tagsoup.Parser;
@@ -168,7 +169,7 @@ public class Html {
for(int j = 0; j < style.length; j++) {
if (style[j] instanceof AlignmentSpan) {
- Layout.Alignment align =
+ Layout.Alignment align =
((AlignmentSpan) style[j]).getAlignment();
needDiv = true;
if (align == Layout.Alignment.ALIGN_CENTER) {
@@ -181,7 +182,7 @@ public class Html {
}
}
if (needDiv) {
- out.append("<div " + elements + ">");
+ out.append("<div ").append(elements).append(">");
}
withinDiv(out, text, i, next);
@@ -199,13 +200,13 @@ public class Html {
next = text.nextSpanTransition(i, end, QuoteSpan.class);
QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class);
- for (QuoteSpan quote: quotes) {
+ for (QuoteSpan quote : quotes) {
out.append("<blockquote>");
}
withinBlockquote(out, text, i, next);
- for (QuoteSpan quote: quotes) {
+ for (QuoteSpan quote : quotes) {
out.append("</blockquote>\n");
}
}
@@ -391,7 +392,7 @@ public class Html {
} else if (c == '&') {
out.append("&amp;");
} else if (c > 0x7E || c < ' ') {
- out.append("&#" + ((int) c) + ";");
+ out.append("&#").append((int) c).append(";");
} else if (c == ' ') {
while (i + 1 < end && text.charAt(i + 1) == ' ') {
out.append("&nbsp;");
@@ -616,8 +617,6 @@ class HtmlToSpannedConverter implements ContentHandler {
if (where != len) {
text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
-
- return;
}
private static void startImg(SpannableStringBuilder text,
@@ -673,7 +672,7 @@ class HtmlToSpannedConverter implements ContentHandler {
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
- int c = getHtmlColor(f.mColor);
+ int c = Color.getHtmlColor(f.mColor);
if (c != -1) {
text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
where, len,
@@ -842,47 +841,4 @@ class HtmlToSpannedConverter implements ContentHandler {
mLevel = level;
}
}
-
- private static HashMap<String,Integer> COLORS = buildColorMap();
-
- private static HashMap<String,Integer> buildColorMap() {
- HashMap<String,Integer> map = new HashMap<String,Integer>();
- map.put("aqua", 0x00FFFF);
- map.put("black", 0x000000);
- map.put("blue", 0x0000FF);
- map.put("fuchsia", 0xFF00FF);
- map.put("green", 0x008000);
- map.put("grey", 0x808080);
- map.put("lime", 0x00FF00);
- map.put("maroon", 0x800000);
- map.put("navy", 0x000080);
- map.put("olive", 0x808000);
- map.put("purple", 0x800080);
- map.put("red", 0xFF0000);
- map.put("silver", 0xC0C0C0);
- map.put("teal", 0x008080);
- map.put("white", 0xFFFFFF);
- map.put("yellow", 0xFFFF00);
- return map;
- }
-
- /**
- * Converts an HTML color (named or numeric) to an integer RGB value.
- *
- * @param color Non-null color string.
- * @return A color value, or {@code -1} if the color string could not be interpreted.
- */
- private static int getHtmlColor(String color) {
- Integer i = COLORS.get(color.toLowerCase());
- if (i != null) {
- return i;
- } else {
- try {
- return XmlUtils.convertValueToInt(color, -1);
- } catch (NumberFormatException nfe) {
- return -1;
- }
- }
- }
-
}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 123acca..a6e8c70 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -792,8 +792,17 @@ public abstract class Layout {
* the paragraph's primary direction.
*/
public float getPrimaryHorizontal(int offset) {
+ return getPrimaryHorizontal(offset, false /* not clamped */);
+ }
+
+ /**
+ * Get the primary horizontal position for the specified text offset, but
+ * optionally clamp it so that it doesn't exceed the width of the layout.
+ * @hide
+ */
+ public float getPrimaryHorizontal(int offset, boolean clamped) {
boolean trailing = primaryIsTrailingPrevious(offset);
- return getHorizontal(offset, trailing);
+ return getHorizontal(offset, trailing, clamped);
}
/**
@@ -802,17 +811,26 @@ public abstract class Layout {
* the direction other than the paragraph's primary direction.
*/
public float getSecondaryHorizontal(int offset) {
+ return getSecondaryHorizontal(offset, false /* not clamped */);
+ }
+
+ /**
+ * Get the secondary horizontal position for the specified text offset, but
+ * optionally clamp it so that it doesn't exceed the width of the layout.
+ * @hide
+ */
+ public float getSecondaryHorizontal(int offset, boolean clamped) {
boolean trailing = primaryIsTrailingPrevious(offset);
- return getHorizontal(offset, !trailing);
+ return getHorizontal(offset, !trailing, clamped);
}
- private float getHorizontal(int offset, boolean trailing) {
+ private float getHorizontal(int offset, boolean trailing, boolean clamped) {
int line = getLineForOffset(offset);
- return getHorizontal(offset, trailing, line);
+ return getHorizontal(offset, trailing, line, clamped);
}
- private float getHorizontal(int offset, boolean trailing, int line) {
+ private float getHorizontal(int offset, boolean trailing, int line, boolean clamped) {
int start = getLineStart(line);
int end = getLineEnd(line);
int dir = getParagraphDirection(line);
@@ -834,6 +852,9 @@ public abstract class Layout {
float wid = tl.measure(offset - start, trailing, null);
TextLine.recycle(tl);
+ if (clamped && wid > mWidth) {
+ wid = mWidth;
+ }
int left = getParagraphLeft(line);
int right = getParagraphRight(line);
@@ -1257,6 +1278,23 @@ public abstract class Layout {
}
/**
+ * Determine whether we should clamp cursor position. Currently it's
+ * only robust for left-aligned displays.
+ * @hide
+ */
+ public boolean shouldClampCursor(int line) {
+ // Only clamp cursor position in left-aligned displays.
+ switch (getParagraphAlignment(line)) {
+ case ALIGN_LEFT:
+ return true;
+ case ALIGN_NORMAL:
+ return getParagraphDirection(line) > 0;
+ default:
+ return false;
+ }
+
+ }
+ /**
* Fills in the specified Path with a representation of a cursor
* at the specified offset. This will often be a vertical line
* but can be multiple discontinuous lines in text with multiple
@@ -1270,8 +1308,9 @@ public abstract class Layout {
int top = getLineTop(line);
int bottom = getLineTop(line+1);
- float h1 = getPrimaryHorizontal(point) - 0.5f;
- float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point) - 0.5f : h1;
+ boolean clamped = shouldClampCursor(line);
+ float h1 = getPrimaryHorizontal(point, clamped) - 0.5f;
+ float h2 = isLevelBoundary(point) ? getSecondaryHorizontal(point, clamped) - 0.5f : h1;
int caps = TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SHIFT_ON) |
TextKeyListener.getMetaState(editingBuffer, TextKeyListener.META_SELECTING);
@@ -1357,8 +1396,8 @@ public abstract class Layout {
int en = Math.min(end, there);
if (st != en) {
- float h1 = getHorizontal(st, false, line);
- float h2 = getHorizontal(en, true, line);
+ float h1 = getHorizontal(st, false, line, false /* not clamped */);
+ float h2 = getHorizontal(en, true, line, false /* not clamped */);
float left = Math.min(h1, h2);
float right = Math.max(h1, h2);
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index bd9310c..0c881a4 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -159,18 +159,15 @@ class MeasuredText {
mPos = p + len;
if (mEasy) {
- int flags = mDir == Layout.DIR_LEFT_TO_RIGHT
- ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
- return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p);
+ return paint.getTextRunAdvances(mChars, p, len, p, len, mWidths, p);
}
float totalAdvance = 0;
int level = mLevels[p];
for (int q = p, i = p + 1, e = p + len;; ++i) {
if (i == e || mLevels[i] != level) {
- int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL;
totalAdvance +=
- paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q);
+ paint.getTextRunAdvances(mChars, q, i - q, q, i - q, mWidths, q);
if (i == e) {
break;
}
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 0f30d25..9e43671 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -1130,20 +1130,20 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
* {@hide}
*/
public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
- float x, float y, int flags, Paint p) {
+ float x, float y, Paint p) {
checkRange("drawTextRun", start, end);
int contextLen = contextEnd - contextStart;
int len = end - start;
if (contextEnd <= mGapStart) {
- c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
+ c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, p);
} else if (contextStart >= mGapStart) {
c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
- contextLen, x, y, flags, p);
+ contextLen, x, y, p);
} else {
char[] buf = TextUtils.obtain(contextLen);
getChars(contextStart, contextEnd, buf, 0);
- c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
+ c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, p);
TextUtils.recycle(buf);
}
}
@@ -1200,7 +1200,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
* Don't call this yourself -- exists for Paint to use internally.
* {@hide}
*/
- public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
+ public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd,
float[] advances, int advancesPos, Paint p) {
float ret;
@@ -1210,44 +1210,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (end <= mGapStart) {
ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
- flags, advances, advancesPos);
+ advances, advancesPos);
} else if (start >= mGapStart) {
ret = p.getTextRunAdvances(mText, start + mGapLength, len,
- contextStart + mGapLength, contextLen, flags, advances, advancesPos);
+ contextStart + mGapLength, contextLen, advances, advancesPos);
} else {
char[] buf = TextUtils.obtain(contextLen);
getChars(contextStart, contextEnd, buf, 0);
ret = p.getTextRunAdvances(buf, start - contextStart, len,
- 0, contextLen, flags, advances, advancesPos);
- TextUtils.recycle(buf);
- }
-
- return ret;
- }
-
- /**
- * Don't call this yourself -- exists for Paint to use internally.
- * {@hide}
- */
- public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
- float[] advances, int advancesPos, Paint p, int reserved) {
-
- float ret;
-
- int contextLen = contextEnd - contextStart;
- int len = end - start;
-
- if (end <= mGapStart) {
- ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
- flags, advances, advancesPos, reserved);
- } else if (start >= mGapStart) {
- ret = p.getTextRunAdvances(mText, start + mGapLength, len,
- contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
- } else {
- char[] buf = TextUtils.obtain(contextLen);
- getChars(contextStart, contextEnd, buf, 0);
- ret = p.getTextRunAdvances(buf, start - contextStart, len,
- 0, contextLen, flags, advances, advancesPos, reserved);
+ 0, contextLen, advances, advancesPos);
TextUtils.recycle(buf);
}
@@ -1270,7 +1241,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
*
* @param contextStart the start index of the context
* @param contextEnd the (non-inclusive) end index of the context
- * @param flags either DIRECTION_RTL or DIRECTION_LTR
+ * @param flags reserved
* @param offset the cursor position to move from
* @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
* CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
@@ -1281,22 +1252,30 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
*/
@Deprecated
public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
- int cursorOpt, Paint p) {
+ int cursorOpt, Paint p) {
+ return getTextRunCursor(contextStart, contextEnd, offset, cursorOpt, p);
+ }
+
+ /**
+ * @hide
+ */
+ public int getTextRunCursor(int contextStart, int contextEnd, int offset,
+ int cursorOpt, Paint p) {
int ret;
int contextLen = contextEnd - contextStart;
if (contextEnd <= mGapStart) {
ret = p.getTextRunCursor(mText, contextStart, contextLen,
- flags, offset, cursorOpt);
+ offset, cursorOpt);
} else if (contextStart >= mGapStart) {
ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
- flags, offset + mGapLength, cursorOpt) - mGapLength;
+ offset + mGapLength, cursorOpt) - mGapLength;
} else {
char[] buf = TextUtils.obtain(contextLen);
getChars(contextStart, contextEnd, buf, 0);
ret = p.getTextRunCursor(buf, 0, contextLen,
- flags, offset - contextStart, cursorOpt) + contextStart;
+ offset - contextStart, cursorOpt) + contextStart;
TextUtils.recycle(buf);
}
diff --git a/core/java/android/text/TextDirectionHeuristic.java b/core/java/android/text/TextDirectionHeuristic.java
index 513e11c..8a4ba42 100644
--- a/core/java/android/text/TextDirectionHeuristic.java
+++ b/core/java/android/text/TextDirectionHeuristic.java
@@ -17,10 +17,30 @@
package android.text;
/**
- * Interface for objects that guess at the paragraph direction by examining text.
- *
- * @hide
+ * Interface for objects that use a heuristic for guessing at the paragraph direction by examining text.
*/
public interface TextDirectionHeuristic {
- boolean isRtl(char[] text, int start, int count);
+ /**
+ * Guess if a chars array is in the RTL direction or not.
+ *
+ * @param array the char array.
+ * @param start start index, inclusive.
+ * @param count the length to check, must not be negative and not greater than
+ * {@code array.length - start}.
+ * @return true if all chars in the range are to be considered in a RTL direction,
+ * false otherwise.
+ */
+ boolean isRtl(char[] array, int start, int count);
+
+ /**
+ * Guess if a {@code CharSequence} is in the RTL direction or not.
+ *
+ * @param cs the CharSequence.
+ * @param start start index, inclusive.
+ * @param count the length to check, must not be negative and not greater than
+ * {@code CharSequence.length() - start}.
+ * @return true if all chars in the range are to be considered in a RTL direction,
+ * false otherwise.
+ */
+ boolean isRtl(CharSequence cs, int start, int count);
}
diff --git a/core/java/android/text/TextDirectionHeuristics.java b/core/java/android/text/TextDirectionHeuristics.java
index df8c4c6..7d7e3a9 100644
--- a/core/java/android/text/TextDirectionHeuristics.java
+++ b/core/java/android/text/TextDirectionHeuristics.java
@@ -19,43 +19,45 @@ package android.text;
import android.view.View;
+import java.nio.CharBuffer;
+
/**
* Some objects that implement TextDirectionHeuristic.
*
- * @hide
*/
public class TextDirectionHeuristics {
- /** Always decides that the direction is left to right. */
+ /**
+ * Always decides that the direction is left to right.
+ */
public static final TextDirectionHeuristic LTR =
new TextDirectionHeuristicInternal(null /* no algorithm */, false);
- /** Always decides that the direction is right to left. */
+ /**
+ * Always decides that the direction is right to left.
+ */
public static final TextDirectionHeuristic RTL =
new TextDirectionHeuristicInternal(null /* no algorithm */, true);
/**
- * Determines the direction based on the first strong directional character,
- * including bidi format chars, falling back to left to right if it
- * finds none. This is the default behavior of the Unicode Bidirectional
- * Algorithm.
+ * Determines the direction based on the first strong directional character, including bidi
+ * format chars, falling back to left to right if it finds none. This is the default behavior
+ * of the Unicode Bidirectional Algorithm.
*/
public static final TextDirectionHeuristic FIRSTSTRONG_LTR =
new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, false);
/**
- * Determines the direction based on the first strong directional character,
- * including bidi format chars, falling back to right to left if it
- * finds none. This is similar to the default behavior of the Unicode
- * Bidirectional Algorithm, just with different fallback behavior.
+ * Determines the direction based on the first strong directional character, including bidi
+ * format chars, falling back to right to left if it finds none. This is similar to the default
+ * behavior of the Unicode Bidirectional Algorithm, just with different fallback behavior.
*/
public static final TextDirectionHeuristic FIRSTSTRONG_RTL =
new TextDirectionHeuristicInternal(FirstStrong.INSTANCE, true);
/**
- * If the text contains any strong right to left non-format character, determines
- * that the direction is right to left, falling back to left to right if it
- * finds none.
+ * If the text contains any strong right to left non-format character, determines that the
+ * direction is right to left, falling back to left to right if it finds none.
*/
public static final TextDirectionHeuristic ANYRTL_LTR =
new TextDirectionHeuristicInternal(AnyStrong.INSTANCE_RTL, false);
@@ -65,8 +67,39 @@ public class TextDirectionHeuristics {
*/
public static final TextDirectionHeuristic LOCALE = TextDirectionHeuristicLocale.INSTANCE;
- private static enum TriState {
- TRUE, FALSE, UNKNOWN;
+ /**
+ * State constants for taking care about true / false / unknown
+ */
+ private static final int STATE_TRUE = 0;
+ private static final int STATE_FALSE = 1;
+ private static final int STATE_UNKNOWN = 2;
+
+ private static int isRtlText(int directionality) {
+ switch (directionality) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ return STATE_FALSE;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ return STATE_TRUE;
+ default:
+ return STATE_UNKNOWN;
+ }
+ }
+
+ private static int isRtlTextOrFormat(int directionality) {
+ switch (directionality) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ return STATE_FALSE;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ return STATE_TRUE;
+ default:
+ return STATE_UNKNOWN;
+ }
}
/**
@@ -87,21 +120,26 @@ public class TextDirectionHeuristics {
abstract protected boolean defaultIsRtl();
@Override
- public boolean isRtl(char[] chars, int start, int count) {
- if (chars == null || start < 0 || count < 0 || chars.length - count < start) {
+ public boolean isRtl(char[] array, int start, int count) {
+ return isRtl(CharBuffer.wrap(array), start, count);
+ }
+
+ @Override
+ public boolean isRtl(CharSequence cs, int start, int count) {
+ if (cs == null || start < 0 || count < 0 || cs.length() - count < start) {
throw new IllegalArgumentException();
}
if (mAlgorithm == null) {
return defaultIsRtl();
}
- return doCheck(chars, start, count);
+ return doCheck(cs, start, count);
}
- private boolean doCheck(char[] chars, int start, int count) {
- switch(mAlgorithm.checkRtl(chars, start, count)) {
- case TRUE:
+ private boolean doCheck(CharSequence cs, int start, int count) {
+ switch(mAlgorithm.checkRtl(cs, start, count)) {
+ case STATE_TRUE:
return true;
- case FALSE:
+ case STATE_FALSE:
return false;
default:
return defaultIsRtl();
@@ -124,58 +162,26 @@ public class TextDirectionHeuristics {
}
}
- private static TriState isRtlText(int directionality) {
- switch (directionality) {
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
- return TriState.FALSE;
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
- return TriState.TRUE;
- default:
- return TriState.UNKNOWN;
- }
- }
-
- private static TriState isRtlTextOrFormat(int directionality) {
- switch (directionality) {
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
- case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
- return TriState.FALSE;
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
- case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
- return TriState.TRUE;
- default:
- return TriState.UNKNOWN;
- }
- }
-
/**
* Interface for an algorithm to guess the direction of a paragraph of text.
- *
*/
private static interface TextDirectionAlgorithm {
/**
* Returns whether the range of text is RTL according to the algorithm.
- *
*/
- TriState checkRtl(char[] text, int start, int count);
+ int checkRtl(CharSequence cs, int start, int count);
}
/**
- * Algorithm that uses the first strong directional character to determine
- * the paragraph direction. This is the standard Unicode Bidirectional
- * algorithm.
- *
+ * Algorithm that uses the first strong directional character to determine the paragraph
+ * direction. This is the standard Unicode Bidirectional algorithm.
*/
private static class FirstStrong implements TextDirectionAlgorithm {
@Override
- public TriState checkRtl(char[] text, int start, int count) {
- TriState result = TriState.UNKNOWN;
- for (int i = start, e = start + count; i < e && result == TriState.UNKNOWN; ++i) {
- result = isRtlTextOrFormat(Character.getDirectionality(text[i]));
+ public int checkRtl(CharSequence cs, int start, int count) {
+ int result = STATE_UNKNOWN;
+ for (int i = start, e = start + count; i < e && result == STATE_UNKNOWN; ++i) {
+ result = isRtlTextOrFormat(Character.getDirectionality(cs.charAt(i)));
}
return result;
}
@@ -190,25 +196,24 @@ public class TextDirectionHeuristics {
* Algorithm that uses the presence of any strong directional non-format
* character (e.g. excludes LRE, LRO, RLE, RLO) to determine the
* direction of text.
- *
*/
private static class AnyStrong implements TextDirectionAlgorithm {
private final boolean mLookForRtl;
@Override
- public TriState checkRtl(char[] text, int start, int count) {
+ public int checkRtl(CharSequence cs, int start, int count) {
boolean haveUnlookedFor = false;
for (int i = start, e = start + count; i < e; ++i) {
- switch (isRtlText(Character.getDirectionality(text[i]))) {
- case TRUE:
+ switch (isRtlText(Character.getDirectionality(cs.charAt(i)))) {
+ case STATE_TRUE:
if (mLookForRtl) {
- return TriState.TRUE;
+ return STATE_TRUE;
}
haveUnlookedFor = true;
break;
- case FALSE:
+ case STATE_FALSE:
if (!mLookForRtl) {
- return TriState.FALSE;
+ return STATE_FALSE;
}
haveUnlookedFor = true;
break;
@@ -217,9 +222,9 @@ public class TextDirectionHeuristics {
}
}
if (haveUnlookedFor) {
- return mLookForRtl ? TriState.FALSE : TriState.TRUE;
+ return mLookForRtl ? STATE_FALSE : STATE_TRUE;
}
- return TriState.UNKNOWN;
+ return STATE_UNKNOWN;
}
private AnyStrong(boolean lookForRtl) {
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1fecf81..e34a0ef 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -664,14 +664,13 @@ class TextLine {
}
}
- int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE;
if (mCharsValid) {
return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart,
- flags, offset, cursorOpt);
+ offset, cursorOpt);
} else {
return wp.getTextRunCursor(mText, mStart + spanStart,
- mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart;
+ mStart + spanLimit, mStart + offset, cursorOpt) - mStart;
}
}
@@ -738,15 +737,13 @@ class TextLine {
int contextLen = contextEnd - contextStart;
if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) {
- int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR;
if (mCharsValid) {
ret = wp.getTextRunAdvances(mChars, start, runLen,
- contextStart, contextLen, flags, null, 0);
+ contextStart, contextLen, null, 0);
} else {
int delta = mStart;
- ret = wp.getTextRunAdvances(mText, delta + start,
- delta + end, delta + contextStart, delta + contextEnd,
- flags, null, 0);
+ ret = wp.getTextRunAdvances(mText, delta + start, delta + end,
+ delta + contextStart, delta + contextEnd, null, 0);
}
}
@@ -786,8 +783,7 @@ class TextLine {
wp.setAntiAlias(previousAntiAlias);
}
- drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl,
- x, y + wp.baselineShift);
+ drawTextRun(c, wp, start, end, contextStart, contextEnd, x, y + wp.baselineShift);
}
return runIsRtl ? -ret : ret;
@@ -970,23 +966,21 @@ class TextLine {
* @param end the end of the run
* @param contextStart the start of context for the run
* @param contextEnd the end of the context for the run
- * @param runIsRtl true if the run is right-to-left
* @param x the x position of the left edge of the run
* @param y the baseline of the run
*/
private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
- int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
+ int contextStart, int contextEnd, float x, int y) {
- int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
if (mCharsValid) {
int count = end - start;
int contextCount = contextEnd - contextStart;
c.drawTextRun(mChars, start, count, contextStart, contextCount,
- x, y, flags, wp);
+ x, y, wp);
} else {
int delta = mStart;
c.drawTextRun(mText, delta + start, delta + end,
- delta + contextStart, delta + contextEnd, x, y, flags, wp);
+ delta + contextStart, delta + contextEnd, x, y, wp);
}
}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 1508d10..2ab9bf8 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -757,7 +757,7 @@ public class TextUtils {
break;
case EASY_EDIT_SPAN:
- readSpan(p, sp, new EasyEditSpan());
+ readSpan(p, sp, new EasyEditSpan(p));
break;
case LOCALE_SPAN:
diff --git a/core/java/android/text/bidi/BidiFormatter.java b/core/java/android/text/bidi/BidiFormatter.java
new file mode 100644
index 0000000..370cbf7
--- /dev/null
+++ b/core/java/android/text/bidi/BidiFormatter.java
@@ -0,0 +1,1123 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.bidi;
+
+import android.text.TextDirectionHeuristic;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.view.View;
+
+import static android.text.TextDirectionHeuristics.FIRSTSTRONG_LTR;
+
+import java.util.Locale;
+
+
+/**
+ * Utility class for formatting text for display in a potentially opposite-directionality context
+ * without garbling. The directionality of the context is set at formatter creation and the
+ * directionality of the text can be either estimated or passed in when known. Provides the
+ * following functionality:
+ * <p>
+ * 1. Bidi Wrapping
+ * When text in one language is mixed into a document in another, opposite-directionality language,
+ * e.g. when an English business name is embedded in a Hebrew web page, both the inserted string
+ * and the text surrounding it may be displayed incorrectly unless the inserted string is explicitly
+ * separated from the surrounding text in a "wrapper" that:
+ * <p>
+ * - Declares its directionality so that the string is displayed correctly. This can be done in HTML
+ * markup (e.g. a 'span dir="rtl"' element) by {@link #spanWrap} and similar methods, or - only in
+ * contexts where markup can't be used - in Unicode bidi formatting codes by {@link #unicodeWrap}
+ * and similar methods.
+ * <p>
+ * - Isolates the string's directionality, so it does not unduly affect the surrounding content.
+ * Currently, this can only be done using invisible Unicode characters of the same direction as
+ * the context (LRM or RLM) in addition to the directionality declaration above, thus "resetting"
+ * the directionality to that of the context. The "reset" may need to be done at both ends of the
+ * string. Without "reset" after the string, the string will "stick" to a number or logically
+ * separate opposite-direction text that happens to follow it in-line (even if separated by
+ * neutral content like spaces and punctuation). Without "reset" before the string, the same can
+ * happen there, but only with more opposite-direction text, not a number. One approach is to
+ * "reset" the direction only after each string, on the theory that if the preceding opposite-
+ * direction text is itself bidi-wrapped, the "reset" after it will prevent the sticking. (Doing
+ * the "reset" only before each string definitely does not work because we do not want to require
+ * bidi-wrapping numbers, and a bidi-wrapped opposite-direction string could be followed by a
+ * number.) Still, the safest policy is to do the "reset" on both ends of each string, since RTL
+ * message translations often contain untranslated Latin-script brand names and technical terms,
+ * and one of these can be followed by a bidi-wrapped inserted value. On the other hand, when one
+ * has such a message, it is best to do the "reset" manually in the message translation itself,
+ * since the message's opposite-direction text could be followed by an inserted number, which we
+ * would not bidi-wrap anyway. Thus, "reset" only after the string is the current default. In an
+ * alternative to "reset", recent additions to the HTML, CSS, and Unicode standards allow the
+ * isolation to be part of the directionality declaration. This form of isolation is better than
+ * "reset" because it takes less space, does not require knowing the context directionality, has a
+ * gentler effect than "reset", and protects both ends of the string. However, we do not yet allow
+ * using it because required platforms do not yet support it.
+ * <p>
+ * Providing these wrapping services is the basic purpose of the bidi formatter.
+ * <p>
+ * 2. Directionality estimation
+ * How does one know whether a string about to be inserted into surrounding text has the same
+ * directionality? Well, in many cases, one knows that this must be the case when writing the code
+ * doing the insertion, e.g. when a localized message is inserted into a localized page. In such
+ * cases there is no need to involve the bidi formatter at all. In some other cases, it need not be
+ * the same as the context, but is either constant (e.g. urls are always LTR) or otherwise known.
+ * In the remaining cases, e.g. when the string is user-entered or comes from a database, the
+ * language of the string (and thus its directionality) is not known a priori, and must be
+ * estimated at run-time. The bidi formatter can do this automatically using the default
+ * first-strong estimation algorithm. It can also be configured to use a custom directionality
+ * estimation object.
+ * <p>
+ * 3. Escaping
+ * When wrapping plain text - i.e. text that is not already HTML or HTML-escaped - in HTML markup,
+ * the text must first be HTML-escaped to prevent XSS attacks and other nasty business. This of
+ * course is always true, but the escaping can not be done after the string has already been wrapped
+ * in markup, so the bidi formatter also serves as a last chance and includes escaping services.
+ * <p>
+ * Thus, in a single call, the formatter will escape the input string as specified, determine its
+ * directionality, and wrap it as necessary. It is then up to the caller to insert the return value
+ * in the output.
+ */
+public final class BidiFormatter {
+
+ /**
+ * The default text direction heuristic.
+ */
+ private static TextDirectionHeuristic DEFAULT_TEXT_DIRECTION_HEURISTIC = FIRSTSTRONG_LTR;
+
+ /**
+ * Unicode "Left-To-Right Embedding" (LRE) character.
+ */
+ private static final char LRE = '\u202A';
+
+ /**
+ * Unicode "Right-To-Left Embedding" (RLE) character.
+ */
+ private static final char RLE = '\u202B';
+
+ /**
+ * Unicode "Pop Directional Formatting" (PDF) character.
+ */
+ private static final char PDF = '\u202C';
+
+ /**
+ * Unicode "Left-To-Right Mark" (LRM) character.
+ */
+ private static final char LRM = '\u200E';
+
+ /*
+ * Unicode "Right-To-Left Mark" (RLM) character.
+ */
+ private static final char RLM = '\u200F';
+
+ /*
+ * String representation of LRM
+ */
+ private static final String LRM_STRING = Character.toString(LRM);
+
+ /*
+ * String representation of RLM
+ */
+ private static final String RLM_STRING = Character.toString(RLM);
+
+ /**
+ * "ltr" string constant.
+ */
+ private static final String LTR_STRING = "ltr";
+
+ /**
+ * "rtl" string constant.
+ */
+ private static final String RTL_STRING = "rtl";
+
+ /**
+ * "dir=\"ltr\"" string constant.
+ */
+ private static final String DIR_LTR_STRING = "dir=\"ltr\"";
+
+ /**
+ * "dir=\"rtl\"" string constant.
+ */
+ private static final String DIR_RTL_STRING = "dir=\"rtl\"";
+
+ /**
+ * "right" string constant.
+ */
+ private static final String RIGHT = "right";
+
+ /**
+ * "left" string constant.
+ */
+ private static final String LEFT = "left";
+
+ /**
+ * Empty string constant.
+ */
+ private static final String EMPTY_STRING = "";
+
+ /**
+ * A class for building a BidiFormatter with non-default options.
+ */
+ public static final class Builder {
+ private boolean isRtlContext;
+ private int flags;
+ private TextDirectionHeuristic textDirectionHeuristic;
+
+ /**
+ * Constructor.
+ *
+ */
+ public Builder() {
+ initialize(isRtlLocale(Locale.getDefault()));
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param rtlContext Whether the context directionality is RTL.
+ */
+ public Builder(boolean rtlContext) {
+ initialize(rtlContext);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param locale The context locale.
+ */
+ public Builder(Locale locale) {
+ initialize(isRtlLocale(locale));
+ }
+
+ /**
+ * Initializes the builder with the given context directionality and default options.
+ *
+ * @param isRtlContext Whether the context is RTL or not.
+ */
+ private void initialize(boolean isRtlContext) {
+ this.isRtlContext = isRtlContext;
+ textDirectionHeuristic = DEFAULT_TEXT_DIRECTION_HEURISTIC;
+ this.flags = DEFAULT_FLAGS;
+ }
+
+ /**
+ * Specifies whether the BidiFormatter to be built should also "reset" directionality before
+ * a string being bidi-wrapped, not just after it. The default is false.
+ */
+ public Builder stereoReset(boolean stereoReset) {
+ if (stereoReset) {
+ flags |= FLAG_STEREO_RESET;
+ } else {
+ flags &= ~FLAG_STEREO_RESET;
+ }
+ return this;
+ }
+
+ /**
+ * Specifies the default directionality estimation algorithm to be used by the BidiFormatter.
+ * By default, uses the first-strong heuristic.
+ *
+ * @param heuristic the {@code TextDirectionHeuristic} to use.
+ * @return the builder itself.
+ */
+ public Builder setTextDirectionHeuristic(TextDirectionHeuristic heuristic) {
+ this.textDirectionHeuristic = heuristic;
+ return this;
+ }
+
+ private static BidiFormatter getDefaultInstanceFromContext(boolean isRtlContext) {
+ return isRtlContext ? DEFAULT_RTL_INSTANCE : DEFAULT_LTR_INSTANCE;
+ }
+
+ /**
+ * @return A BidiFormatter with the specified options.
+ */
+ public BidiFormatter build() {
+ if (flags == DEFAULT_FLAGS &&
+ textDirectionHeuristic == DEFAULT_TEXT_DIRECTION_HEURISTIC) {
+ return getDefaultInstanceFromContext(isRtlContext);
+ }
+ return new BidiFormatter(isRtlContext, flags, textDirectionHeuristic);
+ }
+ }
+
+ //
+ private static final int FLAG_STEREO_RESET = 2;
+ private static final int DEFAULT_FLAGS = FLAG_STEREO_RESET;
+
+ private static final BidiFormatter DEFAULT_LTR_INSTANCE = new BidiFormatter(
+ false /* LTR context */,
+ DEFAULT_FLAGS,
+ DEFAULT_TEXT_DIRECTION_HEURISTIC);
+
+ private static final BidiFormatter DEFAULT_RTL_INSTANCE = new BidiFormatter(
+ true /* RTL context */,
+ DEFAULT_FLAGS,
+ DEFAULT_TEXT_DIRECTION_HEURISTIC);
+
+ private final boolean isRtlContext;
+ private final int flags;
+ private final TextDirectionHeuristic defaultTextDirectionHeuristic;
+
+ /**
+ * Factory for creating an instance of BidiFormatter given the context directionality.
+ *
+ * @param rtlContext Whether the context directionality is RTL.
+ */
+ public static BidiFormatter getInstance(boolean rtlContext) {
+ return new Builder(rtlContext).build();
+ }
+
+ /**
+ * Factory for creating an instance of BidiFormatter given the context locale.
+ *
+ * @param locale The context locale.
+ */
+ public static BidiFormatter getInstance(Locale locale) {
+ return new Builder(locale).build();
+ }
+
+ /**
+ * @param isRtlContext Whether the context directionality is RTL or not.
+ * @param flags The option flags.
+ * @param heuristic The default text direction heuristic.
+ */
+ private BidiFormatter(boolean isRtlContext, int flags, TextDirectionHeuristic heuristic) {
+ this.isRtlContext = isRtlContext;
+ this.flags = flags;
+ this.defaultTextDirectionHeuristic = heuristic;
+ }
+
+ /**
+ * @return Whether the context directionality is RTL
+ */
+ public boolean isRtlContext() {
+ return isRtlContext;
+ }
+
+ /**
+ * @return Whether directionality "reset" should also be done before a string being
+ * bidi-wrapped, not just after it.
+ */
+ public boolean getStereoReset() {
+ return (flags & FLAG_STEREO_RESET) != 0;
+ }
+
+ /**
+ * Returns "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" if it is LTR.
+ *
+ * @param str String whose directionality is to be estimated.
+ * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise.
+ */
+ public String dirAttrValue(String str) {
+ return dirAttrValue(isRtl(str));
+ }
+
+ /**
+ * Operates like {@link #dirAttrValue(String)}, but uses a given heuristic to estimate the
+ * {@code str}'s directionality.
+ *
+ * @param str String whose directionality is to be estimated.
+ * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
+ * directionality.
+ * @return "rtl" if {@code str}'s estimated directionality is RTL, and "ltr" otherwise.
+ */
+ public String dirAttrValue(String str, TextDirectionHeuristic heuristic) {
+ return dirAttrValue(heuristic.isRtl(str, 0, str.length()));
+ }
+
+ /**
+ * Returns "rtl" if the given directionality is RTL, and "ltr" if it is LTR.
+ *
+ * @param isRtl Whether the directionality is RTL or not.
+ * @return "rtl" if the given directionality is RTL, and "ltr" otherwise.
+ */
+ public String dirAttrValue(boolean isRtl) {
+ return isRtl ? RTL_STRING : LTR_STRING;
+ }
+
+ /**
+ * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on {@code str}'s estimated directionality,
+ * if it is not the same as the context directionality. Otherwise, returns the empty string.
+ *
+ * @param str String whose directionality is to be estimated.
+ * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
+ * context; else, the empty string.
+ */
+ public String dirAttr(String str) {
+ return dirAttr(isRtl(str));
+ }
+
+ /**
+ * Operates like {@link #dirAttr(String)}, but uses a given heuristic to estimate the
+ * {@code str}'s directionality.
+ *
+ * @param str String whose directionality is to be estimated.
+ * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
+ * directionality.
+ * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
+ * context; else, the empty string.
+ */
+ public String dirAttr(String str, TextDirectionHeuristic heuristic) {
+ return dirAttr(heuristic.isRtl(str, 0, str.length()));
+ }
+
+ /**
+ * Returns "dir=\"ltr\"" or "dir=\"rtl\"", depending on the given directionality, if it is not
+ * the same as the context directionality. Otherwise, returns the empty string.
+ *
+ * @param isRtl Whether the directionality is RTL or not
+ * @return "dir=\"rtl\"" for RTL text in non-RTL context; "dir=\"ltr\"" for LTR text in non-LTR
+ * context; else, the empty string.
+ */
+ public String dirAttr(boolean isRtl) {
+ return (isRtl != isRtlContext) ? (isRtl ? DIR_RTL_STRING : DIR_LTR_STRING) : EMPTY_STRING;
+ }
+
+ /**
+ * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
+ * overall or the exit directionality of a given string is opposite to the context directionality.
+ * Putting this after the string (including its directionality declaration wrapping) prevents it
+ * from "sticking" to other opposite-directionality text or a number appearing after it inline
+ * with only neutral content in between. Otherwise returns the empty string. While the exit
+ * directionality is determined by scanning the end of the string, the overall directionality is
+ * given explicitly in {@code dir}.
+ *
+ * @param str String after which the mark may need to appear.
+ * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
+ * else, the empty string.
+ */
+ public String markAfter(String str) {
+ return markAfter(str, defaultTextDirectionHeuristic);
+ }
+
+ /**
+ * Operates like {@link #markAfter(String)}, but uses a given heuristic to estimate the
+ * {@code str}'s directionality.
+ *
+ * @param str String after which the mark may need to appear.
+ * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
+ * directionality.
+ * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
+ * else, the empty string.
+ */
+ public String markAfter(String str, TextDirectionHeuristic heuristic) {
+ final boolean isRtl = heuristic.isRtl(str, 0, str.length());
+ // getExitDir() is called only if needed (short-circuit).
+ if (!isRtlContext && (isRtl || getExitDir(str) == DIR_RTL)) {
+ return LRM_STRING;
+ }
+ if (isRtlContext && (!isRtl || getExitDir(str) == DIR_LTR)) {
+ return RLM_STRING;
+ }
+ return EMPTY_STRING;
+ }
+
+ /**
+ * Returns a Unicode bidi mark matching the context directionality (LRM or RLM) if either the
+ * overall or the entry directionality of a given string is opposite to the context
+ * directionality. Putting this before the string (including its directionality declaration
+ * wrapping) prevents it from "sticking" to other opposite-directionality text appearing before it
+ * inline with only neutral content in between. Otherwise returns the empty string. While the
+ * entry directionality is determined by scanning the beginning of the string, the overall
+ * directionality is given explicitly in {@code dir}.
+ *
+ * @param str String before which the mark may need to appear.
+ * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
+ * else, the empty string.
+ */
+ public String markBefore(String str) {
+ return markBefore(str, defaultTextDirectionHeuristic);
+ }
+
+ /**
+ * Operates like {@link #markBefore(String)}, but uses a given heuristic to estimate the
+ * {@code str}'s directionality.
+ *
+ * @param str String before which the mark may need to appear.
+ * @param heuristic The text direction heuristic that will be used to estimate the {@code str}'s
+ * directionality.
+ * @return LRM for RTL text in LTR context; RLM for LTR text in RTL context;
+ * else, the empty string.
+ */
+ public String markBefore(String str, TextDirectionHeuristic heuristic) {
+ final boolean isRtl = heuristic.isRtl(str, 0, str.length());
+ // getEntryDir() is called only if needed (short-circuit).
+ if (!isRtlContext && (isRtl || getEntryDir(str) == DIR_RTL)) {
+ return LRM_STRING;
+ }
+ if (isRtlContext && (!isRtl || getEntryDir(str) == DIR_LTR)) {
+ return RLM_STRING;
+ }
+ return EMPTY_STRING;
+ }
+
+ /**
+ * Returns the Unicode bidi mark matching the context directionality (LRM for LTR context
+ * directionality, RLM for RTL context directionality).
+ */
+ public String mark() {
+ return isRtlContext ? RLM_STRING : LRM_STRING;
+ }
+
+ /**
+ * Returns "right" for RTL context directionality. Otherwise for LTR context directionality
+ * returns "left".
+ */
+ public String startEdge() {
+ return isRtlContext ? RIGHT : LEFT;
+ }
+
+ /**
+ * Returns "left" for RTL context directionality. Otherwise for LTR context directionality
+ * returns "right".
+ */
+ public String endEdge() {
+ return isRtlContext ? LEFT : RIGHT;
+ }
+
+ /**
+ * Estimates the directionality of a string using the default text direction heuristic.
+ *
+ * @param str String whose directionality is to be estimated.
+ * @return true if {@code str}'s estimated overall directionality is RTL. Otherwise returns
+ * false.
+ */
+ public boolean isRtl(String str) {
+ return defaultTextDirectionHeuristic.isRtl(str, 0, str.length());
+ }
+
+ /**
+ * Formats a given string of unknown directionality for use in HTML output of the context
+ * directionality, so an opposite-directionality string is neither garbled nor garbles its
+ * surroundings.
+ * <p>
+ * The algorithm: estimates the directionality of the given string using the given heuristic.
+ * If the directionality is known, pass TextDirectionHeuristics.LTR or RTL for heuristic.
+ * In case its directionality doesn't match the context directionality, wraps it with a 'span'
+ * element and adds a "dir" attribute (either 'dir=\"rtl\"' or 'dir=\"ltr\"').
+ * <p>
+ * If {@code isolate}, directionally isolates the string so that it does not garble its
+ * surroundings. Currently, this is done by "resetting" the directionality after the string by
+ * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
+ * either the overall directionality or the exit directionality of the string is opposite to that
+ * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
+ * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
+ * directionality when either the overall directionality or the entry directionality of the
+ * string is opposite to that of the context.
+ * <p>
+ *
+ * @param str The input string.
+ * @param heuristic The algorithm to be used to estimate the string's overall direction.
+ * @param isolate Whether to directionally isolate the string to prevent it from garbling the
+ * content around it.
+ * @return Input string after applying the above processing.
+ */
+ public String spanWrap(String str, TextDirectionHeuristic heuristic, boolean isolate) {
+ final boolean isRtl = heuristic.isRtl(str, 0, str.length());
+ String origStr = str;
+ str = TextUtils.htmlEncode(str);
+
+ StringBuilder result = new StringBuilder();
+ if (getStereoReset() && isolate) {
+ result.append(markBefore(origStr,
+ isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
+ }
+ if (isRtl != isRtlContext) {
+ result.append("<span ").append(dirAttr(isRtl)).append('>').append(str).append("</span>");
+ } else {
+ result.append(str);
+ }
+ if (isolate) {
+ result.append(markAfter(origStr,
+ isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Operates like {@link #spanWrap(String, TextDirectionHeuristic, boolean)}, but assumes
+ * {@code isolate} is true.
+ *
+ * @param str The input string.
+ * @param heuristic The algorithm to be used to estimate the string's overall direction.
+ * @return Input string after applying the above processing.
+ */
+ public String spanWrap(String str, TextDirectionHeuristic heuristic) {
+ return spanWrap(str, heuristic, true /* isolate */);
+ }
+
+ /**
+ * Operates like {@link #spanWrap(String, TextDirectionHeuristic, boolean)}, but uses the
+ * formatter's default direction estimation algorithm.
+ *
+ * @param str The input string.
+ * @param isolate Whether to directionally isolate the string to prevent it from garbling the
+ * content around it
+ * @return Input string after applying the above processing.
+ */
+ public String spanWrap(String str, boolean isolate) {
+ return spanWrap(str, defaultTextDirectionHeuristic, isolate);
+ }
+
+ /**
+ * Operates like {@link #spanWrap(String, TextDirectionHeuristic, boolean)}, but uses the
+ * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
+ *
+ * @param str The input string.
+ * @return Input string after applying the above processing.
+ */
+ public String spanWrap(String str) {
+ return spanWrap(str, defaultTextDirectionHeuristic, true /* isolate */);
+ }
+
+ /**
+ * Formats a string of given directionality for use in plain-text output of the context
+ * directionality, so an opposite-directionality string is neither garbled nor garbles its
+ * surroundings. As opposed to {@link #spanWrap}, this makes use of Unicode bidi
+ * formatting characters. In HTML, its *only* valid use is inside of elements that do not allow
+ * markup, e.g. the 'option' and 'title' elements.
+ * <p>
+ * The algorithm: In case the given directionality doesn't match the context directionality, wraps
+ * the string with Unicode bidi formatting characters: RLE+{@code str}+PDF for RTL text, or
+ * LRE+{@code str}+PDF for LTR text.
+ * <p>
+ * If {@code isolate}, directionally isolates the string so that it does not garble its
+ * surroundings. Currently, this is done by "resetting" the directionality after the string by
+ * appending a trailing Unicode bidi mark matching the context directionality (LRM or RLM) when
+ * either the overall directionality or the exit directionality of the string is opposite to that
+ * of the context. If the formatter was built using {@link Builder#stereoReset(boolean)} and
+ * passing "true" as an argument, also prepends a Unicode bidi mark matching the context
+ * directionality when either the overall directionality or the entry directionality of the
+ * string is opposite to that of the context. Note that as opposed to the overall
+ * directionality, the entry and exit directionalities are determined from the string itself.
+ * <p>
+ * Does *not* do HTML-escaping.
+ *
+ * @param str The input string.
+ * @param heuristic The algorithm to be used to estimate the string's overall direction.
+ * @param isolate Whether to directionally isolate the string to prevent it from garbling the
+ * content around it
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str, TextDirectionHeuristic heuristic, boolean isolate) {
+ final boolean isRtl = heuristic.isRtl(str, 0, str.length());
+ StringBuilder result = new StringBuilder();
+ if (getStereoReset() && isolate) {
+ result.append(markBefore(str,
+ isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
+ }
+ if (isRtl != isRtlContext) {
+ result.append(isRtl ? RLE : LRE);
+ result.append(str);
+ result.append(PDF);
+ } else {
+ result.append(str);
+ }
+ if (isolate) {
+ result.append(markAfter(str,
+ isRtl ? TextDirectionHeuristics.RTL : TextDirectionHeuristics.LTR));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but assumes
+ * {@code isolate} is true.
+ *
+ * @param str The input string.
+ * @param heuristic The algorithm to be used to estimate the string's overall direction.
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str, TextDirectionHeuristic heuristic) {
+ return unicodeWrap(str, heuristic, true /* isolate */);
+ }
+
+ /**
+ * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
+ * formatter's default direction estimation algorithm.
+ *
+ * @param str The input string.
+ * @param isolate Whether to directionally isolate the string to prevent it from garbling the
+ * content around it
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str, boolean isolate) {
+ return unicodeWrap(str, defaultTextDirectionHeuristic, isolate);
+ }
+
+ /**
+ * Operates like {@link #unicodeWrap(String, TextDirectionHeuristic, boolean)}, but uses the
+ * formatter's default direction estimation algorithm and assumes {@code isolate} is true.
+ *
+ * @param str The input string.
+ * @return Input string after applying the above processing.
+ */
+ public String unicodeWrap(String str) {
+ return unicodeWrap(str, defaultTextDirectionHeuristic, true /* isolate */);
+ }
+
+ /**
+ * Helper method to return true if the Locale directionality is RTL.
+ *
+ * @param locale The Locale whose directionality will be checked to be RTL or LTR
+ * @return true if the {@code locale} directionality is RTL. False otherwise.
+ */
+ private static boolean isRtlLocale(Locale locale) {
+ return (TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ /**
+ * Enum for directionality type.
+ */
+ private static final int DIR_LTR = -1;
+ private static final int DIR_UNKNOWN = 0;
+ private static final int DIR_RTL = +1;
+
+ /**
+ * Returns the directionality of the last character with strong directionality in the string, or
+ * DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards from the end of
+ * the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its matching PDF as a
+ * strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results are undefined for a
+ * string containing unbalanced LRE/RLE/LRO/RLO/PDF characters. The intended use is to check
+ * whether a logically separate item that starts with a number or a character of the string's
+ * exit directionality and follows this string inline (not counting any neutral characters in
+ * between) would "stick" to it in an opposite-directionality context, thus being displayed in
+ * an incorrect position. An LRM or RLM character (the one of the context's directionality)
+ * between the two will prevent such sticking.
+ *
+ * @param str the string to check.
+ */
+ private static int getExitDir(String str) {
+ return new DirectionalityEstimator(str, false /* isHtml */).getExitDir();
+ }
+
+ /**
+ * Returns the directionality of the first character with strong directionality in the string,
+ * or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
+ * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL after
+ * RLE/RLO. The results are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF
+ * characters. The intended use is to check whether a logically separate item that ends with a
+ * character of the string's entry directionality and precedes the string inline (not counting
+ * any neutral characters in between) would "stick" to it in an opposite-directionality context,
+ * thus being displayed in an incorrect position. An LRM or RLM character (the one of the
+ * context's directionality) between the two will prevent such sticking.
+ *
+ * @param str the string to check.
+ */
+ private static int getEntryDir(String str) {
+ return new DirectionalityEstimator(str, false /* isHtml */).getEntryDir();
+ }
+
+ /**
+ * An object that estimates the directionality of a given string by various methods.
+ *
+ */
+ private static class DirectionalityEstimator {
+
+ // Internal static variables and constants.
+
+ /**
+ * Size of the bidi character class cache. The results of the Character.getDirectionality()
+ * calls on the lowest DIR_TYPE_CACHE_SIZE codepoints are kept in an array for speed.
+ * The 0x700 value is designed to leave all the European and Near Eastern languages in the
+ * cache. It can be reduced to 0x180, restricting the cache to the Western European
+ * languages.
+ */
+ private static final int DIR_TYPE_CACHE_SIZE = 0x700;
+
+ /**
+ * The bidi character class cache.
+ */
+ private static final byte DIR_TYPE_CACHE[];
+
+ static {
+ DIR_TYPE_CACHE = new byte[DIR_TYPE_CACHE_SIZE];
+ for (int i = 0; i < DIR_TYPE_CACHE_SIZE; i++) {
+ DIR_TYPE_CACHE[i] = Character.getDirectionality(i);
+ }
+ }
+
+ // Internal instance variables.
+
+ /**
+ * The text to be scanned.
+ */
+ private final String text;
+
+ /**
+ * Whether the text to be scanned is to be treated as HTML, i.e. skipping over tags and
+ * entities when looking for the next / preceding dir type.
+ */
+ private final boolean isHtml;
+
+ /**
+ * The length of the text in chars.
+ */
+ private final int length;
+
+ /**
+ * The current position in the text.
+ */
+ private int charIndex;
+
+ /**
+ * The char encountered by the last dirTypeForward or dirTypeBackward call. If it
+ * encountered a supplementary codepoint, this contains a char that is not a valid
+ * codepoint. This is ok, because this member is only used to detect some well-known ASCII
+ * syntax, e.g. "http://" and the beginning of an HTML tag or entity.
+ */
+ private char lastChar;
+
+ /**
+ * Constructor.
+ *
+ * @param text The string to scan.
+ * @param isHtml Whether the text to be scanned is to be treated as HTML, i.e. skipping over
+ * tags and entities.
+ */
+ DirectionalityEstimator(String text, boolean isHtml) {
+ this.text = text;
+ this.isHtml = isHtml;
+ length = text.length();
+ }
+
+ /**
+ * Returns the directionality of the first character with strong directionality in the
+ * string, or DIR_UNKNOWN if none was encountered. Treats a non-BN character between an
+ * LRE/RLE/LRO/RLO and its matching PDF as a strong character, LTR after LRE/LRO, and RTL
+ * after RLE/RLO. The results are undefined for a string containing unbalanced
+ * LRE/RLE/LRO/RLO/PDF characters.
+ */
+ int getEntryDir() {
+ // The reason for this method name, as opposed to getFirstStrongDir(), is that
+ // "first strong" is a commonly used description of Unicode's estimation algorithm,
+ // but the two must treat formatting characters quite differently. Thus, we are staying
+ // away from both "first" and "last" in these method names to avoid confusion.
+ charIndex = 0;
+ int embeddingLevel = 0;
+ int embeddingLevelDir = DIR_UNKNOWN;
+ int firstNonEmptyEmbeddingLevel = 0;
+ while (charIndex < length && firstNonEmptyEmbeddingLevel == 0) {
+ switch (dirTypeForward()) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ ++embeddingLevel;
+ embeddingLevelDir = DIR_LTR;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ ++embeddingLevel;
+ embeddingLevelDir = DIR_RTL;
+ break;
+ case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
+ --embeddingLevel;
+ // To restore embeddingLevelDir to its previous value, we would need a
+ // stack, which we want to avoid. Thus, at this point we do not know the
+ // current embedding's directionality.
+ embeddingLevelDir = DIR_UNKNOWN;
+ break;
+ case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
+ break;
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ if (embeddingLevel == 0) {
+ return DIR_LTR;
+ }
+ firstNonEmptyEmbeddingLevel = embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ if (embeddingLevel == 0) {
+ return DIR_RTL;
+ }
+ firstNonEmptyEmbeddingLevel = embeddingLevel;
+ break;
+ default:
+ firstNonEmptyEmbeddingLevel = embeddingLevel;
+ break;
+ }
+ }
+
+ // We have either found a non-empty embedding or scanned the entire string finding
+ // neither a non-empty embedding nor a strong character outside of an embedding.
+ if (firstNonEmptyEmbeddingLevel == 0) {
+ // We have not found a non-empty embedding. Thus, the string contains neither a
+ // non-empty embedding nor a strong character outside of an embedding.
+ return DIR_UNKNOWN;
+ }
+
+ // We have found a non-empty embedding.
+ if (embeddingLevelDir != DIR_UNKNOWN) {
+ // We know the directionality of the non-empty embedding.
+ return embeddingLevelDir;
+ }
+
+ // We do not remember the directionality of the non-empty embedding we found. So, we go
+ // backwards to find the start of the non-empty embedding and get its directionality.
+ while (charIndex > 0) {
+ switch (dirTypeBackward()) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_LTR;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ if (firstNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_RTL;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
+ ++embeddingLevel;
+ break;
+ }
+ }
+ // We should never get here.
+ return DIR_UNKNOWN;
+ }
+
+ /**
+ * Returns the directionality of the last character with strong directionality in the
+ * string, or DIR_UNKNOWN if none was encountered. For efficiency, actually scans backwards
+ * from the end of the string. Treats a non-BN character between an LRE/RLE/LRO/RLO and its
+ * matching PDF as a strong character, LTR after LRE/LRO, and RTL after RLE/RLO. The results
+ * are undefined for a string containing unbalanced LRE/RLE/LRO/RLO/PDF characters.
+ */
+ int getExitDir() {
+ // The reason for this method name, as opposed to getLastStrongDir(), is that "last
+ // strong" sounds like the exact opposite of "first strong", which is a commonly used
+ // description of Unicode's estimation algorithm (getUnicodeDir() above), but the two
+ // must treat formatting characters quite differently. Thus, we are staying away from
+ // both "first" and "last" in these method names to avoid confusion.
+ charIndex = length;
+ int embeddingLevel = 0;
+ int lastNonEmptyEmbeddingLevel = 0;
+ while (charIndex > 0) {
+ switch (dirTypeBackward()) {
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT:
+ if (embeddingLevel == 0) {
+ return DIR_LTR;
+ }
+ if (lastNonEmptyEmbeddingLevel == 0) {
+ lastNonEmptyEmbeddingLevel = embeddingLevel;
+ }
+ break;
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_EMBEDDING:
+ case Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE:
+ if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_LTR;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
+ if (embeddingLevel == 0) {
+ return DIR_RTL;
+ }
+ if (lastNonEmptyEmbeddingLevel == 0) {
+ lastNonEmptyEmbeddingLevel = embeddingLevel;
+ }
+ break;
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING:
+ case Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE:
+ if (lastNonEmptyEmbeddingLevel == embeddingLevel) {
+ return DIR_RTL;
+ }
+ --embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_POP_DIRECTIONAL_FORMAT:
+ ++embeddingLevel;
+ break;
+ case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
+ break;
+ default:
+ if (lastNonEmptyEmbeddingLevel == 0) {
+ lastNonEmptyEmbeddingLevel = embeddingLevel;
+ }
+ break;
+ }
+ }
+ return DIR_UNKNOWN;
+ }
+
+ // Internal methods
+
+ /**
+ * Gets the bidi character class, i.e. Character.getDirectionality(), of a given char, using
+ * a cache for speed. Not designed for supplementary codepoints, whose results we do not
+ * cache.
+ */
+ private static byte getCachedDirectionality(char c) {
+ return c < DIR_TYPE_CACHE_SIZE ? DIR_TYPE_CACHE[c] : Character.getDirectionality(c);
+ }
+
+ /**
+ * Returns the Character.DIRECTIONALITY_... value of the next codepoint and advances
+ * charIndex. If isHtml, and the codepoint is '<' or '&', advances through the tag/entity,
+ * and returns Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to
+ * figure out the actual character, and return its dirtype, but treating it as whitespace is
+ * good enough for our purposes.
+ *
+ * @throws java.lang.IndexOutOfBoundsException if called when charIndex >= length or < 0.
+ */
+ byte dirTypeForward() {
+ lastChar = text.charAt(charIndex);
+ if (Character.isHighSurrogate(lastChar)) {
+ int codePoint = Character.codePointAt(text, charIndex);
+ charIndex += Character.charCount(codePoint);
+ return Character.getDirectionality(codePoint);
+ }
+ charIndex++;
+ byte dirType = getCachedDirectionality(lastChar);
+ if (isHtml) {
+ // Process tags and entities.
+ if (lastChar == '<') {
+ dirType = skipTagForward();
+ } else if (lastChar == '&') {
+ dirType = skipEntityForward();
+ }
+ }
+ return dirType;
+ }
+
+ /**
+ * Returns the Character.DIRECTIONALITY_... value of the preceding codepoint and advances
+ * charIndex backwards. If isHtml, and the codepoint is the end of a complete HTML tag or
+ * entity, advances over the whole tag/entity and returns
+ * Character.DIRECTIONALITY_WHITESPACE. For an entity, it would be best to figure out the
+ * actual character, and return its dirtype, but treating it as whitespace is good enough
+ * for our purposes.
+ *
+ * @throws java.lang.IndexOutOfBoundsException if called when charIndex > length or <= 0.
+ */
+ byte dirTypeBackward() {
+ lastChar = text.charAt(charIndex - 1);
+ if (Character.isLowSurrogate(lastChar)) {
+ int codePoint = Character.codePointBefore(text, charIndex);
+ charIndex -= Character.charCount(codePoint);
+ return Character.getDirectionality(codePoint);
+ }
+ charIndex--;
+ byte dirType = getCachedDirectionality(lastChar);
+ if (isHtml) {
+ // Process tags and entities.
+ if (lastChar == '>') {
+ dirType = skipTagBackward();
+ } else if (lastChar == ';') {
+ dirType = skipEntityBackward();
+ }
+ }
+ return dirType;
+ }
+
+ /**
+ * Advances charIndex forward through an HTML tag (after the opening &lt; has already been
+ * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &gt;,
+ * does not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the
+ * &lt; that hadn't been part of a tag after all).
+ */
+ private byte skipTagForward() {
+ int initialCharIndex = charIndex;
+ while (charIndex < length) {
+ lastChar = text.charAt(charIndex++);
+ if (lastChar == '>') {
+ // The end of the tag.
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+ if (lastChar == '"' || lastChar == '\'') {
+ // Skip over a quoted attribute value inside the tag.
+ char quote = lastChar;
+ while (charIndex < length && (lastChar = text.charAt(charIndex++)) != quote) {}
+ }
+ }
+ // The original '<' wasn't the start of a tag after all.
+ charIndex = initialCharIndex;
+ lastChar = '<';
+ return Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+
+ /**
+ * Advances charIndex backward through an HTML tag (after the closing &gt; has already been
+ * read) and returns Character.DIRECTIONALITY_WHITESPACE. If there is no matching &lt;, does
+ * not change charIndex and returns Character.DIRECTIONALITY_OTHER_NEUTRALS (for the &gt;
+ * that hadn't been part of a tag after all). Nevertheless, the running time for calling
+ * skipTagBackward() in a loop remains linear in the size of the text, even for a text like
+ * "&gt;&gt;&gt;&gt;", because skipTagBackward() also stops looking for a matching &lt;
+ * when it encounters another &gt;.
+ */
+ private byte skipTagBackward() {
+ int initialCharIndex = charIndex;
+ while (charIndex > 0) {
+ lastChar = text.charAt(--charIndex);
+ if (lastChar == '<') {
+ // The start of the tag.
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+ if (lastChar == '>') {
+ break;
+ }
+ if (lastChar == '"' || lastChar == '\'') {
+ // Skip over a quoted attribute value inside the tag.
+ char quote = lastChar;
+ while (charIndex > 0 && (lastChar = text.charAt(--charIndex)) != quote) {}
+ }
+ }
+ // The original '>' wasn't the end of a tag after all.
+ charIndex = initialCharIndex;
+ lastChar = '>';
+ return Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+
+ /**
+ * Advances charIndex forward through an HTML character entity tag (after the opening
+ * &amp; has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be
+ * best to figure out the actual character and return its dirtype, but this is good enough.
+ */
+ private byte skipEntityForward() {
+ while (charIndex < length && (lastChar = text.charAt(charIndex++)) != ';') {}
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+
+ /**
+ * Advances charIndex backward through an HTML character entity tag (after the closing ;
+ * has already been read) and returns Character.DIRECTIONALITY_WHITESPACE. It would be best
+ * to figure out the actual character and return its dirtype, but this is good enough.
+ * If there is no matching &amp;, does not change charIndex and returns
+ * Character.DIRECTIONALITY_OTHER_NEUTRALS (for the ';' that did not start an entity after
+ * all). Nevertheless, the running time for calling skipEntityBackward() in a loop remains
+ * linear in the size of the text, even for a text like ";;;;;;;", because skipTagBackward()
+ * also stops looking for a matching &amp; when it encounters another ;.
+ */
+ private byte skipEntityBackward() {
+ int initialCharIndex = charIndex;
+ while (charIndex > 0) {
+ lastChar = text.charAt(--charIndex);
+ if (lastChar == '&') {
+ return Character.DIRECTIONALITY_WHITESPACE;
+ }
+ if (lastChar == ';') {
+ break;
+ }
+ }
+ charIndex = initialCharIndex;
+ lastChar = ';';
+ return Character.DIRECTIONALITY_OTHER_NEUTRALS;
+ }
+ }
+} \ No newline at end of file
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index cc676de..7e230ac 100644
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -447,6 +447,16 @@ public class DateFormat {
* @hide
*/
public static boolean hasSeconds(CharSequence inFormat) {
+ return hasDesignator(inFormat, SECONDS);
+ }
+
+ /**
+ * Test if a format string contains the given designator. Always returns
+ * {@code false} if the input format is {@code null}.
+ *
+ * @hide
+ */
+ public static boolean hasDesignator(CharSequence inFormat, char designator) {
if (inFormat == null) return false;
final int length = inFormat.length();
@@ -460,7 +470,7 @@ public class DateFormat {
if (c == QUOTE) {
count = skipQuotedText(inFormat, i, length);
- } else if (c == SECONDS) {
+ } else if (c == designator) {
return true;
}
}
diff --git a/core/java/android/text/method/DigitsKeyListener.java b/core/java/android/text/method/DigitsKeyListener.java
index 3d9daed..c95df46 100644
--- a/core/java/android/text/method/DigitsKeyListener.java
+++ b/core/java/android/text/method/DigitsKeyListener.java
@@ -49,13 +49,22 @@ public class DigitsKeyListener extends NumberKeyListener
* @see KeyEvent#getMatch
* @see #getAcceptedChars
*/
- private static final char[][] CHARACTERS = new char[][] {
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
- new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '.' },
+ private static final char[][] CHARACTERS = {
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' },
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+' },
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' },
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.' },
};
+ private static boolean isSignChar(final char c) {
+ return c == '-' || c == '+';
+ }
+
+ // TODO: Needs internationalization
+ private static boolean isDecimalPointChar(final char c) {
+ return c == '.';
+ }
+
/**
* Allocates a DigitsKeyListener that accepts the digits 0 through 9.
*/
@@ -145,32 +154,32 @@ public class DigitsKeyListener extends NumberKeyListener
int dlen = dest.length();
/*
- * Find out if the existing text has '-' or '.' characters.
+ * Find out if the existing text has a sign or decimal point characters.
*/
for (int i = 0; i < dstart; i++) {
char c = dest.charAt(i);
- if (c == '-') {
+ if (isSignChar(c)) {
sign = i;
- } else if (c == '.') {
+ } else if (isDecimalPointChar(c)) {
decimal = i;
}
}
for (int i = dend; i < dlen; i++) {
char c = dest.charAt(i);
- if (c == '-') {
- return ""; // Nothing can be inserted in front of a '-'.
- } else if (c == '.') {
+ if (isSignChar(c)) {
+ return ""; // Nothing can be inserted in front of a sign character.
+ } else if (isDecimalPointChar(c)) {
decimal = i;
}
}
/*
* If it does, we must strip them out from the source.
- * In addition, '-' must be the very first character,
- * and nothing can be inserted before an existing '-'.
+ * In addition, a sign character must be the very first character,
+ * and nothing can be inserted before an existing sign character.
* Go in reverse order so the offsets are stable.
*/
@@ -180,7 +189,7 @@ public class DigitsKeyListener extends NumberKeyListener
char c = source.charAt(i);
boolean strip = false;
- if (c == '-') {
+ if (isSignChar(c)) {
if (i != start || dstart != 0) {
strip = true;
} else if (sign >= 0) {
@@ -188,7 +197,7 @@ public class DigitsKeyListener extends NumberKeyListener
} else {
sign = i;
}
- } else if (c == '.') {
+ } else if (isDecimalPointChar(c)) {
if (decimal >= 0) {
strip = true;
} else {
diff --git a/core/java/android/text/method/QwertyKeyListener.java b/core/java/android/text/method/QwertyKeyListener.java
index c5261f3..98316ae 100644
--- a/core/java/android/text/method/QwertyKeyListener.java
+++ b/core/java/android/text/method/QwertyKeyListener.java
@@ -180,6 +180,7 @@ public class QwertyKeyListener extends BaseKeyListener {
if (composed != 0) {
i = composed;
replace = true;
+ dead = false;
}
}
diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index 2feb719..03b4f60 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -16,6 +16,7 @@
package android.text.style;
+import android.app.PendingIntent;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextUtils;
@@ -25,12 +26,62 @@ import android.widget.TextView;
* Provides an easy way to edit a portion of text.
* <p>
* The {@link TextView} uses this span to allow the user to delete a chuck of text in one click.
- * the text. {@link TextView} removes this span as soon as the text is edited, or the cursor moves.
+ * <p>
+ * {@link TextView} removes the span when the user deletes the whole text or modifies it.
+ * <p>
+ * This span can be also used to receive notification when the user deletes or modifies the text;
*/
public class EasyEditSpan implements ParcelableSpan {
+ /**
+ * The extra key field in the pending intent that describes how the text changed.
+ *
+ * @see #TEXT_DELETED
+ * @see #TEXT_MODIFIED
+ * @see #getPendingIntent()
+ */
+ public static final String EXTRA_TEXT_CHANGED_TYPE =
+ "android.text.style.EXTRA_TEXT_CHANGED_TYPE";
+
+ /**
+ * The value of {@link #EXTRA_TEXT_CHANGED_TYPE} when the text wrapped by this span is deleted.
+ */
+ public static final int TEXT_DELETED = 1;
+
+ /**
+ * The value of {@link #EXTRA_TEXT_CHANGED_TYPE} when the text wrapped by this span is modified.
+ */
+ public static final int TEXT_MODIFIED = 2;
+
+ private final PendingIntent mPendingIntent;
+
+ private boolean mDeleteEnabled;
+
+ /**
+ * Creates the span. No intent is sent when the wrapped text is modified or
+ * deleted.
+ */
public EasyEditSpan() {
- // Empty
+ mPendingIntent = null;
+ mDeleteEnabled = true;
+ }
+
+ /**
+ * @param pendingIntent The intent will be sent when the wrapped text is deleted or modified.
+ * When the pending intent is sent, {@link #EXTRA_TEXT_CHANGED_TYPE} is
+ * added in the intent to describe how the text changed.
+ */
+ public EasyEditSpan(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ mDeleteEnabled = true;
+ }
+
+ /**
+ * Constructor called from {@link TextUtils} to restore the span.
+ */
+ public EasyEditSpan(Parcel source) {
+ mPendingIntent = source.readParcelable(null);
+ mDeleteEnabled = (source.readByte() == 1);
}
@Override
@@ -40,11 +91,39 @@ public class EasyEditSpan implements ParcelableSpan {
@Override
public void writeToParcel(Parcel dest, int flags) {
- // Empty
+ dest.writeParcelable(mPendingIntent, 0);
+ dest.writeByte((byte) (mDeleteEnabled ? 1 : 0));
}
@Override
public int getSpanTypeId() {
return TextUtils.EASY_EDIT_SPAN;
}
+
+ /**
+ * @return True if the {@link TextView} should offer the ability to delete the text.
+ *
+ * @hide
+ */
+ public boolean isDeleteEnabled() {
+ return mDeleteEnabled;
+ }
+
+ /**
+ * Enables or disables the deletion of the text.
+ *
+ * @hide
+ */
+ public void setDeleteEnabled(boolean value) {
+ mDeleteEnabled = value;
+ }
+
+ /**
+ * @return the pending intent to send when the wrapped text is deleted or modified.
+ *
+ * @hide
+ */
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
}
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 5dc206f..0ec7e84 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -17,6 +17,7 @@
package android.text.style;
import android.content.Context;
+import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Parcel;
@@ -26,6 +27,7 @@ import android.text.ParcelableSpan;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import java.util.Arrays;
@@ -45,6 +47,8 @@ import java.util.Locale;
*/
public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
+ private static final String TAG = "SuggestionSpan";
+
/**
* Sets this flag if the suggestions should be easily accessible with few interactions.
* This flag should be set for every suggestions that the user is likely to use.
@@ -82,6 +86,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
private final String[] mSuggestions;
private final String mLocaleString;
private final String mNotificationTargetClassName;
+ private final String mNotificationTargetPackageName;
private final int mHashCode;
private float mEasyCorrectUnderlineThickness;
@@ -134,6 +139,12 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mLocaleString = "";
}
+ if (context != null) {
+ mNotificationTargetPackageName = context.getPackageName();
+ } else {
+ mNotificationTargetPackageName = null;
+ }
+
if (notificationTargetClass != null) {
mNotificationTargetClassName = notificationTargetClass.getCanonicalName();
} else {
@@ -185,6 +196,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
mFlags = src.readInt();
mLocaleString = src.readString();
mNotificationTargetClassName = src.readString();
+ mNotificationTargetPackageName = src.readString();
mHashCode = src.readInt();
mEasyCorrectUnderlineColor = src.readInt();
mEasyCorrectUnderlineThickness = src.readFloat();
@@ -240,6 +252,7 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
dest.writeInt(mFlags);
dest.writeString(mLocaleString);
dest.writeString(mNotificationTargetClassName);
+ dest.writeString(mNotificationTargetPackageName);
dest.writeInt(mHashCode);
dest.writeInt(mEasyCorrectUnderlineColor);
dest.writeFloat(mEasyCorrectUnderlineThickness);
@@ -325,4 +338,40 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
}
return 0;
}
+
+ /**
+ * Notifies a suggestion selection.
+ *
+ * @hide
+ */
+ public void notifySelection(Context context, String original, int index) {
+ final Intent intent = new Intent();
+
+ if (context == null || mNotificationTargetClassName == null) {
+ return;
+ }
+ // Ensures that only a class in the original IME package will receive the
+ // notification.
+ if (mSuggestions == null || index < 0 || index >= mSuggestions.length) {
+ Log.w(TAG, "Unable to notify the suggestion as the index is out of range index=" + index
+ + " length=" + mSuggestions.length);
+ return;
+ }
+
+ // The package name is not mandatory (legacy from JB), and if the package name
+ // is missing, we try to notify the suggestion through the input method manager.
+ if (mNotificationTargetPackageName != null) {
+ intent.setClassName(mNotificationTargetPackageName, mNotificationTargetClassName);
+ intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, original);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, mSuggestions[index]);
+ intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, hashCode());
+ context.sendBroadcast(intent);
+ } else {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.notifySuggestionPicked(this, original, index);
+ }
+ }
+ }
}
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index e856501..dae47b8 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -74,6 +74,15 @@ public class DisplayMetrics {
public static final int DENSITY_XXHIGH = 480;
/**
+ * Standard quantized DPI for extra-extra-extra-high-density screens. Applications
+ * should not generally worry about this density; relying on XHIGH graphics
+ * being scaled up to it should be sufficient for almost all cases. A typical
+ * use of this density would be 4K television screens -- 3840x2160, which
+ * is 2x a traditional HD 1920x1080 screen which runs at DENSITY_XHIGH.
+ */
+ public static final int DENSITY_XXXHIGH = 640;
+
+ /**
* The reference density used throughout the system.
*/
public static final int DENSITY_DEFAULT = DENSITY_MEDIUM;
diff --git a/core/java/android/util/FinitePool.java b/core/java/android/util/FinitePool.java
deleted file mode 100644
index b30f2bf..0000000
--- a/core/java/android/util/FinitePool.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- * @hide
- */
-class FinitePool<T extends Poolable<T>> implements Pool<T> {
- private static final String LOG_TAG = "FinitePool";
-
- /**
- * Factory used to create new pool objects
- */
- private final PoolableManager<T> mManager;
- /**
- * Maximum number of objects in the pool
- */
- private final int mLimit;
- /**
- * If true, mLimit is ignored
- */
- private final boolean mInfinite;
-
- /**
- * Next object to acquire
- */
- private T mRoot;
- /**
- * Number of objects in the pool
- */
- private int mPoolCount;
-
- FinitePool(PoolableManager<T> manager) {
- mManager = manager;
- mLimit = 0;
- mInfinite = true;
- }
-
- FinitePool(PoolableManager<T> manager, int limit) {
- if (limit <= 0) throw new IllegalArgumentException("The pool limit must be > 0");
-
- mManager = manager;
- mLimit = limit;
- mInfinite = false;
- }
-
- public T acquire() {
- T element;
-
- if (mRoot != null) {
- element = mRoot;
- mRoot = element.getNextPoolable();
- mPoolCount--;
- } else {
- element = mManager.newInstance();
- }
-
- if (element != null) {
- element.setNextPoolable(null);
- element.setPooled(false);
- mManager.onAcquired(element);
- }
-
- return element;
- }
-
- public void release(T element) {
- if (!element.isPooled()) {
- if (mInfinite || mPoolCount < mLimit) {
- mPoolCount++;
- element.setNextPoolable(mRoot);
- element.setPooled(true);
- mRoot = element;
- }
- mManager.onReleased(element);
- } else {
- Log.w(LOG_TAG, "Element is already in pool: " + element);
- }
- }
-}
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java
new file mode 100644
index 0000000..34b6126
--- /dev/null
+++ b/core/java/android/util/LongSparseLongArray.java
@@ -0,0 +1,228 @@
+/*
+ * 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.util;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+
+/**
+ * Map of {@code long} to {@code long}. Unlike a normal array of longs, there
+ * can be gaps in the indices. It is intended to be more efficient than using a
+ * {@code HashMap}.
+ *
+ * @hide
+ */
+public class LongSparseLongArray implements Cloneable {
+ private long[] mKeys;
+ private long[] mValues;
+ private int mSize;
+
+ /**
+ * Creates a new SparseLongArray containing no mappings.
+ */
+ public LongSparseLongArray() {
+ this(10);
+ }
+
+ /**
+ * Creates a new SparseLongArray containing no mappings that will not
+ * require any additional memory allocation to store the specified
+ * number of mappings.
+ */
+ public LongSparseLongArray(int initialCapacity) {
+ initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity);
+
+ mKeys = new long[initialCapacity];
+ mValues = new long[initialCapacity];
+ mSize = 0;
+ }
+
+ @Override
+ public LongSparseLongArray clone() {
+ LongSparseLongArray clone = null;
+ try {
+ clone = (LongSparseLongArray) super.clone();
+ clone.mKeys = mKeys.clone();
+ clone.mValues = mValues.clone();
+ } catch (CloneNotSupportedException cnse) {
+ /* ignore */
+ }
+ return clone;
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or <code>0</code>
+ * if no such mapping has been made.
+ */
+ public long get(long key) {
+ return get(key, 0);
+ }
+
+ /**
+ * Gets the long mapped from the specified key, or the specified value
+ * if no such mapping has been made.
+ */
+ public long get(long key, long valueIfKeyNotFound) {
+ int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+
+ if (i < 0) {
+ return valueIfKeyNotFound;
+ } else {
+ return mValues[i];
+ }
+ }
+
+ /**
+ * Removes the mapping from the specified key, if there was any.
+ */
+ public void delete(long key) {
+ int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ removeAt(i);
+ }
+ }
+
+ /**
+ * Removes the mapping at the given index.
+ */
+ public void removeAt(int index) {
+ System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
+ System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
+ 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, long value) {
+ int i = Arrays.binarySearch(mKeys, 0, mSize, key);
+
+ if (i >= 0) {
+ mValues[i] = value;
+ } else {
+ i = ~i;
+
+ if (mSize >= mKeys.length) {
+ growKeyAndValueArrays(mSize + 1);
+ }
+
+ if (mSize - i != 0) {
+ 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 SparseIntArray
+ * currently stores.
+ */
+ public int size() {
+ 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
+ * SparseLongArray stores.
+ */
+ public long keyAt(int index) {
+ 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
+ * SparseLongArray stores.
+ */
+ public long valueAt(int index) {
+ return mValues[index];
+ }
+
+ /**
+ * 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) {
+ return Arrays.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(long value) {
+ for (int i = 0; i < mSize; i++)
+ if (mValues[i] == value)
+ return i;
+
+ return -1;
+ }
+
+ /**
+ * Removes all key-value mappings from this SparseIntArray.
+ */
+ public void clear() {
+ mSize = 0;
+ }
+
+ /**
+ * 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, long value) {
+ if (mSize != 0 && key <= mKeys[mSize - 1]) {
+ put(key, value);
+ return;
+ }
+
+ int pos = mSize;
+ if (pos >= mKeys.length) {
+ growKeyAndValueArrays(pos + 1);
+ }
+
+ mKeys[pos] = key;
+ mValues[pos] = value;
+ mSize = pos + 1;
+ }
+
+ private void growKeyAndValueArrays(int minNeededSize) {
+ int n = ArrayUtils.idealLongArraySize(minNeededSize);
+
+ long[] nkeys = new long[n];
+ long[] nvalues = new long[n];
+
+ System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
+ System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
+
+ mKeys = nkeys;
+ mValues = nvalues;
+ }
+}
diff --git a/core/java/android/util/Pools.java b/core/java/android/util/Pools.java
index 8edb3e6..70581be 100644
--- a/core/java/android/util/Pools.java
+++ b/core/java/android/util/Pools.java
@@ -17,25 +17,149 @@
package android.util;
/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ * private static final SynchronizedPool<MyPooledClass> sPool =
+ * new SynchronizedPool<MyPooledClass>(10);
+ *
+ * public static MyPooledClass obtain() {
+ * MyPooledClass instance = sPool.acquire();
+ * return (instance != null) ? instance : new MyPooledClass();
+ * }
+ *
+ * public void recycle() {
+ * // Clear state if needed.
+ * sPool.release(this);
+ * }
+ *
+ * . . .
+ * }
+ * </pre>
+ *
* @hide
*/
-public class Pools {
- private Pools() {
- }
+public final class Pools {
+
+ /**
+ * Interface for managing a pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static interface Pool<T> {
- public static <T extends Poolable<T>> Pool<T> simplePool(PoolableManager<T> manager) {
- return new FinitePool<T>(manager);
+ /**
+ * @return An instance from the pool if such, null otherwise.
+ */
+ public T acquire();
+
+ /**
+ * Release an instance to the pool.
+ *
+ * @param instance The instance to release.
+ * @return Whether the instance was put in the pool.
+ *
+ * @throws IllegalStateException If the instance is already in the pool.
+ */
+ public boolean release(T instance);
}
-
- public static <T extends Poolable<T>> Pool<T> finitePool(PoolableManager<T> manager, int limit) {
- return new FinitePool<T>(manager, limit);
+
+ private Pools() {
+ /* do nothing - hiding constructor */
}
- public static <T extends Poolable<T>> Pool<T> synchronizedPool(Pool<T> pool) {
- return new SynchronizedPool<T>(pool);
+ /**
+ * Simple (non-synchronized) pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static class SimplePool<T> implements Pool<T> {
+ private final Object[] mPool;
+
+ private int mPoolSize;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SimplePool(int maxPoolSize) {
+ if (maxPoolSize <= 0) {
+ throw new IllegalArgumentException("The max pool size must be > 0");
+ }
+ mPool = new Object[maxPoolSize];
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T acquire() {
+ if (mPoolSize > 0) {
+ final int lastPooledIndex = mPoolSize - 1;
+ T instance = (T) mPool[lastPooledIndex];
+ mPool[lastPooledIndex] = null;
+ mPoolSize--;
+ return instance;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean release(T instance) {
+ if (isInPool(instance)) {
+ throw new IllegalStateException("Already in the pool!");
+ }
+ if (mPoolSize < mPool.length) {
+ mPool[mPoolSize] = instance;
+ mPoolSize++;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isInPool(T instance) {
+ for (int i = 0; i < mPoolSize; i++) {
+ if (mPool[i] == instance) {
+ return true;
+ }
+ }
+ return false;
+ }
}
- public static <T extends Poolable<T>> Pool<T> synchronizedPool(Pool<T> pool, Object lock) {
- return new SynchronizedPool<T>(pool, lock);
+ /**
+ * Synchronized) pool of objects.
+ *
+ * @param <T> The pooled type.
+ */
+ public static class SynchronizedPool<T> extends SimplePool<T> {
+ private final Object mLock = new Object();
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SynchronizedPool(int maxPoolSize) {
+ super(maxPoolSize);
+ }
+
+ @Override
+ public T acquire() {
+ synchronized (mLock) {
+ return super.acquire();
+ }
+ }
+
+ @Override
+ public boolean release(T element) {
+ synchronized (mLock) {
+ return super.release(element);
+ }
+ }
}
}
diff --git a/core/java/android/util/PropertyValueModel.java b/core/java/android/util/PropertyValueModel.java
new file mode 100755
index 0000000..eb9c47d
--- /dev/null
+++ b/core/java/android/util/PropertyValueModel.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 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;
+
+/**
+ * A value model for a {@link Property property} of a host object. This class can be used for
+ * both reflective and non-reflective property implementations.
+ *
+ * @param <H> the host type, where the host is the object that holds this property
+ * @param <T> the value type
+ *
+ * @see Property
+ * @see ValueModel
+ */
+public class PropertyValueModel<H, T> extends ValueModel<T> {
+ private final H mHost;
+ private final Property<H, T> mProperty;
+
+ private PropertyValueModel(H host, Property<H, T> property) {
+ mProperty = property;
+ mHost = host;
+ }
+
+ /**
+ * Returns the host.
+ *
+ * @return the host
+ */
+ public H getHost() {
+ return mHost;
+ }
+
+ /**
+ * Returns the property.
+ *
+ * @return the property
+ */
+ public Property<H, T> getProperty() {
+ return mProperty;
+ }
+
+ @Override
+ public Class<T> getType() {
+ return mProperty.getType();
+ }
+
+ @Override
+ public T get() {
+ return mProperty.get(mHost);
+ }
+
+ @Override
+ public void set(T value) {
+ mProperty.set(mHost, value);
+ }
+
+ /**
+ * Return an appropriate PropertyValueModel for this host and property.
+ *
+ * @param host the host
+ * @param property the property
+ * @return the value model
+ */
+ public static <H, T> PropertyValueModel<H, T> of(H host, Property<H, T> property) {
+ return new PropertyValueModel<H, T>(host, property);
+ }
+
+ /**
+ * Return a PropertyValueModel for this {@code host} and a
+ * reflective property, constructed from this {@code propertyType} and {@code propertyName}.
+ *
+ * @param host
+ * @param propertyType the property type
+ * @param propertyName the property name
+ * @return a value model with this host and a reflective property with this type and name
+ *
+ * @see Property#of
+ */
+ public static <H, T> PropertyValueModel<H, T> of(H host, Class<T> propertyType,
+ String propertyName) {
+ return of(host, Property.of((Class<H>) host.getClass(), propertyType, propertyName));
+ }
+
+ private static Class getNullaryMethodReturnType(Class c, String name) {
+ try {
+ return c.getMethod(name).getReturnType();
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ private static Class getFieldType(Class c, String name) {
+ try {
+ return c.getField(name).getType();
+ } catch (NoSuchFieldException e) {
+ return null;
+ }
+ }
+
+ private static String capitalize(String name) {
+ if (name.isEmpty()) {
+ return name;
+ }
+ return Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ }
+
+ /**
+ * Return a PropertyValueModel for this {@code host} and and {@code propertyName}.
+ *
+ * @param host the host
+ * @param propertyName the property name
+ * @return a value model with this host and a reflective property with this name
+ */
+ public static PropertyValueModel of(Object host, String propertyName) {
+ Class clazz = host.getClass();
+ String suffix = capitalize(propertyName);
+ Class propertyType = getNullaryMethodReturnType(clazz, "get" + suffix);
+ if (propertyType == null) {
+ propertyType = getNullaryMethodReturnType(clazz, "is" + suffix);
+ }
+ if (propertyType == null) {
+ propertyType = getFieldType(clazz, propertyName);
+ }
+ if (propertyType == null) {
+ throw new NoSuchPropertyException(propertyName);
+ }
+ return of(host, propertyType, propertyName);
+ }
+}
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index a08d5cb..2f7a6fe 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -22,8 +22,6 @@ import com.android.internal.util.ArrayUtils;
* SparseLongArrays map integers to longs. Unlike a normal array of longs,
* there can be gaps in the indices. It is intended to be more efficient
* than using a HashMap to map Integers to Longs.
- *
- * @hide
*/
public class SparseLongArray implements Cloneable {
diff --git a/core/java/android/util/SynchronizedPool.java b/core/java/android/util/SynchronizedPool.java
deleted file mode 100644
index 651e0c3..0000000
--- a/core/java/android/util/SynchronizedPool.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.util;
-
-/**
- *
- * @hide
- */
-class SynchronizedPool<T extends Poolable<T>> implements Pool<T> {
- private final Pool<T> mPool;
- private final Object mLock;
-
- public SynchronizedPool(Pool<T> pool) {
- mPool = pool;
- mLock = this;
- }
-
- public SynchronizedPool(Pool<T> pool, Object lock) {
- mPool = pool;
- mLock = lock;
- }
-
- public T acquire() {
- synchronized (mLock) {
- return mPool.acquire();
- }
- }
-
- public void release(T element) {
- synchronized (mLock) {
- mPool.release(element);
- }
- }
-}
diff --git a/core/java/android/util/ValueModel.java b/core/java/android/util/ValueModel.java
new file mode 100755
index 0000000..4789682
--- /dev/null
+++ b/core/java/android/util/ValueModel.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 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;
+
+/**
+ * A ValueModel is an abstraction for a 'slot' or place in memory in which a value
+ * may be stored and retrieved. A common implementation of ValueModel is a regular property of
+ * an object, whose value may be retrieved by calling the appropriate <em>getter</em>
+ * method and set by calling the corresponding <em>setter</em> method.
+ *
+ * @param <T> the value type
+ *
+ * @see PropertyValueModel
+ */
+public abstract class ValueModel<T> {
+ /**
+ * The empty model should be used in place of {@code null} to indicate that a
+ * model has not been set. The empty model has no value and does nothing when it is set.
+ */
+ public static final ValueModel EMPTY = new ValueModel() {
+ @Override
+ public Class getType() {
+ return Object.class;
+ }
+
+ @Override
+ public Object get() {
+ return null;
+ }
+
+ @Override
+ public void set(Object value) {
+
+ }
+ };
+
+ protected ValueModel() {
+ }
+
+ /**
+ * Returns the type of this property.
+ *
+ * @return the property type
+ */
+ public abstract Class<T> getType();
+
+ /**
+ * Returns the value of this property.
+ *
+ * @return the property value
+ */
+ public abstract T get();
+
+ /**
+ * Sets the value of this property.
+ *
+ * @param value the new value for this property
+ */
+ public abstract void set(T value);
+} \ No newline at end of file
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 9bee4bf..2d6453e 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -16,8 +16,7 @@
package android.view;
-import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
-
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -26,12 +25,14 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.util.SparseLongArray;
+import android.view.View.AttachInfo;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Predicate;
import java.util.ArrayList;
import java.util.HashMap;
@@ -47,7 +48,7 @@ import java.util.Map;
*/
final class AccessibilityInteractionController {
- private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
new ArrayList<AccessibilityNodeInfo>();
private final Handler mHandler;
@@ -62,7 +63,12 @@ final class AccessibilityInteractionController {
private final ArrayList<View> mTempArrayList = new ArrayList<View>();
+ private final Point mTempPoint = new Point();
private final Rect mTempRect = new Rect();
+ private final Rect mTempRect1 = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private AddNodeInfosForViewId mAddNodeInfosForViewId;
public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
Looper looper = viewRootImpl.mHandler.getLooper();
@@ -86,7 +92,7 @@ final class AccessibilityInteractionController {
public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid) {
+ long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
message.arg1 = flags;
@@ -96,6 +102,7 @@ final class AccessibilityInteractionController {
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi3 = interactionId;
args.arg1 = callback;
+ args.arg2 = spec;
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -119,6 +126,7 @@ final class AccessibilityInteractionController {
final int interactionId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
args.recycle();
@@ -128,8 +136,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
root = mViewRootImpl.mView;
@@ -141,8 +148,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(infos);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
infos.clear();
} catch (RemoteException re) {
@@ -151,18 +161,19 @@ final class AccessibilityInteractionController {
}
}
- public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interrogatingPid, long interrogatingTid) {
+ public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
+ String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
- message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID;
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
SomeArgs args = SomeArgs.obtain();
- args.argi1 = viewId;
- args.argi2 = interactionId;
+ args.argi1 = interactionId;
args.arg1 = callback;
+ args.arg2 = spec;
+ args.arg3 = viewId;
message.obj = args;
@@ -178,25 +189,26 @@ final class AccessibilityInteractionController {
}
}
- private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
final int flags = message.arg1;
final int accessibilityViewId = message.arg2;
SomeArgs args = (SomeArgs) message.obj;
- final int viewId = args.argi1;
- final int interactionId = args.argi2;
+ final int interactionId = args.argi1;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
+ final String viewId = (String) args.arg3;
args.recycle();
- AccessibilityNodeInfo info = null;
+ final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -204,16 +216,26 @@ final class AccessibilityInteractionController {
root = mViewRootImpl.mView;
}
if (root != null) {
- View target = root.findViewById(viewId);
- if (target != null && isShown(target)) {
- info = target.createAccessibilityNodeInfo();
+ final int resolvedViewId = root.getContext().getResources()
+ .getIdentifier(viewId, null, null);
+ if (resolvedViewId <= 0) {
+ return;
+ }
+ if (mAddNodeInfosForViewId == null) {
+ mAddNodeInfosForViewId = new AddNodeInfosForViewId();
}
+ mAddNodeInfosForViewId.init(resolvedViewId, infos);
+ root.findViewByPredicate(mAddNodeInfosForViewId);
+ mAddNodeInfosForViewId.reset();
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(info);
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
@@ -222,7 +244,7 @@ final class AccessibilityInteractionController {
public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int flags, int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
message.arg1 = flags;
@@ -230,10 +252,10 @@ final class AccessibilityInteractionController {
SomeArgs args = SomeArgs.obtain();
args.arg1 = text;
args.arg2 = callback;
+ args.arg3 = spec;
args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi3 = interactionId;
-
message.obj = args;
// If the interrogation is performed by the same thread as the main UI
@@ -255,6 +277,7 @@ final class AccessibilityInteractionController {
final String text = (String) args.arg1;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg2;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg3;
final int accessibilityViewId = args.argi1;
final int virtualDescendantId = args.argi2;
final int interactionId = args.argi3;
@@ -265,8 +288,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -309,8 +331,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(infos);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -320,7 +345,7 @@ final class AccessibilityInteractionController {
public void findFocusClientThread(long accessibilityNodeId, int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
- long interrogatingTid) {
+ long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FIND_FOCUS;
message.arg1 = flags;
@@ -331,6 +356,7 @@ final class AccessibilityInteractionController {
args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.arg1 = callback;
+ args.arg2 = spec;
message.obj = args;
@@ -356,7 +382,7 @@ final class AccessibilityInteractionController {
final int virtualDescendantId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
-
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
args.recycle();
AccessibilityNodeInfo focused = null;
@@ -364,8 +390,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -406,8 +431,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(focused);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -417,7 +445,7 @@ final class AccessibilityInteractionController {
public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
- long interrogatingTid) {
+ long interrogatingTid, MagnificationSpec spec) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_FOCUS_SEARCH;
message.arg1 = flags;
@@ -427,6 +455,7 @@ final class AccessibilityInteractionController {
args.argi2 = direction;
args.argi3 = interactionId;
args.arg1 = callback;
+ args.arg2 = spec;
message.obj = args;
@@ -451,6 +480,7 @@ final class AccessibilityInteractionController {
final int interactionId = args.argi3;
final IAccessibilityInteractionConnectionCallback callback =
(IAccessibilityInteractionConnectionCallback) args.arg1;
+ final MagnificationSpec spec = (MagnificationSpec) args.arg2;
args.recycle();
@@ -459,8 +489,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
root = findViewByAccessibilityId(accessibilityViewId);
@@ -475,8 +504,11 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
- applyApplicationScaleIfNeeded(next);
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
+ applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
+ if (spec != null) {
+ spec.recycle();
+ }
callback.setFindAccessibilityNodeInfoResult(next, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -533,8 +565,7 @@ final class AccessibilityInteractionController {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
- (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View target = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
target = findViewByAccessibilityId(accessibilityViewId);
@@ -552,7 +583,7 @@ final class AccessibilityInteractionController {
}
} finally {
try {
- mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
@@ -572,38 +603,84 @@ final class AccessibilityInteractionController {
return foundView;
}
- private void applyApplicationScaleIfNeeded(List<AccessibilityNodeInfo> infos) {
+ private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
+ MagnificationSpec spec) {
if (infos == null) {
return;
}
final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
- if (applicationScale != 1.0f) {
+ if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
final int infoCount = infos.size();
for (int i = 0; i < infoCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
- applyApplicationScaleIfNeeded(info);
+ applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
}
}
}
- private void applyApplicationScaleIfNeeded(AccessibilityNodeInfo info) {
+ private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
+ MagnificationSpec spec) {
if (info == null) {
return;
}
+
final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
+ if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
+ return;
+ }
+
+ Rect boundsInParent = mTempRect;
+ Rect boundsInScreen = mTempRect1;
+
+ info.getBoundsInParent(boundsInParent);
+ info.getBoundsInScreen(boundsInScreen);
if (applicationScale != 1.0f) {
- Rect bounds = mTempRect;
+ boundsInParent.scale(applicationScale);
+ boundsInScreen.scale(applicationScale);
+ }
+ if (spec != null) {
+ boundsInParent.scale(spec.scale);
+ // boundsInParent must not be offset.
+ boundsInScreen.scale(spec.scale);
+ boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
+ }
+ info.setBoundsInParent(boundsInParent);
+ info.setBoundsInScreen(boundsInScreen);
+
+ if (spec != null) {
+ AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
+ if (attachInfo.mDisplay == null) {
+ return;
+ }
- info.getBoundsInParent(bounds);
- bounds.scale(applicationScale);
- info.setBoundsInParent(bounds);
+ final float scale = attachInfo.mApplicationScale * spec.scale;
- info.getBoundsInScreen(bounds);
- bounds.scale(applicationScale);
- info.setBoundsInScreen(bounds);
+ Rect visibleWinFrame = mTempRect1;
+ visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
+ visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
+ visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
+ visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
+
+ attachInfo.mDisplay.getRealSize(mTempPoint);
+ final int displayWidth = mTempPoint.x;
+ final int displayHeight = mTempPoint.y;
+
+ Rect visibleDisplayFrame = mTempRect2;
+ visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
+
+ visibleWinFrame.intersect(visibleDisplayFrame);
+
+ if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
+ boundsInScreen.right, boundsInScreen.bottom)) {
+ info.setVisibleToUser(false);
+ }
}
}
+ private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
+ MagnificationSpec spec) {
+ return (appScale != 1.0f || (spec != null && !spec.isNop()));
+ }
/**
* This class encapsulates a prefetching strategy for the accessibility APIs for
@@ -616,20 +693,20 @@ final class AccessibilityInteractionController {
private final ArrayList<View> mTempViewList = new ArrayList<View>();
- public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
List<AccessibilityNodeInfo> outInfos) {
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
if (provider == null) {
AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfRealNode(view, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfRealNode(view, outInfos);
}
}
@@ -637,13 +714,13 @@ final class AccessibilityInteractionController {
AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
if (root != null) {
outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
}
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
prefetchDescendantsOfVirtualNode(root, provider, outInfos);
}
}
@@ -846,7 +923,7 @@ final class AccessibilityInteractionController {
private class PrivateHandler extends Handler {
private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3;
private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
private final static int MSG_FIND_FOCUS = 5;
private final static int MSG_FOCUS_SEARCH = 6;
@@ -863,8 +940,8 @@ final class AccessibilityInteractionController {
return "MSG_PERFORM_ACCESSIBILITY_ACTION";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID";
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case MSG_FIND_FOCUS:
@@ -886,8 +963,8 @@ final class AccessibilityInteractionController {
case MSG_PERFORM_ACCESSIBILITY_ACTION: {
perfromAccessibilityActionUiThread(message);
} break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
- findAccessibilityNodeInfoByViewIdUiThread(message);
+ case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: {
+ findAccessibilityNodeInfosByViewIdUiThread(message);
} break;
case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
findAccessibilityNodeInfosByTextUiThread(message);
@@ -903,4 +980,27 @@ final class AccessibilityInteractionController {
}
}
}
+
+ private final class AddNodeInfosForViewId implements Predicate<View> {
+ private int mViewId = View.NO_ID;
+ private List<AccessibilityNodeInfo> mInfos;
+
+ public void init(int viewId, List<AccessibilityNodeInfo> infos) {
+ mViewId = viewId;
+ mInfos = infos;
+ }
+
+ public void reset() {
+ mViewId = View.NO_ID;
+ mInfos = null;
+ }
+
+ @Override
+ public boolean apply(View view) {
+ if (view.getId() == mViewId && isShown(view)) {
+ mInfos.add(view.createAccessibilityNodeInfo());
+ }
+ return false;
+ }
+ }
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index b661748..f28e4b5 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,7 +693,7 @@ public final class Choreographer {
// At this time Surface Flinger won't send us vsyncs for secondary displays
// but that could change in the future so let's log a message to help us remember
// that we need to fix this.
- if (builtInDisplayId != Surface.BUILT_IN_DISPLAY_ID_MAIN) {
+ if (builtInDisplayId != SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
Log.d(TAG, "Received vsync from secondary display, but we don't support "
+ "this case yet. Choreographer needs a way to explicitly request "
+ "vsync for a specific display to ensure it doesn't lose track "
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 758abb5..e6a7950 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -437,6 +437,20 @@ public final class Display {
}
/**
+ * @hide
+ * Return a rectangle defining the insets of the overscan region of the display.
+ * Each field of the rectangle is the number of pixels the overscan area extends
+ * into the display on that side.
+ */
+ public void getOverscanInsets(Rect outRect) {
+ synchronized (this) {
+ updateDisplayInfoLocked();
+ outRect.set(mDisplayInfo.overscanLeft, mDisplayInfo.overscanTop,
+ mDisplayInfo.overscanRight, mDisplayInfo.overscanBottom);
+ }
+ }
+
+ /**
* Returns the rotation of the screen from its "natural" orientation.
* The returned value may be {@link Surface#ROTATION_0 Surface.ROTATION_0}
* (no rotation), {@link Surface#ROTATION_90 Surface.ROTATION_90},
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index a919ffc..4dade20 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -102,7 +102,7 @@ public abstract class DisplayEventReceiver {
* @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
* timebase.
* @param builtInDisplayId The surface flinger built-in display id such as
- * {@link Surface#BUILT_IN_DISPLAY_ID_MAIN}.
+ * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_MAIN}.
* @param frame The frame number. Increases by one for each vertical sync interval.
*/
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
@@ -114,7 +114,7 @@ public abstract class DisplayEventReceiver {
* @param timestampNanos The timestamp of the event, in the {@link System#nanoTime()}
* timebase.
* @param builtInDisplayId The surface flinger built-in display id such as
- * {@link Surface#BUILT_IN_DISPLAY_ID_HDMI}.
+ * {@link SurfaceControl#BUILT_IN_DISPLAY_ID_HDMI}.
* @param connected True if the display is connected, false if it disconnected.
*/
public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 305fd5c..9fcd9b1 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -109,6 +109,30 @@ public final class DisplayInfo implements Parcelable {
public int logicalHeight;
/**
+ * @hide
+ * Number of overscan pixels on the left side of the display.
+ */
+ public int overscanLeft;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the top side of the display.
+ */
+ public int overscanTop;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the right side of the display.
+ */
+ public int overscanRight;
+
+ /**
+ * @hide
+ * Number of overscan pixels on the bottom side of the display.
+ */
+ public int overscanBottom;
+
+ /**
* The rotation of the display relative to its natural orientation.
* May be one of {@link android.view.Surface#ROTATION_0},
* {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180},
@@ -196,6 +220,10 @@ public final class DisplayInfo implements Parcelable {
&& largestNominalAppHeight == other.largestNominalAppHeight
&& logicalWidth == other.logicalWidth
&& logicalHeight == other.logicalHeight
+ && overscanLeft == other.overscanLeft
+ && overscanTop == other.overscanTop
+ && overscanRight == other.overscanRight
+ && overscanBottom == other.overscanBottom
&& rotation == other.rotation
&& refreshRate == other.refreshRate
&& logicalDensityDpi == other.logicalDensityDpi
@@ -222,6 +250,10 @@ public final class DisplayInfo implements Parcelable {
largestNominalAppHeight = other.largestNominalAppHeight;
logicalWidth = other.logicalWidth;
logicalHeight = other.logicalHeight;
+ overscanLeft = other.overscanLeft;
+ overscanTop = other.overscanTop;
+ overscanRight = other.overscanRight;
+ overscanBottom = other.overscanBottom;
rotation = other.rotation;
refreshRate = other.refreshRate;
logicalDensityDpi = other.logicalDensityDpi;
@@ -243,6 +275,10 @@ public final class DisplayInfo implements Parcelable {
largestNominalAppHeight = source.readInt();
logicalWidth = source.readInt();
logicalHeight = source.readInt();
+ overscanLeft = source.readInt();
+ overscanTop = source.readInt();
+ overscanRight = source.readInt();
+ overscanBottom = source.readInt();
rotation = source.readInt();
refreshRate = source.readFloat();
logicalDensityDpi = source.readInt();
@@ -265,6 +301,10 @@ public final class DisplayInfo implements Parcelable {
dest.writeInt(largestNominalAppHeight);
dest.writeInt(logicalWidth);
dest.writeInt(logicalHeight);
+ dest.writeInt(overscanLeft);
+ dest.writeInt(overscanTop);
+ dest.writeInt(overscanRight);
+ dest.writeInt(overscanBottom);
dest.writeInt(rotation);
dest.writeFloat(refreshRate);
dest.writeInt(logicalDensityDpi);
@@ -318,18 +358,55 @@ public final class DisplayInfo implements Parcelable {
// For debugging purposes
@Override
public String toString() {
- return "DisplayInfo{\"" + name + "\", app " + appWidth + " x " + appHeight
- + ", real " + logicalWidth + " x " + logicalHeight
- + ", largest app " + largestNominalAppWidth + " x " + largestNominalAppHeight
- + ", smallest app " + smallestNominalAppWidth + " x " + smallestNominalAppHeight
- + ", " + refreshRate + " fps"
- + ", rotation " + rotation
- + ", density " + logicalDensityDpi
- + ", " + physicalXDpi + " x " + physicalYDpi + " dpi"
- + ", layerStack " + layerStack
- + ", type " + Display.typeToString(type)
- + ", address " + address
- + flagsToString(flags) + "}";
+ StringBuilder sb = new StringBuilder();
+ sb.append("DisplayInfo{\"");
+ sb.append(name);
+ sb.append("\", app ");
+ sb.append(appWidth);
+ sb.append(" x ");
+ sb.append(appHeight);
+ sb.append(", real ");
+ sb.append(logicalWidth);
+ sb.append(" x ");
+ sb.append(logicalHeight);
+ if (overscanLeft != 0 || overscanTop != 0 || overscanRight != 0 || overscanBottom != 0) {
+ sb.append(", overscan (");
+ sb.append(overscanLeft);
+ sb.append(",");
+ sb.append(overscanTop);
+ sb.append(",");
+ sb.append(overscanRight);
+ sb.append(",");
+ sb.append(overscanBottom);
+ sb.append(")");
+ }
+ sb.append(", largest app ");
+ sb.append(largestNominalAppWidth);
+ sb.append(" x ");
+ sb.append(largestNominalAppHeight);
+ sb.append(", smallest app ");
+ sb.append(smallestNominalAppWidth);
+ sb.append(" x ");
+ sb.append(smallestNominalAppHeight);
+ sb.append(", ");
+ sb.append(refreshRate);
+ sb.append(" fps, rotation");
+ sb.append(rotation);
+ sb.append(", density ");
+ sb.append(logicalDensityDpi);
+ sb.append(" (");
+ sb.append(physicalXDpi);
+ sb.append(" x ");
+ sb.append(physicalYDpi);
+ sb.append(") dpi, layerStack ");
+ sb.append(layerStack);
+ sb.append(", type ");
+ sb.append(Display.typeToString(type));
+ sb.append(", address ");
+ sb.append(address);
+ sb.append(flagsToString(flags));
+ sb.append("}");
+ return sb.toString();
}
private static String flagsToString(int flags) {
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index 5e34a36..3bad98e 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -19,21 +19,121 @@ package android.view;
import android.graphics.Matrix;
/**
- * A display lists records a series of graphics related operation and can replay
+ * <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
- * {@link android.graphics.Canvas}. Replaying the operations from a display list
- * avoids executing views drawing code on every frame, and is thus much more
- * efficient.
+ * {@link HardwareCanvas}. Replaying the operations from a display list avoids
+ * executing application code on every frame, and is thus much more efficient.</p>
*
- * @hide
+ * <p>Display lists are used internally for all views by default, and are not
+ * typically used directly. One reason to consider using a display is a custom
+ * {@link View} implementation that needs to issue a large number of drawing commands.
+ * When the view invalidates, all the drawing commands must be reissued, even if
+ * large portions of the drawing command stream stay the same frame to frame, which
+ * can become a performance bottleneck. To solve this issue, a custom View might split
+ * its content into several display lists. A display list is updated only when its
+ * content, and only its content, needs to be updated.</p>
+ *
+ * <p>A text editor might for instance store each paragraph into its own display list.
+ * Thus when the user inserts or removes characters, only the display list of the
+ * affected paragraph needs to be recorded again.</p>
+ *
+ * <h3>Hardware acceleration</h3>
+ * <p>Display lists can only be replayed using a {@link HardwareCanvas}. They are not
+ * supported in software. Always make sure that the {@link android.graphics.Canvas}
+ * you are using to render a display list is hardware accelerated using
+ * {@link android.graphics.Canvas#isHardwareAccelerated()}.</p>
+ *
+ * <h3>Creating a display list</h3>
+ * <pre class="prettyprint">
+ * HardwareRenderer renderer = myView.getHardwareRenderer();
+ * if (renderer != null) {
+ * DisplayList displayList = renderer.createDisplayList();
+ * HardwareCanvas canvas = displayList.start(width, height);
+ * try {
+ * // Draw onto the canvas
+ * // For instance: canvas.drawBitmap(...);
+ * } finally {
+ * displayList.end();
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Rendering a display list on a View</h3>
+ * <pre class="prettyprint">
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
+ * hardwareCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ * </pre>
+ *
+ * <h3>Releasing resources</h3>
+ * <p>This step is not mandatory but recommended if you want to release resources
+ * held by a display list as soon as possible.</p>
+ * <pre class="prettyprint">
+ * // Mark this display list invalid, it cannot be used for drawing anymore,
+ * // and release resources held by this display list
+ * displayList.clear();
+ * </pre>
+ *
+ * <h3>Properties</h3>
+ * <p>In addition, a display list offers several properties, such as
+ * {@link #setScaleX(float)} or {@link #setLeft(int)}, that can be used to affect all
+ * the drawing commands recorded within. For instance, these properties can be used
+ * to move around a large number of images without re-issuing all the individual
+ * <code>drawBitmap()</code> calls.</p>
+ *
+ * <pre class="prettyprint">
+ * private void createDisplayList() {
+ * HardwareRenderer renderer = getHardwareRenderer();
+ * if (renderer != null) {
+ * mDisplayList = renderer.createDisplayList();
+ * HardwareCanvas canvas = mDisplayList.start(width, height);
+ * try {
+ * for (Bitmap b : mBitmaps) {
+ * canvas.drawBitmap(b, 0.0f, 0.0f, null);
+ * canvas.translate(0.0f, b.getHeight());
+ * }
+ * } finally {
+ * displayList.end();
+ * }
+ * }
+ * }
+ *
+ * protected void onDraw(Canvas canvas) {
+ * if (canvas.isHardwareAccelerated()) {
+ * HardwareCanvas hardwareCanvas = (HardwareCanvas) canvas;
+ * hardwareCanvas.drawDisplayList(mDisplayList);
+ * }
+ * }
+ *
+ * private void moveContentBy(int x) {
+ * // This will move all the bitmaps recorded inside the display list
+ * // by x pixels to the right and redraw this view. All the commands
+ * // recorded in createDisplayList() won't be re-issued, only onDraw()
+ * // will be invoked and will execute very quickly
+ * mDisplayList.offsetLeftAndRight(x);
+ * invalidate();
+ * }
+ * </pre>
+ *
+ * <h3>Threading</h3>
+ * <p>Display lists must be created on and manipulated from the UI thread only.</p>
+ *
+ * @hide
*/
public abstract class DisplayList {
+ private boolean mDirty;
+
/**
* Flag used when calling
* {@link HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)}
* When this flag is set, draw operations lying outside of the bounds of the
* display list will be culled early. It is recommeneded to always set this
* flag.
+ *
+ * @hide
*/
public static final int FLAG_CLIP_CHILDREN = 0x1;
@@ -42,14 +142,18 @@ public abstract class DisplayList {
/**
* Indicates that the display list is done drawing.
*
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ *
+ * @hide
*/
public static final int STATUS_DONE = 0x0;
/**
* Indicates that the display list needs another drawing pass.
*
- * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ * @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ *
+ * @hide
*/
public static final int STATUS_DRAW = 0x1;
@@ -57,7 +161,9 @@ public abstract class DisplayList {
* Indicates that the display list needs to re-execute its GL functors.
*
* @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
- * @see HardwareCanvas#callDrawGLFunction(int)
+ * @see HardwareCanvas#callDrawGLFunction(int)
+ *
+ * @hide
*/
public static final int STATUS_INVOKE = 0x2;
@@ -65,35 +171,83 @@ public abstract class DisplayList {
* Indicates that the display list performed GL drawing operations.
*
* @see HardwareCanvas#drawDisplayList(DisplayList, android.graphics.Rect, int)
+ *
+ * @hide
*/
public static final int STATUS_DREW = 0x4;
/**
* Starts recording the display list. All operations performed on the
* returned canvas are recorded and stored in this display list.
- *
+ *
+ * Calling this method will mark the display list invalid until
+ * {@link #end()} is called. Only valid display lists can be replayed.
+ *
+ * @param width The width of the display list's viewport
+ * @param height The height of the display list's viewport
+ *
* @return A canvas to record drawing operations.
+ *
+ * @see #end()
+ * @see #isValid()
*/
- public abstract HardwareCanvas start();
+ public abstract HardwareCanvas start(int width, int height);
/**
* Ends the recording for this display list. A display list cannot be
- * replayed if recording is not finished.
+ * replayed if recording is not finished. Calling this method marks
+ * the display list valid and {@link #isValid()} will return true.
+ *
+ * @see #start(int, int)
+ * @see #isValid()
*/
public abstract void end();
/**
- * Invalidates the display list, indicating that it should be repopulated
- * with new drawing commands prior to being used again. Calling this method
- * causes calls to {@link #isValid()} to return <code>false</code>.
+ * Clears resources held onto by this display list. After calling this method
+ * {@link #isValid()} will return false.
+ *
+ * @see #isValid()
+ */
+ public abstract void clear();
+
+ /**
+ * Sets the dirty flag. When a display list is dirty, {@link #clear()} should
+ * be invoked whenever possible.
+ *
+ * @see #isDirty()
+ * @see #clear()
+ *
+ * @hide
*/
- public abstract void invalidate();
+ public void markDirty() {
+ mDirty = true;
+ }
/**
- * Clears additional resources held onto by this display list. You should
- * only invoke this method after {@link #invalidate()}.
+ * Removes the dirty flag. This method can be used to cancel a cleanup
+ * previously scheduled by setting the dirty flag.
+ *
+ * @see #isDirty()
+ * @see #clear()
+ *
+ * @hide
*/
- public abstract void clear();
+ protected void clearDirty() {
+ mDirty = false;
+ }
+
+ /**
+ * Indicates whether the display list is dirty.
+ *
+ * @see #markDirty()
+ * @see #clear()
+ *
+ * @hide
+ */
+ public boolean isDirty() {
+ return mDirty;
+ }
/**
* Returns whether the display list is currently usable. If this returns false,
@@ -107,6 +261,8 @@ public abstract class DisplayList {
* Return the amount of memory used by this display list.
*
* @return The size of this display list in bytes
+ *
+ * @hide
*/
public abstract int getSize();
@@ -115,228 +271,412 @@ public abstract class DisplayList {
///////////////////////////////////////////////////////////////////////////
/**
- * Set the caching property on the DisplayList, which indicates whether the DisplayList
- * holds a layer. Layer DisplayLists should avoid creating an alpha layer, since alpha is
+ * Set the caching property on the display list, which indicates whether the display list
+ * holds a layer. Layer display lists should avoid creating an alpha layer, since alpha is
* handled in the drawLayer operation directly (and more efficiently).
*
- * @param caching true if the DisplayList represents a hardware layer, false otherwise.
+ * @param caching true if the display list represents a hardware layer, false otherwise.
+ *
+ * @hide
*/
public abstract void setCaching(boolean caching);
/**
- * Set whether the DisplayList should clip itself to its bounds. This property is controlled by
+ * Set whether the display list should clip itself to its bounds. This property is controlled by
* the view's parent.
*
- * @param clipChildren true if the DisplayList should clip to its bounds
+ * @param clipChildren true if the display list should clip to its bounds
*/
public abstract void setClipChildren(boolean clipChildren);
/**
- * Set the static matrix on the DisplayList. This matrix exists if a custom ViewGroup
- * overrides
- * {@link ViewGroup#getChildStaticTransformation(View, android.view.animation.Transformation)}
- * and also has {@link ViewGroup#setStaticTransformationsEnabled(boolean)} set to true.
- * This matrix will be concatenated with any other matrices in the DisplayList to position
- * the view appropriately.
+ * Set the static matrix on the display list. The specified matrix is combined with other
+ * transforms (such as {@link #setScaleX(float)}, {@link #setRotation(float)}, etc.)
+ *
+ * @param matrix A transform matrix to apply to this display list
+ *
+ * @see #getMatrix(android.graphics.Matrix)
+ * @see #getMatrix()
+ */
+ public abstract void setMatrix(Matrix matrix);
+
+ /**
+ * Returns the static matrix set on this display list.
+ *
+ * @return A new {@link Matrix} instance populated with this display list's static
+ * matrix
+ *
+ * @see #getMatrix(android.graphics.Matrix)
+ * @see #setMatrix(android.graphics.Matrix)
+ */
+ public Matrix getMatrix() {
+ return getMatrix(new Matrix());
+ }
+
+ /**
+ * Copies this display list's static matrix into the specified matrix.
+ *
+ * @param matrix The {@link Matrix} instance in which to copy this display
+ * list's static matrix. Cannot be null
*
- * @param matrix The matrix
+ * @return The <code>matrix</code> parameter, for convenience
+ *
+ * @see #getMatrix()
+ * @see #setMatrix(android.graphics.Matrix)
*/
- public abstract void setStaticMatrix(Matrix matrix);
+ public abstract Matrix getMatrix(Matrix matrix);
/**
- * Set the Animation matrix on the DisplayList. This matrix exists if an Animation is
- * currently playing on a View, and is set on the DisplayList during at draw() time. When
+ * Set the Animation matrix on the display list. This matrix exists if an Animation is
+ * currently playing on a View, and is set on the display list during at draw() time. When
* the Animation finishes, the matrix should be cleared by sending <code>null</code>
* for the matrix parameter.
*
* @param matrix The matrix, null indicates that the matrix should be cleared.
+ *
+ * @hide
*/
public abstract void setAnimationMatrix(Matrix matrix);
/**
- * Sets the alpha value for the DisplayList
+ * Sets the translucency level for the display list.
+ *
+ * @param alpha The translucency of the display list, must be a value between 0.0f and 1.0f
*
- * @param alpha The translucency of the DisplayList
* @see View#setAlpha(float)
+ * @see #getAlpha()
*/
public abstract void setAlpha(float alpha);
/**
- * Sets whether the DisplayList renders content which overlaps. Non-overlapping rendering
- * can use a fast path for alpha that avoids rendering to an offscreen buffer.
+ * Returns the translucency level of this display list.
+ *
+ * @return A value between 0.0f and 1.0f
+ *
+ * @see #setAlpha(float)
+ */
+ public abstract float getAlpha();
+
+ /**
+ * Sets whether the display list renders content which overlaps. Non-overlapping rendering
+ * can use a fast path for alpha that avoids rendering to an offscreen buffer. By default
+ * display lists consider they do not have overlapping content.
+ *
+ * @param hasOverlappingRendering False if the content is guaranteed to be non-overlapping,
+ * true otherwise.
*
- * @param hasOverlappingRendering
* @see android.view.View#hasOverlappingRendering()
+ * @see #hasOverlappingRendering()
*/
public abstract void setHasOverlappingRendering(boolean hasOverlappingRendering);
/**
- * Sets the translationX value for the DisplayList
+ * Indicates whether the content of this display list overlaps.
+ *
+ * @return True if this display list renders content which overlaps, false otherwise.
+ *
+ * @see #setHasOverlappingRendering(boolean)
+ */
+ public abstract boolean hasOverlappingRendering();
+
+ /**
+ * Sets the translation value for the display list on the X axis
+ *
+ * @param translationX The X axis translation value of the display list, in pixels
*
- * @param translationX The translationX value of the DisplayList
* @see View#setTranslationX(float)
+ * @see #getTranslationX()
*/
public abstract void setTranslationX(float translationX);
/**
- * Sets the translationY value for the DisplayList
+ * Returns the translation value for this display list on the X axis, in pixels.
+ *
+ * @see #setTranslationX(float)
+ */
+ public abstract float getTranslationX();
+
+ /**
+ * Sets the translation value for the display list on the Y axis
+ *
+ * @param translationY The Y axis translation value of the display list, in pixels
*
- * @param translationY The translationY value of the DisplayList
* @see View#setTranslationY(float)
+ * @see #getTranslationY()
*/
public abstract void setTranslationY(float translationY);
/**
- * Sets the rotation value for the DisplayList
+ * Returns the translation value for this display list on the Y axis, in pixels.
+ *
+ * @see #setTranslationY(float)
+ */
+ public abstract float getTranslationY();
+
+ /**
+ * Sets the rotation value for the display list around the Z axis
+ *
+ * @param rotation The rotation value of the display list, in degrees
*
- * @param rotation The rotation value of the DisplayList
* @see View#setRotation(float)
+ * @see #getRotation()
*/
public abstract void setRotation(float rotation);
/**
- * Sets the rotationX value for the DisplayList
+ * Returns the rotation value for this display list around the Z axis, in degrees.
+ *
+ * @see #setRotation(float)
+ */
+ public abstract float getRotation();
+
+ /**
+ * Sets the rotation value for the display list around the X axis
+ *
+ * @param rotationX The rotation value of the display list, in degrees
*
- * @param rotationX The rotationX value of the DisplayList
* @see View#setRotationX(float)
+ * @see #getRotationX()
*/
public abstract void setRotationX(float rotationX);
/**
- * Sets the rotationY value for the DisplayList
+ * Returns the rotation value for this display list around the X axis, in degrees.
+ *
+ * @see #setRotationX(float)
+ */
+ public abstract float getRotationX();
+
+ /**
+ * Sets the rotation value for the display list around the Y axis
+ *
+ * @param rotationY The rotation value of the display list, in degrees
*
- * @param rotationY The rotationY value of the DisplayList
* @see View#setRotationY(float)
+ * @see #getRotationY()
*/
public abstract void setRotationY(float rotationY);
/**
- * Sets the scaleX value for the DisplayList
+ * Returns the rotation value for this display list around the Y axis, in degrees.
+ *
+ * @see #setRotationY(float)
+ */
+ public abstract float getRotationY();
+
+ /**
+ * Sets the scale value for the display list on the X axis
+ *
+ * @param scaleX The scale value of the display list
*
- * @param scaleX The scaleX value of the DisplayList
* @see View#setScaleX(float)
+ * @see #getScaleX()
*/
public abstract void setScaleX(float scaleX);
/**
- * Sets the scaleY value for the DisplayList
+ * Returns the scale value for this display list on the X axis.
+ *
+ * @see #setScaleX(float)
+ */
+ public abstract float getScaleX();
+
+ /**
+ * Sets the scale value for the display list on the Y axis
+ *
+ * @param scaleY The scale value of the display list
*
- * @param scaleY The scaleY value of the DisplayList
* @see View#setScaleY(float)
+ * @see #getScaleY()
*/
public abstract void setScaleY(float scaleY);
/**
- * Sets all of the transform-related values of the View onto the DisplayList
+ * Returns the scale value for this display list on the Y axis.
*
- * @param alpha The alpha value of the DisplayList
- * @param translationX The translationX value of the DisplayList
- * @param translationY The translationY value of the DisplayList
- * @param rotation The rotation value of the DisplayList
- * @param rotationX The rotationX value of the DisplayList
- * @param rotationY The rotationY value of the DisplayList
- * @param scaleX The scaleX value of the DisplayList
- * @param scaleY The scaleY value of the DisplayList
+ * @see #setScaleY(float)
+ */
+ public abstract float getScaleY();
+
+ /**
+ * Sets all of the transform-related values of the display list
+ *
+ * @param alpha The alpha value of the display list
+ * @param translationX The translationX value of the display list
+ * @param translationY The translationY value of the display list
+ * @param rotation The rotation value of the display list
+ * @param rotationX The rotationX value of the display list
+ * @param rotationY The rotationY value of the display list
+ * @param scaleX The scaleX value of the display list
+ * @param scaleY The scaleY value of the display list
+ *
+ * @hide
*/
public abstract void setTransformationInfo(float alpha, float translationX, float translationY,
float rotation, float rotationX, float rotationY, float scaleX, float scaleY);
/**
- * Sets the pivotX value for the DisplayList
+ * Sets the pivot value for the display list on the X axis
+ *
+ * @param pivotX The pivot value of the display list on the X axis, in pixels
*
- * @param pivotX The pivotX value of the DisplayList
* @see View#setPivotX(float)
+ * @see #getPivotX()
*/
public abstract void setPivotX(float pivotX);
/**
- * Sets the pivotY value for the DisplayList
+ * Returns the pivot value for this display list on the X axis, in pixels.
+ *
+ * @see #setPivotX(float)
+ */
+ public abstract float getPivotX();
+
+ /**
+ * Sets the pivot value for the display list on the Y axis
+ *
+ * @param pivotY The pivot value of the display list on the Y axis, in pixels
*
- * @param pivotY The pivotY value of the DisplayList
* @see View#setPivotY(float)
+ * @see #getPivotY()
*/
public abstract void setPivotY(float pivotY);
/**
- * Sets the camera distance for the DisplayList
+ * Returns the pivot value for this display list on the Y axis, in pixels.
+ *
+ * @see #setPivotY(float)
+ */
+ public abstract float getPivotY();
+
+ /**
+ * Sets the camera distance for the display list. Refer to
+ * {@link View#setCameraDistance(float)} for more information on how to
+ * use this property.
+ *
+ * @param distance The distance in Z of the camera of the display list
*
- * @param distance The distance in z of the camera of the DisplayList
* @see View#setCameraDistance(float)
+ * @see #getCameraDistance()
*/
public abstract void setCameraDistance(float distance);
/**
- * Sets the left value for the DisplayList
+ * Returns the distance in Z of the camera of the display list.
+ *
+ * @see #setCameraDistance(float)
+ */
+ public abstract float getCameraDistance();
+
+ /**
+ * Sets the left position for the display list.
+ *
+ * @param left The left position, in pixels, of the display list
*
- * @param left The left value of the DisplayList
* @see View#setLeft(int)
+ * @see #getLeft()
*/
public abstract void setLeft(int left);
/**
- * Sets the top value for the DisplayList
+ * Returns the left position for the display list in pixels.
+ *
+ * @see #setLeft(int)
+ */
+ public abstract float getLeft();
+
+ /**
+ * Sets the top position for the display list.
+ *
+ * @param top The top position, in pixels, of the display list
*
- * @param top The top value of the DisplayList
* @see View#setTop(int)
+ * @see #getTop()
*/
public abstract void setTop(int top);
/**
- * Sets the right value for the DisplayList
+ * Returns the top position for the display list in pixels.
+ *
+ * @see #setTop(int)
+ */
+ public abstract float getTop();
+
+ /**
+ * Sets the right position for the display list.
+ *
+ * @param right The right position, in pixels, of the display list
*
- * @param right The right value of the DisplayList
* @see View#setRight(int)
+ * @see #getRight()
*/
public abstract void setRight(int right);
/**
- * Sets the bottom value for the DisplayList
+ * Returns the right position for the display list in pixels.
+ *
+ * @see #setRight(int)
+ */
+ public abstract float getRight();
+
+ /**
+ * Sets the bottom position for the display list.
+ *
+ * @param bottom The bottom position, in pixels, of the display list
*
- * @param bottom The bottom value of the DisplayList
* @see View#setBottom(int)
+ * @see #getBottom()
*/
public abstract void setBottom(int bottom);
/**
- * Sets the left and top values for the DisplayList
+ * Returns the bottom position for the display list in pixels.
*
- * @param left The left value of the DisplayList
- * @param top The top value of the DisplayList
- * @see View#setLeft(int)
- * @see View#setTop(int)
+ * @see #setBottom(int)
*/
- public abstract void setLeftTop(int left, int top);
+ public abstract float getBottom();
/**
- * Sets the left and top values for the DisplayList
+ * Sets the left and top positions for the display list
+ *
+ * @param left The left position of the display list, in pixels
+ * @param top The top position of the display list, in pixels
+ * @param right The right position of the display list, in pixels
+ * @param bottom The bottom position of the display list, in pixels
*
- * @param left The left value of the DisplayList
- * @param top The top value of the DisplayList
* @see View#setLeft(int)
* @see View#setTop(int)
+ * @see View#setRight(int)
+ * @see View#setBottom(int)
*/
public abstract void setLeftTopRightBottom(int left, int top, int right, int bottom);
/**
- * Offsets the left and right values for the DisplayList
+ * Offsets the left and right positions for the display list
+ *
+ * @param offset The amount that the left and right positions of the display
+ * list are offset, in pixels
*
- * @param offset The amount that the left and right values of the DisplayList are offset
* @see View#offsetLeftAndRight(int)
*/
- public abstract void offsetLeftRight(int offset);
+ public abstract void offsetLeftAndRight(float offset);
/**
- * Offsets the top and bottom values for the DisplayList
+ * Offsets the top and bottom values for the display list
+ *
+ * @param offset The amount that the top and bottom positions of the display
+ * list are offset, in pixels
*
- * @param offset The amount that the top and bottom values of the DisplayList are offset
* @see View#offsetTopAndBottom(int)
*/
- public abstract void offsetTopBottom(int offset);
+ public abstract void offsetTopAndBottom(float offset);
/**
- * Reset native resources. This is called when cleaning up the state of DisplayLists
+ * Reset native resources. This is called when cleaning up the state of display lists
* during destruction of hardware resources, to ensure that we do not hold onto
* obsolete resources after related resources are gone.
+ *
+ * @hide
*/
public abstract void reset();
}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index b64a06e..6c48e43 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -144,6 +144,14 @@ class GLES20Canvas extends HardwareCanvas {
}
}
+ @Override
+ public void setName(String name) {
+ super.setName(name);
+ nSetName(mRenderer, name);
+ }
+
+ private static native void nSetName(int renderer, String name);
+
///////////////////////////////////////////////////////////////////////////
// Hardware layers
///////////////////////////////////////////////////////////////////////////
@@ -369,24 +377,13 @@ class GLES20Canvas extends HardwareCanvas {
}
private static native int nGetDisplayList(int renderer, int displayList);
-
- static void destroyDisplayList(int displayList) {
- nDestroyDisplayList(displayList);
- }
- private static native void nDestroyDisplayList(int displayList);
-
- static int getDisplayListSize(int displayList) {
- return nGetDisplayListSize(displayList);
- }
-
- private static native int nGetDisplayListSize(int displayList);
-
- static void setDisplayListName(int displayList, String name) {
- nSetDisplayListName(displayList, name);
+ @Override
+ void outputDisplayList(DisplayList displayList) {
+ nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList());
}
- private static native void nSetDisplayListName(int displayList, String name);
+ private static native void nOutputDisplayList(int renderer, int displayList);
@Override
public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
@@ -397,13 +394,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native int nDrawDisplayList(int renderer, int displayList,
Rect dirty, int flags);
- @Override
- void outputDisplayList(DisplayList displayList) {
- nOutputDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList());
- }
-
- private static native void nOutputDisplayList(int renderer, int displayList);
-
///////////////////////////////////////////////////////////////////////////
// Hardware layer
///////////////////////////////////////////////////////////////////////////
@@ -433,20 +423,16 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public boolean clipPath(Path path) {
- // TODO: Implement
- path.computeBounds(mPathBounds, true);
- return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top,
- mPathBounds.right, mPathBounds.bottom, Region.Op.INTERSECT.nativeInt);
+ return nClipPath(mRenderer, path.mNativePath, Region.Op.INTERSECT.nativeInt);
}
@Override
public boolean clipPath(Path path, Region.Op op) {
- // TODO: Implement
- path.computeBounds(mPathBounds, true);
- return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top,
- mPathBounds.right, mPathBounds.bottom, op.nativeInt);
+ return nClipPath(mRenderer, path.mNativePath, op.nativeInt);
}
+ private static native boolean nClipPath(int renderer, int path, int op);
+
@Override
public boolean clipRect(float left, float top, float right, float bottom) {
return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
@@ -465,8 +451,8 @@ class GLES20Canvas extends HardwareCanvas {
return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
}
- private static native boolean nClipRect(int renderer, int left, int top, int right, int bottom,
- int op);
+ private static native boolean nClipRect(int renderer, int left, int top,
+ int right, int bottom, int op);
@Override
public boolean clipRect(Rect rect) {
@@ -492,20 +478,16 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public boolean clipRegion(Region region) {
- // TODO: Implement
- region.getBounds(mClipBounds);
- return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top,
- mClipBounds.right, mClipBounds.bottom, Region.Op.INTERSECT.nativeInt);
+ return nClipRegion(mRenderer, region.mNativeRegion, Region.Op.INTERSECT.nativeInt);
}
@Override
public boolean clipRegion(Region region, Region.Op op) {
- // TODO: Implement
- region.getBounds(mClipBounds);
- return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top,
- mClipBounds.right, mClipBounds.bottom, op.nativeInt);
+ return nClipRegion(mRenderer, region.mNativeRegion, op.nativeInt);
}
+ private static native boolean nClipRegion(int renderer, int region, int op);
+
@Override
public boolean getClipBounds(Rect bounds) {
return nGetClipBounds(mRenderer, bounds);
@@ -515,22 +497,22 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) {
- return nQuickReject(mRenderer, left, top, right, bottom, type.nativeInt);
+ return nQuickReject(mRenderer, left, top, right, bottom);
}
private static native boolean nQuickReject(int renderer, float left, float top,
- float right, float bottom, int edge);
+ float right, float bottom);
@Override
public boolean quickReject(Path path, EdgeType type) {
path.computeBounds(mPathBounds, true);
return nQuickReject(mRenderer, mPathBounds.left, mPathBounds.top,
- mPathBounds.right, mPathBounds.bottom, type.nativeInt);
+ mPathBounds.right, mPathBounds.bottom);
}
@Override
public boolean quickReject(RectF rect, EdgeType type) {
- return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom, type.nativeInt);
+ return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom);
}
///////////////////////////////////////////////////////////////////////////
@@ -901,9 +883,9 @@ class GLES20Canvas extends HardwareCanvas {
final int count = (meshWidth + 1) * (meshHeight + 1);
checkRange(verts.length, vertOffset, count * 2);
- // TODO: Colors are ignored for now
- colors = null;
- colorOffset = 0;
+ if (colors != null) {
+ checkRange(colors.length, colorOffset, count);
+ }
int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE;
try {
@@ -955,6 +937,8 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawLines(float[] pts, int offset, int count, Paint paint) {
+ if (count < 4) return;
+
if ((offset | count) < 0 || offset + count > pts.length) {
throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
}
@@ -1013,6 +997,17 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawPath(int renderer, int path, int paint);
private static native void nDrawRects(int renderer, int region, int paint);
+ void drawRects(float[] rects, int count, Paint paint) {
+ int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
+ try {
+ nDrawRects(mRenderer, rects, count, paint.mNativePaint);
+ } finally {
+ if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
+ }
+ }
+
+ private static native void nDrawRects(int renderer, float[] rects, int count, int paint);
+
@Override
public void drawPicture(Picture picture) {
if (picture.createdFromStream) {
@@ -1067,6 +1062,8 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void drawPoints(float[] pts, int offset, int count, Paint paint) {
+ if (count < 2) return;
+
int modifiers = setupModifiers(paint, MODIFIER_COLOR_FILTER | MODIFIER_SHADER);
try {
nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
@@ -1165,14 +1162,14 @@ class GLES20Canvas extends HardwareCanvas {
int modifiers = setupModifiers(paint);
try {
- nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
+ nDrawText(mRenderer, text, index, count, x, y, paint.mNativePaint);
} finally {
if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
private static native void nDrawText(int renderer, char[] text, int index, int count,
- float x, float y, int bidiFlags, int paint);
+ float x, float y, int paint);
@Override
public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
@@ -1180,16 +1177,14 @@ class GLES20Canvas extends HardwareCanvas {
try {
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
- nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
- paint.mNativePaint);
+ nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mNativePaint);
} else if (text instanceof GraphicsOperations) {
((GraphicsOperations) text).drawText(this, start, end, x, y,
paint);
} else {
char[] buf = TemporaryBuffer.obtain(end - start);
TextUtils.getChars(text, start, end, buf, 0);
- nDrawText(mRenderer, buf, 0, end - start, x, y,
- paint.mBidiFlags, paint.mNativePaint);
+ nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mNativePaint);
TemporaryBuffer.recycle(buf);
}
} finally {
@@ -1205,21 +1200,20 @@ class GLES20Canvas extends HardwareCanvas {
int modifiers = setupModifiers(paint);
try {
- nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
+ nDrawText(mRenderer, text, start, end, x, y, paint.mNativePaint);
} finally {
if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
private static native void nDrawText(int renderer, String text, int start, int end,
- float x, float y, int bidiFlags, int paint);
+ float x, float y, int paint);
@Override
public void drawText(String text, float x, float y, Paint paint) {
int modifiers = setupModifiers(paint);
try {
- nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
- paint.mNativePaint);
+ nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mNativePaint);
} finally {
if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
@@ -1235,14 +1229,14 @@ class GLES20Canvas extends HardwareCanvas {
int modifiers = setupModifiers(paint);
try {
nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint);
+ paint.mNativePaint);
} finally {
if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
private static native void nDrawTextOnPath(int renderer, char[] text, int index, int count,
- int path, float hOffset, float vOffset, int bidiFlags, int nativePaint);
+ int path, float hOffset, float vOffset, int nativePaint);
@Override
public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
@@ -1251,28 +1245,25 @@ class GLES20Canvas extends HardwareCanvas {
int modifiers = setupModifiers(paint);
try {
nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint);
+ paint.mNativePaint);
} finally {
if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
}
}
private static native void nDrawTextOnPath(int renderer, String text, int start, int end,
- int path, float hOffset, float vOffset, int bidiFlags, int nativePaint);
+ int path, float hOffset, float vOffset, int nativePaint);
@Override
public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
- float x, float y, int dir, Paint paint) {
+ float x, float y, Paint paint) {
if ((index | count | text.length - index - count) < 0) {
throw new IndexOutOfBoundsException();
}
- if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
- throw new IllegalArgumentException("Unknown direction: " + dir);
- }
int modifiers = setupModifiers(paint);
try {
- nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
+ nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y,
paint.mNativePaint);
} finally {
if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers);
@@ -1280,32 +1271,31 @@ class GLES20Canvas extends HardwareCanvas {
}
private static native void nDrawTextRun(int renderer, char[] text, int index, int count,
- int contextIndex, int contextCount, float x, float y, int dir, int nativePaint);
+ int contextIndex, int contextCount, float x, float y, int nativePaint);
@Override
public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
- float x, float y, int dir, Paint paint) {
+ float x, float y, Paint paint) {
if ((start | end | end - start | text.length() - end) < 0) {
throw new IndexOutOfBoundsException();
}
int modifiers = setupModifiers(paint);
try {
- int flags = dir == 0 ? 0 : 1;
if (text instanceof String || text instanceof SpannedString ||
text instanceof SpannableString) {
nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
- contextEnd, x, y, flags, paint.mNativePaint);
+ contextEnd, x, y, paint.mNativePaint);
} else if (text instanceof GraphicsOperations) {
((GraphicsOperations) text).drawTextRun(this, start, end,
- contextStart, contextEnd, x, y, flags, paint);
+ contextStart, contextEnd, x, y, paint);
} else {
int contextLen = contextEnd - contextStart;
int len = end - start;
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
- x, y, flags, paint.mNativePaint);
+ x, y, paint.mNativePaint);
TemporaryBuffer.recycle(buf);
}
} finally {
@@ -1314,7 +1304,7 @@ class GLES20Canvas extends HardwareCanvas {
}
private static native void nDrawTextRun(int renderer, String text, int start, int end,
- int contextStart, int contextEnd, float x, float y, int flags, int nativePaint);
+ int contextStart, int contextEnd, float x, float y, int nativePaint);
@Override
public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index e9bd0c4..9c5cdb2 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -58,7 +58,7 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public HardwareCanvas start() {
+ public HardwareCanvas start(int width, int height) {
if (mCanvas != null) {
throw new IllegalStateException("Recording has already started");
}
@@ -66,24 +66,25 @@ class GLES20DisplayList extends DisplayList {
mValid = false;
mCanvas = GLES20RecordingCanvas.obtain(this);
mCanvas.start();
+
+ mCanvas.setViewport(width, height);
+ // The dirty rect should always be null for a display list
+ mCanvas.onPreDraw(null);
+
return mCanvas;
}
-
@Override
- public void invalidate() {
+ public void clear() {
+ clearDirty();
+
if (mCanvas != null) {
mCanvas.recycle();
mCanvas = null;
}
mValid = false;
- }
- @Override
- public void clear() {
- if (!mValid) {
- mBitmaps.clear();
- mChildDisplayLists.clear();
- }
+ mBitmaps.clear();
+ mChildDisplayLists.clear();
}
@Override
@@ -101,11 +102,12 @@ class GLES20DisplayList extends DisplayList {
@Override
public void end() {
if (mCanvas != null) {
+ mCanvas.onPostDraw();
if (mFinalizer != null) {
mCanvas.end(mFinalizer.mNativeDisplayList);
} else {
mFinalizer = new DisplayListFinalizer(mCanvas.end(0));
- GLES20Canvas.setDisplayListName(mFinalizer.mNativeDisplayList, mName);
+ nSetDisplayListName(mFinalizer.mNativeDisplayList, mName);
}
mCanvas.recycle();
mCanvas = null;
@@ -116,9 +118,13 @@ class GLES20DisplayList extends DisplayList {
@Override
public int getSize() {
if (mFinalizer == null) return 0;
- return GLES20Canvas.getDisplayListSize(mFinalizer.mNativeDisplayList);
+ return nGetDisplayListSize(mFinalizer.mNativeDisplayList);
}
+ private static native void nDestroyDisplayList(int displayList);
+ private static native int nGetDisplayListSize(int displayList);
+ private static native void nSetDisplayListName(int displayList, String name);
+
///////////////////////////////////////////////////////////////////////////
// Native View Properties
///////////////////////////////////////////////////////////////////////////
@@ -138,13 +144,21 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public void setStaticMatrix(Matrix matrix) {
+ public void setMatrix(Matrix matrix) {
if (hasNativeDisplayList()) {
nSetStaticMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance);
}
}
@Override
+ public Matrix getMatrix(Matrix matrix) {
+ if (hasNativeDisplayList()) {
+ nGetMatrix(mFinalizer.mNativeDisplayList, matrix.native_instance);
+ }
+ return matrix;
+ }
+
+ @Override
public void setAnimationMatrix(Matrix matrix) {
if (hasNativeDisplayList()) {
nSetAnimationMatrix(mFinalizer.mNativeDisplayList,
@@ -160,6 +174,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getAlpha() {
+ if (hasNativeDisplayList()) {
+ return nGetAlpha(mFinalizer.mNativeDisplayList);
+ }
+ return 1.0f;
+ }
+
+ @Override
public void setHasOverlappingRendering(boolean hasOverlappingRendering) {
if (hasNativeDisplayList()) {
nSetHasOverlappingRendering(mFinalizer.mNativeDisplayList, hasOverlappingRendering);
@@ -167,6 +189,15 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public boolean hasOverlappingRendering() {
+ //noinspection SimplifiableIfStatement
+ if (hasNativeDisplayList()) {
+ return nHasOverlappingRendering(mFinalizer.mNativeDisplayList);
+ }
+ return true;
+ }
+
+ @Override
public void setTranslationX(float translationX) {
if (hasNativeDisplayList()) {
nSetTranslationX(mFinalizer.mNativeDisplayList, translationX);
@@ -174,6 +205,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getTranslationX() {
+ if (hasNativeDisplayList()) {
+ return nGetTranslationX(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setTranslationY(float translationY) {
if (hasNativeDisplayList()) {
nSetTranslationY(mFinalizer.mNativeDisplayList, translationY);
@@ -181,6 +220,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getTranslationY() {
+ if (hasNativeDisplayList()) {
+ return nGetTranslationY(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRotation(float rotation) {
if (hasNativeDisplayList()) {
nSetRotation(mFinalizer.mNativeDisplayList, rotation);
@@ -188,6 +235,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRotation() {
+ if (hasNativeDisplayList()) {
+ return nGetRotation(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRotationX(float rotationX) {
if (hasNativeDisplayList()) {
nSetRotationX(mFinalizer.mNativeDisplayList, rotationX);
@@ -195,6 +250,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRotationX() {
+ if (hasNativeDisplayList()) {
+ return nGetRotationX(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRotationY(float rotationY) {
if (hasNativeDisplayList()) {
nSetRotationY(mFinalizer.mNativeDisplayList, rotationY);
@@ -202,6 +265,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRotationY() {
+ if (hasNativeDisplayList()) {
+ return nGetRotationY(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setScaleX(float scaleX) {
if (hasNativeDisplayList()) {
nSetScaleX(mFinalizer.mNativeDisplayList, scaleX);
@@ -209,6 +280,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getScaleX() {
+ if (hasNativeDisplayList()) {
+ return nGetScaleX(mFinalizer.mNativeDisplayList);
+ }
+ return 1.0f;
+ }
+
+ @Override
public void setScaleY(float scaleY) {
if (hasNativeDisplayList()) {
nSetScaleY(mFinalizer.mNativeDisplayList, scaleY);
@@ -216,6 +295,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getScaleY() {
+ if (hasNativeDisplayList()) {
+ return nGetScaleY(mFinalizer.mNativeDisplayList);
+ }
+ return 1.0f;
+ }
+
+ @Override
public void setTransformationInfo(float alpha, float translationX, float translationY,
float rotation, float rotationX, float rotationY, float scaleX, float scaleY) {
if (hasNativeDisplayList()) {
@@ -232,6 +319,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getPivotX() {
+ if (hasNativeDisplayList()) {
+ return nGetPivotX(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setPivotY(float pivotY) {
if (hasNativeDisplayList()) {
nSetPivotY(mFinalizer.mNativeDisplayList, pivotY);
@@ -239,6 +334,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getPivotY() {
+ if (hasNativeDisplayList()) {
+ return nGetPivotY(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setCameraDistance(float distance) {
if (hasNativeDisplayList()) {
nSetCameraDistance(mFinalizer.mNativeDisplayList, distance);
@@ -246,6 +349,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getCameraDistance() {
+ if (hasNativeDisplayList()) {
+ return nGetCameraDistance(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setLeft(int left) {
if (hasNativeDisplayList()) {
nSetLeft(mFinalizer.mNativeDisplayList, left);
@@ -253,6 +364,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getLeft() {
+ if (hasNativeDisplayList()) {
+ return nGetLeft(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setTop(int top) {
if (hasNativeDisplayList()) {
nSetTop(mFinalizer.mNativeDisplayList, top);
@@ -260,6 +379,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getTop() {
+ if (hasNativeDisplayList()) {
+ return nGetTop(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setRight(int right) {
if (hasNativeDisplayList()) {
nSetRight(mFinalizer.mNativeDisplayList, right);
@@ -267,6 +394,14 @@ class GLES20DisplayList extends DisplayList {
}
@Override
+ public float getRight() {
+ if (hasNativeDisplayList()) {
+ return nGetRight(mFinalizer.mNativeDisplayList);
+ }
+ return 0.0f;
+ }
+
+ @Override
public void setBottom(int bottom) {
if (hasNativeDisplayList()) {
nSetBottom(mFinalizer.mNativeDisplayList, bottom);
@@ -274,10 +409,11 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public void setLeftTop(int left, int top) {
+ public float getBottom() {
if (hasNativeDisplayList()) {
- nSetLeftTop(mFinalizer.mNativeDisplayList, left, top);
+ return nGetBottom(mFinalizer.mNativeDisplayList);
}
+ return 0.0f;
}
@Override
@@ -288,25 +424,24 @@ class GLES20DisplayList extends DisplayList {
}
@Override
- public void offsetLeftRight(int offset) {
+ public void offsetLeftAndRight(float offset) {
if (hasNativeDisplayList()) {
- nOffsetLeftRight(mFinalizer.mNativeDisplayList, offset);
+ nOffsetLeftAndRight(mFinalizer.mNativeDisplayList, offset);
}
}
@Override
- public void offsetTopBottom(int offset) {
+ public void offsetTopAndBottom(float offset) {
if (hasNativeDisplayList()) {
- nOffsetTopBottom(mFinalizer.mNativeDisplayList, offset);
+ nOffsetTopAndBottom(mFinalizer.mNativeDisplayList, offset);
}
}
private static native void nReset(int displayList);
- private static native void nOffsetTopBottom(int displayList, int offset);
- private static native void nOffsetLeftRight(int displayList, int offset);
+ private static native void nOffsetTopAndBottom(int displayList, float offset);
+ private static native void nOffsetLeftAndRight(int displayList, float offset);
private static native void nSetLeftTopRightBottom(int displayList, int left, int top,
int right, int bottom);
- private static native void nSetLeftTop(int displayList, int left, int top);
private static native void nSetBottom(int displayList, int bottom);
private static native void nSetRight(int displayList, int right);
private static native void nSetTop(int displayList, int top);
@@ -332,6 +467,23 @@ class GLES20DisplayList extends DisplayList {
private static native void nSetStaticMatrix(int displayList, int nativeMatrix);
private static native void nSetAnimationMatrix(int displayList, int animationMatrix);
+ private static native boolean nHasOverlappingRendering(int displayList);
+ private static native void nGetMatrix(int displayList, int matrix);
+ private static native float nGetAlpha(int displayList);
+ private static native float nGetLeft(int displayList);
+ private static native float nGetTop(int displayList);
+ private static native float nGetRight(int displayList);
+ private static native float nGetBottom(int displayList);
+ private static native float nGetCameraDistance(int displayList);
+ private static native float nGetScaleX(int displayList);
+ private static native float nGetScaleY(int displayList);
+ private static native float nGetTranslationX(int displayList);
+ private static native float nGetTranslationY(int displayList);
+ private static native float nGetRotation(int displayList);
+ private static native float nGetRotationX(int displayList);
+ private static native float nGetRotationY(int displayList);
+ private static native float nGetPivotX(int displayList);
+ private static native float nGetPivotY(int displayList);
///////////////////////////////////////////////////////////////////////////
// Finalization
@@ -347,7 +499,7 @@ class GLES20DisplayList extends DisplayList {
@Override
protected void finalize() throws Throwable {
try {
- GLES20Canvas.destroyDisplayList(mNativeDisplayList);
+ nDestroyDisplayList(mNativeDisplayList);
} finally {
super.finalize();
}
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 812fb97..7ee628b 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -53,12 +53,12 @@ abstract class GLES20Layer extends HardwareLayer {
}
@Override
- boolean copyInto(Bitmap bitmap) {
+ public boolean copyInto(Bitmap bitmap) {
return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
}
@Override
- void destroy() {
+ public void destroy() {
if (mFinalizer != null) {
mFinalizer.destroy();
mFinalizer = null;
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index d2df45a..947cf44 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -24,10 +24,7 @@ import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
/**
* An implementation of a GL canvas that records drawing operations.
@@ -35,35 +32,25 @@ import android.util.Pools;
* Bitmap objects that it draws, preventing the backing memory of Bitmaps from being freed while
* the DisplayList is still holding a native reference to the memory.
*/
-class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20RecordingCanvas> {
+class GLES20RecordingCanvas extends GLES20Canvas {
// The recording canvas pool should be large enough to handle a deeply nested
// view hierarchy because display lists are generated recursively.
private static final int POOL_LIMIT = 25;
- private static final Pool<GLES20RecordingCanvas> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<GLES20RecordingCanvas>() {
- public GLES20RecordingCanvas newInstance() {
- return new GLES20RecordingCanvas();
- }
- @Override
- public void onAcquired(GLES20RecordingCanvas element) {
- }
- @Override
- public void onReleased(GLES20RecordingCanvas element) {
- }
- }, POOL_LIMIT));
-
- private GLES20RecordingCanvas mNextPoolable;
- private boolean mIsPooled;
+ private static final SynchronizedPool<GLES20RecordingCanvas> sPool =
+ new SynchronizedPool<GLES20RecordingCanvas>(POOL_LIMIT);
private GLES20DisplayList mDisplayList;
private GLES20RecordingCanvas() {
- super(true /*record*/, true /*translucent*/);
+ super(true, true);
}
static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) {
GLES20RecordingCanvas canvas = sPool.acquire();
+ if (canvas == null) {
+ canvas = new GLES20RecordingCanvas();
+ }
canvas.mDisplayList = displayList;
return canvas;
}
@@ -280,15 +267,15 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor
@Override
public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
- float x, float y, int dir, Paint paint) {
- super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, dir, paint);
+ float x, float y, Paint paint) {
+ super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, paint);
recordShaderBitmap(paint);
}
@Override
public void drawTextRun(CharSequence text, int start, int end, int contextStart,
- int contextEnd, float x, float y, int dir, Paint paint) {
- super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, dir, paint);
+ int contextEnd, float x, float y, Paint paint) {
+ super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, paint);
recordShaderBitmap(paint);
}
@@ -300,24 +287,4 @@ class GLES20RecordingCanvas extends GLES20Canvas implements Poolable<GLES20Recor
colorOffset, indices, indexOffset, indexCount, paint);
recordShaderBitmap(paint);
}
-
- @Override
- public GLES20RecordingCanvas getNextPoolable() {
- return mNextPoolable;
- }
-
- @Override
- public void setNextPoolable(GLES20RecordingCanvas element) {
- mNextPoolable = element;
- }
-
- @Override
- public boolean isPooled() {
- return mIsPooled;
- }
-
- @Override
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
}
diff --git a/core/java/android/view/GLES20RenderLayer.java b/core/java/android/view/GLES20RenderLayer.java
index 44d4719..086e78c 100644
--- a/core/java/android/view/GLES20RenderLayer.java
+++ b/core/java/android/view/GLES20RenderLayer.java
@@ -92,6 +92,10 @@ class GLES20RenderLayer extends GLES20Layer {
if (currentCanvas instanceof GLES20Canvas) {
((GLES20Canvas) currentCanvas).resume();
}
+ HardwareCanvas canvas = getCanvas();
+ if (canvas != null) {
+ canvas.onPostDraw();
+ }
}
@Override
@@ -99,7 +103,10 @@ class GLES20RenderLayer extends GLES20Layer {
if (currentCanvas instanceof GLES20Canvas) {
((GLES20Canvas) currentCanvas).interrupt();
}
- return getCanvas();
+ HardwareCanvas canvas = getCanvas();
+ canvas.setViewport(mWidth, mHeight);
+ canvas.onPreDraw(null);
+ return canvas;
}
/**
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index 9ddb32e..28c1058 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -213,6 +213,7 @@ public class GestureDetector {
private OnDoubleTapListener mDoubleTapListener;
private boolean mStillDown;
+ private boolean mDeferConfirmSingleTap;
private boolean mInLongPress;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
@@ -267,8 +268,12 @@ public class GestureDetector {
case TAP:
// If the user's finger is still down, do not count it as a tap
- if (mDoubleTapListener != null && !mStillDown) {
- mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ if (mDoubleTapListener != null) {
+ if (!mStillDown) {
+ mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent);
+ } else {
+ mDeferConfirmSingleTap = true;
+ }
}
break;
@@ -533,6 +538,7 @@ public class GestureDetector {
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
+ mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
@@ -586,6 +592,9 @@ public class GestureDetector {
mInLongPress = false;
} else if (mAlwaysInTapRegion) {
handled = mListener.onSingleTapUp(ev);
+ if (mDeferConfirmSingleTap && mDoubleTapListener != null) {
+ mDoubleTapListener.onSingleTapConfirmed(ev);
+ }
} else {
// A fling must travel the minimum tap distance
@@ -612,6 +621,7 @@ public class GestureDetector {
mVelocityTracker = null;
}
mIsDoubleTapping = false;
+ mDeferConfirmSingleTap = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
@@ -637,6 +647,7 @@ public class GestureDetector {
mStillDown = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
+ mDeferConfirmSingleTap = false;
if (mInLongPress) {
mInLongPress = false;
}
@@ -649,6 +660,7 @@ public class GestureDetector {
mIsDoubleTapping = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
+ mDeferConfirmSingleTap = false;
if (mInLongPress) {
mInLongPress = false;
}
@@ -671,6 +683,7 @@ public class GestureDetector {
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
+ mDeferConfirmSingleTap = false;
mInLongPress = true;
mListener.onLongPress(mCurrentDownEvent);
}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index eeae3ed..0dfed69 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -23,10 +23,12 @@ import android.graphics.Rect;
/**
* Hardware accelerated canvas.
- *
- * @hide
+ *
+ * @hide
*/
public abstract class HardwareCanvas extends Canvas {
+ private String mName;
+
@Override
public boolean isHardwareAccelerated() {
return true;
@@ -36,33 +38,76 @@ public abstract class HardwareCanvas extends Canvas {
public void setBitmap(Bitmap bitmap) {
throw new UnsupportedOperationException();
}
-
+
+ /**
+ * Specifies the name of this canvas. Naming the canvas is entirely
+ * optional but can be useful for debugging purposes.
+ *
+ * @param name The name of the canvas, can be null
+ *
+ * @see #getName()
+ *
+ * @hide
+ */
+ public void setName(String name) {
+ mName = name;
+ }
+
+ /**
+ * Returns the name of this canvas.
+ *
+ * @return The name of the canvas or null
+ *
+ * @see #setName(String)
+ *
+ * @hide
+ */
+ public String getName() {
+ return mName;
+ }
+
/**
* Invoked before any drawing operation is performed in this canvas.
*
* @param dirty The dirty rectangle to update, can be null.
* @return {@link DisplayList#STATUS_DREW} if anything was drawn (such as a call to clear
- * the canvas).
+ * the canvas).
+ *
+ * @hide
*/
public abstract int onPreDraw(Rect dirty);
/**
* Invoked after all drawing operation have been performed.
+ *
+ * @hide
*/
public abstract void onPostDraw();
/**
+ * Draws the specified display list onto this canvas. The display list can only
+ * be drawn if {@link android.view.DisplayList#isValid()} returns true.
+ *
+ * @param displayList The display list to replay.
+ */
+ public void drawDisplayList(DisplayList displayList) {
+ drawDisplayList(displayList, null, DisplayList.FLAG_CLIP_CHILDREN);
+ }
+
+ /**
* Draws the specified display list onto this canvas.
*
* @param displayList The display list to replay.
* @param dirty The dirty region to redraw in the next pass, matters only
- * if this method returns true, can be null.
+ * if this method returns {@link DisplayList#STATUS_DRAW}, can be null.
* @param flags Optional flags about drawing, see {@link DisplayList} for
* the possible flags.
*
* @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW}, or
* {@link DisplayList#STATUS_INVOKE}, or'd with {@link DisplayList#STATUS_DREW}
* if anything was drawn.
+ *
+ * @hide
*/
public abstract int drawDisplayList(DisplayList displayList, Rect dirty, int flags);
@@ -71,6 +116,8 @@ public abstract class HardwareCanvas extends Canvas {
* tools to output display lists for selected nodes to the log.
*
* @param displayList The display list to be logged.
+ *
+ * @hide
*/
abstract void outputDisplayList(DisplayList displayList);
@@ -81,6 +128,8 @@ public abstract class HardwareCanvas extends Canvas {
* @param x The left coordinate of the layer
* @param y The top coordinate of the layer
* @param paint The paint used to draw the layer
+ *
+ * @hide
*/
abstract void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint);
@@ -93,6 +142,8 @@ public abstract class HardwareCanvas extends Canvas {
*
* @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
* {@link DisplayList#STATUS_INVOKE}
+ *
+ * @hide
*/
public int callDrawGLFunction(int drawGLFunction) {
// Noop - this is done in the display list recorder subclass
@@ -106,6 +157,8 @@ public abstract class HardwareCanvas extends Canvas {
*
* @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or
* {@link DisplayList#STATUS_INVOKE}
+ *
+ * @hide
*/
public int invokeFunctors(Rect dirty) {
return DisplayList.STATUS_DONE;
@@ -118,7 +171,9 @@ public abstract class HardwareCanvas extends Canvas {
*
* @see #invokeFunctors(android.graphics.Rect)
* @see #callDrawGLFunction(int)
- * @see #detachFunctor(int)
+ * @see #detachFunctor(int)
+ *
+ * @hide
*/
abstract void detachFunctor(int functor);
@@ -129,7 +184,9 @@ public abstract class HardwareCanvas extends Canvas {
*
* @see #invokeFunctors(android.graphics.Rect)
* @see #callDrawGLFunction(int)
- * @see #detachFunctor(int)
+ * @see #detachFunctor(int)
+ *
+ * @hide
*/
abstract void attachFunctor(int functor);
@@ -139,13 +196,17 @@ public abstract class HardwareCanvas extends Canvas {
* @param layer The layer to update
*
* @see #clearLayerUpdates()
+ *
+ * @hide
*/
abstract void pushLayerUpdate(HardwareLayer layer);
/**
* Removes all enqueued layer updates.
*
- * @see #pushLayerUpdate(HardwareLayer)
+ * @see #pushLayerUpdate(HardwareLayer)
+ *
+ * @hide
*/
abstract void clearLayerUpdates();
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index d3bc35a..18b838b 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -24,7 +24,7 @@ import android.graphics.Rect;
/**
* A hardware layer can be used to render graphics operations into a hardware
- * friendly buffer. For instance, with an OpenGL backend, a hardware layer
+ * friendly buffer. For instance, with an OpenGL backend a hardware layer
* would use a Frame Buffer Object (FBO.) The hardware layer can be used as
* a drawing cache when a complex set of graphics operations needs to be
* drawn several times.
@@ -68,7 +68,7 @@ abstract class HardwareLayer {
* @param paint The paint used when the layer is drawn into the destination canvas.
* @see View#setLayerPaint(android.graphics.Paint)
*/
- void setLayerPaint(Paint paint) {}
+ void setLayerPaint(Paint paint) { }
/**
* Returns the minimum width of the layer.
@@ -144,6 +144,9 @@ abstract class HardwareLayer {
* this layer.
*
* @return A hardware canvas, or null if a canvas cannot be created
+ *
+ * @see #start(android.graphics.Canvas)
+ * @see #end(android.graphics.Canvas)
*/
abstract HardwareCanvas getCanvas();
@@ -154,12 +157,14 @@ abstract class HardwareLayer {
/**
* This must be invoked before drawing onto this layer.
+ *
* @param currentCanvas
*/
abstract HardwareCanvas start(Canvas currentCanvas);
-
+
/**
* This must be invoked after drawing onto this layer.
+ *
* @param currentCanvas
*/
abstract void end(Canvas currentCanvas);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 5b7a5af..e086f5a 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-
package android.view;
import android.content.ComponentCallbacks2;
@@ -29,6 +28,7 @@ import android.os.Looper;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.DisplayMetrics;
import android.util.Log;
import com.google.android.gles_jni.EGLImpl;
@@ -42,13 +42,14 @@ import javax.microedition.khronos.opengles.GL;
import java.io.File;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
import static javax.microedition.khronos.egl.EGL10.*;
/**
- * Interface for rendering a ViewAncestor using hardware acceleration.
- *
+ * Interface for rendering a view hierarchy using hardware acceleration.
+ *
* @hide
*/
public abstract class HardwareRenderer {
@@ -62,9 +63,9 @@ public abstract class HardwareRenderer {
/**
* Turn on to only refresh the parts of the screen that need updating.
* When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY}
- * must also have the value "true".
+ * must also have the value "true".
*/
- public static final boolean RENDER_DIRTY_REGIONS = true;
+ static final boolean RENDER_DIRTY_REGIONS = true;
/**
* System property used to enable or disable dirty regions invalidation.
@@ -76,16 +77,6 @@ public abstract class HardwareRenderer {
* "false", to disable partial invalidates
*/
static final String RENDER_DIRTY_REGIONS_PROPERTY = "debug.hwui.render_dirty_regions";
-
- /**
- * System property used to enable or disable vsync.
- * The default value of this property is assumed to be false.
- *
- * Possible values:
- * "true", to disable vsync
- * "false", to enable vsync
- */
- static final String DISABLE_VSYNC_PROPERTY = "debug.hwui.disable_vsync";
/**
* System property used to enable or disable hardware rendering profiling.
@@ -97,13 +88,34 @@ public abstract class HardwareRenderer {
*
* Possible values:
* "true", to enable profiling
+ * "visual_bars", to enable profiling and visualize the results on screen
+ * "visual_lines", to enable profiling and visualize the results on screen
* "false", to disable profiling
- *
+ *
+ * @see #PROFILE_PROPERTY_VISUALIZE_BARS
+ * @see #PROFILE_PROPERTY_VISUALIZE_LINES
+ *
* @hide
*/
public static final String PROFILE_PROPERTY = "debug.hwui.profile";
/**
+ * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
+ * value, profiling data will be visualized on screen as a bar chart.
+ *
+ * @hide
+ */
+ public static final String PROFILE_PROPERTY_VISUALIZE_BARS = "visual_bars";
+
+ /**
+ * Value for {@link #PROFILE_PROPERTY}. When the property is set to this
+ * value, profiling data will be visualized on screen as a line chart.
+ *
+ * @hide
+ */
+ public static final String PROFILE_PROPERTY_VISUALIZE_LINES = "visual_lines";
+
+ /**
* System property used to specify the number of frames to be used
* when doing hardware rendering profiling.
* The default value of this property is #PROFILE_MAX_FRAMES.
@@ -161,6 +173,19 @@ public abstract class HardwareRenderer {
public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw";
/**
+ * Turn on to debug non-rectangular clip operations.
+ *
+ * Possible values:
+ * "hide", to disable this debug mode
+ * "highlight", highlight drawing commands tested against a non-rectangular clip
+ * "stencil", renders the clip region on screen when set
+ *
+ * @hide
+ */
+ public static final String DEBUG_SHOW_NON_RECTANGULAR_CLIP_PROPERTY =
+ "debug.hwui.show_non_rect_clip";
+
+ /**
* A process can set this flag to false to prevent the use of hardware
* rendering.
*
@@ -323,10 +348,26 @@ public abstract class HardwareRenderer {
abstract long getFrameCount();
/**
+ * Loads system properties used by the renderer. This method is invoked
+ * whenever system properties are modified. Implementations can use this
+ * to trigger live updates of the renderer based on properties.
+ *
+ * @param surface The surface to update with the new properties.
+ * Can be null.
+ *
+ * @return True if a property has changed.
+ */
+ abstract boolean loadSystemProperties(Surface surface);
+
+ private static native boolean nLoadProperties();
+
+ /**
* Sets the directory to use as a persistent storage for hardware rendering
* resources.
*
* @param cacheDir A directory the current process can write to
+ *
+ * @hide
*/
public static void setupDiskCache(File cacheDir) {
nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
@@ -338,7 +379,7 @@ public abstract class HardwareRenderer {
* Notifies EGL that the frame is about to be rendered.
* @param size
*/
- private static void beginFrame(int[] size) {
+ static void beginFrame(int[] size) {
nBeginFrame(size);
}
@@ -373,15 +414,6 @@ public abstract class HardwareRenderer {
private static native boolean nIsBackBufferPreserved();
/**
- * Disables v-sync. For performance testing only.
- */
- static void disableVsync() {
- nDisableVsync();
- }
-
- private static native void nDisableVsync();
-
- /**
* Indicates that the specified hardware layer needs to be updated
* as soon as possible.
*
@@ -426,10 +458,11 @@ public abstract class HardwareRenderer {
* Creates a new display list that can be used to record batches of
* drawing operations.
*
- * @param name The name of the display list, used for debugging purpose.
- * May be null
+ * @param name The name of the display list, used for debugging purpose. May be null.
*
* @return A new display list.
+ *
+ * @hide
*/
public abstract DisplayList createDisplayList(String name);
@@ -457,7 +490,6 @@ public abstract class HardwareRenderer {
/**
* Creates a new {@link SurfaceTexture} that can be used to render into the
* specified hardware layer.
- *
*
* @param layer The layer to render into using a {@link android.graphics.SurfaceTexture}
*
@@ -526,6 +558,13 @@ public abstract class HardwareRenderer {
}
/**
+ * Optional, sets the name of the renderer. Useful for debugging purposes.
+ *
+ * @param name The name of this renderer, can be null
+ */
+ abstract void setName(String name);
+
+ /**
* Creates a hardware renderer using OpenGL.
*
* @param glVersion The version of OpenGL to use (1 for OpenGL 1, 11 for OpenGL 1.1, etc.)
@@ -612,6 +651,100 @@ public abstract class HardwareRenderer {
mRequested = requested;
}
+ /**
+ * Describes a series of frames that should be drawn on screen as a graph.
+ * Each frame is composed of 1 or more elements.
+ */
+ abstract class GraphDataProvider {
+ /**
+ * Draws the graph as bars. Frame elements are stacked on top of
+ * each other.
+ */
+ public static final int GRAPH_TYPE_BARS = 0;
+ /**
+ * Draws the graph as lines. The number of series drawn corresponds
+ * to the number of elements.
+ */
+ public static final int GRAPH_TYPE_LINES = 1;
+
+ /**
+ * Returns the type of graph to render.
+ *
+ * @return {@link #GRAPH_TYPE_BARS} or {@link #GRAPH_TYPE_LINES}
+ */
+ abstract int getGraphType();
+
+ /**
+ * This method is invoked before the graph is drawn. This method
+ * can be used to compute sizes, etc.
+ *
+ * @param metrics The display metrics
+ */
+ abstract void prepare(DisplayMetrics metrics);
+
+ /**
+ * @return The size in pixels of a vertical unit.
+ */
+ abstract int getVerticalUnitSize();
+
+ /**
+ * @return The size in pixels of a horizontal unit.
+ */
+ abstract int getHorizontalUnitSize();
+
+ /**
+ * @return The size in pixels of the margin between horizontal units.
+ */
+ abstract int getHorizontaUnitMargin();
+
+ /**
+ * An optional threshold value.
+ *
+ * @return A value >= 0 to draw the threshold, a negative value
+ * to ignore it.
+ */
+ abstract float getThreshold();
+
+ /**
+ * The data to draw in the graph. The number of elements in the
+ * array must be at least {@link #getFrameCount()} * {@link #getElementCount()}.
+ * If a value is negative the following values will be ignored.
+ */
+ abstract float[] getData();
+
+ /**
+ * Returns the number of frames to render in the graph.
+ */
+ abstract int getFrameCount();
+
+ /**
+ * Returns the number of elements in each frame. This directly affects
+ * the number of series drawn in the graph.
+ */
+ abstract int getElementCount();
+
+ /**
+ * Returns the current frame, if any. If the returned value is negative
+ * the current frame is ignored.
+ */
+ abstract int getCurrentFrame();
+
+ /**
+ * Prepares the paint to draw the specified element (or series.)
+ */
+ abstract void setupGraphPaint(Paint paint, int elementIndex);
+
+ /**
+ * Prepares the paint to draw the threshold.
+ */
+ abstract void setupThresholdPaint(Paint paint);
+
+ /**
+ * Prepares the paint to draw the current frame indicator.
+ */
+ abstract void setupCurrentFramePaint(Paint paint);
+ }
+
@SuppressWarnings({"deprecation"})
static abstract class GlRenderer extends HardwareRenderer {
static final int SURFACE_STATE_ERROR = 0;
@@ -620,6 +753,14 @@ public abstract class HardwareRenderer {
static final int FUNCTOR_PROCESS_DELAY = 4;
+ private static final int PROFILE_DRAW_MARGIN = 0;
+ private static final int PROFILE_DRAW_WIDTH = 3;
+ private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 };
+ private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d;
+ private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d;
+ private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2;
+ private static final int PROFILE_DRAW_DP_PER_MS = 7;
+
static EGL10 sEgl;
static EGLDisplay sEglDisplay;
static EGLConfig sEglConfig;
@@ -637,6 +778,8 @@ public abstract class HardwareRenderer {
GL mGl;
HardwareCanvas mCanvas;
+ String mName;
+
long mFrameCount;
Paint mDebugPaint;
@@ -652,15 +795,18 @@ public abstract class HardwareRenderer {
boolean mDirtyRegionsEnabled;
boolean mUpdateDirtyRegions;
- final boolean mVsyncDisabled;
-
- final boolean mProfileEnabled;
- final float[] mProfileData;
- final ReentrantLock mProfileLock;
+ boolean mProfileEnabled;
+ int mProfileVisualizerType = -1;
+ float[] mProfileData;
+ ReentrantLock mProfileLock;
int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
-
- final boolean mDebugDirtyRegions;
- final boolean mShowOverdraw;
+
+ GraphDataProvider mDebugDataProvider;
+ float[][] mProfileShapes;
+ Paint mProfilePaint;
+
+ boolean mDebugDirtyRegions;
+ boolean mShowOverdraw;
final int mGlVersion;
final boolean mTranslucent;
@@ -675,44 +821,90 @@ public abstract class HardwareRenderer {
GlRenderer(int glVersion, boolean translucent) {
mGlVersion = glVersion;
mTranslucent = translucent;
-
- String property;
- property = SystemProperties.get(DISABLE_VSYNC_PROPERTY, "false");
- mVsyncDisabled = "true".equalsIgnoreCase(property);
- if (mVsyncDisabled) {
- Log.d(LOG_TAG, "Disabling v-sync");
+ loadSystemProperties(null);
+ }
+
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ PROFILE_PROPERTY_VISUALIZE_LINES
+ };
+
+ @Override
+ boolean loadSystemProperties(Surface surface) {
+ boolean value;
+ boolean changed = false;
+
+ String profiling = SystemProperties.get(PROFILE_PROPERTY);
+ int graphType = Arrays.binarySearch(VISUALIZERS, profiling);
+ value = graphType >= 0;
+
+ if (graphType != mProfileVisualizerType) {
+ changed = true;
+ mProfileVisualizerType = graphType;
+
+ mProfileShapes = null;
+ mProfilePaint = null;
+
+ if (value) {
+ mDebugDataProvider = new DrawPerformanceDataProvider(graphType);
+ } else {
+ mDebugDataProvider = null;
+ }
}
- property = SystemProperties.get(PROFILE_PROPERTY, "false");
- mProfileEnabled = "true".equalsIgnoreCase(property);
- if (mProfileEnabled) {
- Log.d(LOG_TAG, "Profiling hardware renderer");
+ // If on-screen profiling is not enabled, we need to check whether
+ // console profiling only is enabled
+ if (!value) {
+ value = Boolean.parseBoolean(profiling);
}
- if (mProfileEnabled) {
- property = SystemProperties.get(PROFILE_MAXFRAMES_PROPERTY,
- Integer.toString(PROFILE_MAX_FRAMES));
- int maxProfileFrames = Integer.valueOf(property);
- mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
- for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
- mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
+ if (value != mProfileEnabled) {
+ changed = true;
+ mProfileEnabled = value;
+
+ if (mProfileEnabled) {
+ Log.d(LOG_TAG, "Profiling hardware renderer");
+
+ int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY,
+ PROFILE_MAX_FRAMES);
+ mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
+ for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
+ mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
+ }
+
+ mProfileLock = new ReentrantLock();
+ } else {
+ mProfileData = null;
+ mProfileLock = null;
+ mProfileVisualizerType = -1;
}
- mProfileLock = new ReentrantLock();
- } else {
- mProfileData = null;
- mProfileLock = null;
+ mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
}
- property = SystemProperties.get(DEBUG_DIRTY_REGIONS_PROPERTY, "false");
- mDebugDirtyRegions = "true".equalsIgnoreCase(property);
- if (mDebugDirtyRegions) {
- Log.d(LOG_TAG, "Debugging dirty regions");
+ value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false);
+ if (value != mDebugDirtyRegions) {
+ changed = true;
+ mDebugDirtyRegions = value;
+
+ if (mDebugDirtyRegions) {
+ Log.d(LOG_TAG, "Debugging dirty regions");
+ }
}
- mShowOverdraw = SystemProperties.getBoolean(
+ value = SystemProperties.getBoolean(
HardwareRenderer.DEBUG_SHOW_OVERDRAW_PROPERTY, false);
+ if (value != mShowOverdraw) {
+ changed = true;
+ mShowOverdraw = value;
+ }
+
+ if (nLoadProperties()) {
+ changed = true;
+ }
+
+ return changed;
}
@Override
@@ -795,6 +987,7 @@ public abstract class HardwareRenderer {
} else {
if (mCanvas == null) {
mCanvas = createCanvas();
+ mCanvas.setName(mName);
}
if (mCanvas != null) {
setEnabled(true);
@@ -842,19 +1035,7 @@ public abstract class HardwareRenderer {
checkEglErrorsForced();
- sEglConfig = chooseEglConfig();
- if (sEglConfig == null) {
- // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
- if (sDirtyRegions) {
- sDirtyRegions = false;
- sEglConfig = chooseEglConfig();
- if (sEglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
- } else {
- throw new RuntimeException("eglConfig not initialized");
- }
- }
+ sEglConfig = loadEglConfig();
}
}
@@ -868,6 +1049,23 @@ public abstract class HardwareRenderer {
}
}
+ private EGLConfig loadEglConfig() {
+ EGLConfig eglConfig = chooseEglConfig();
+ if (eglConfig == null) {
+ // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
+ if (sDirtyRegions) {
+ sDirtyRegions = false;
+ eglConfig = chooseEglConfig();
+ if (eglConfig == null) {
+ throw new RuntimeException("eglConfig not initialized");
+ }
+ } else {
+ throw new RuntimeException("eglConfig not initialized");
+ }
+ }
+ return eglConfig;
+ }
+
abstract ManagedEGLContext createManagedContext(EGLContext eglContext);
private EGLConfig chooseEglConfig() {
@@ -1104,6 +1302,11 @@ public abstract class HardwareRenderer {
return mCanvas;
}
+ @Override
+ void setName(String name) {
+ mName = name;
+ }
+
boolean canDraw() {
return mGl != null && mCanvas != null;
}
@@ -1154,93 +1357,27 @@ public abstract class HardwareRenderer {
mProfileLock.lock();
}
- // We had to change the current surface and/or context, redraw everything
- if (surfaceState == SURFACE_STATE_UPDATED) {
- dirty = null;
- beginFrame(null);
- } else {
- int[] size = mSurfaceSize;
- beginFrame(size);
-
- if (size[1] != mHeight || size[0] != mWidth) {
- mWidth = size[0];
- mHeight = size[1];
+ dirty = beginFrame(canvas, dirty, surfaceState);
- canvas.setViewport(mWidth, mHeight);
-
- dirty = null;
- }
- }
+ DisplayList displayList = buildDisplayList(view, canvas);
int saveCount = 0;
int status = DisplayList.STATUS_DONE;
try {
- view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
- == View.PFLAG_INVALIDATED;
- view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
-
- long getDisplayListStartTime = 0;
- if (mProfileEnabled) {
- mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
- if (mProfileCurrentFrame >= mProfileData.length) {
- mProfileCurrentFrame = 0;
- }
+ status = prepareFrame(dirty);
- getDisplayListStartTime = System.nanoTime();
- }
-
- canvas.clearLayerUpdates();
-
- DisplayList displayList;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
- try {
- displayList = view.getDisplayList();
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
- try {
- status = onPreDraw(dirty);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
saveCount = canvas.save();
callbacks.onHardwarePreDraw(canvas);
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - getDisplayListStartTime) * 0.000001f;
- //noinspection PointlessArithmeticExpression
- mProfileData[mProfileCurrentFrame] = total;
- }
-
if (displayList != null) {
- long drawDisplayListStartTime = 0;
- if (mProfileEnabled) {
- drawDisplayListStartTime = System.nanoTime();
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
- try {
- status |= canvas.drawDisplayList(displayList, mRedrawClip,
- DisplayList.FLAG_CLIP_CHILDREN);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - drawDisplayListStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 1] = total;
- }
-
- handleFunctorStatus(attachInfo, status);
+ status = drawDisplayList(attachInfo, canvas, displayList, status);
} else {
// Shouldn't reach here
view.draw(canvas);
}
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "An error has occurred while drawing:", e);
} finally {
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
@@ -1248,43 +1385,19 @@ public abstract class HardwareRenderer {
mFrameCount++;
- if (mDebugDirtyRegions) {
- if (mDebugPaint == null) {
- mDebugPaint = new Paint();
- mDebugPaint.setColor(0x7fff0000);
- }
-
- if (dirty != null && (mFrameCount & 1) == 0) {
- canvas.drawRect(dirty, mDebugPaint);
- }
- }
+ debugDirtyRegions(dirty, canvas);
+ drawProfileData(attachInfo);
}
onPostDraw();
- attachInfo.mIgnoreDirtyState = false;
-
- if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) {
- long eglSwapBuffersStartTime = 0;
- if (mProfileEnabled) {
- eglSwapBuffersStartTime = System.nanoTime();
- }
-
- sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - eglSwapBuffersStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 2] = total;
- }
-
- checkEglErrors();
- }
+ swapBuffers(status);
if (mProfileEnabled) {
mProfileLock.unlock();
}
+ attachInfo.mIgnoreDirtyState = false;
return dirty == null;
}
}
@@ -1292,6 +1405,139 @@ public abstract class HardwareRenderer {
return false;
}
+ private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
+ view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+ == View.PFLAG_INVALIDATED;
+ view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+
+ long buildDisplayListStartTime = startBuildDisplayListProfiling();
+ canvas.clearLayerUpdates();
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+ DisplayList displayList = view.getDisplayList();
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+ endBuildDisplayListProfiling(buildDisplayListStartTime);
+
+ return displayList;
+ }
+
+ abstract void drawProfileData(View.AttachInfo attachInfo);
+
+ private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) {
+ // We had to change the current surface and/or context, redraw everything
+ if (surfaceState == SURFACE_STATE_UPDATED) {
+ dirty = null;
+ beginFrame(null);
+ } else {
+ int[] size = mSurfaceSize;
+ beginFrame(size);
+
+ if (size[1] != mHeight || size[0] != mWidth) {
+ mWidth = size[0];
+ mHeight = size[1];
+
+ canvas.setViewport(mWidth, mHeight);
+
+ dirty = null;
+ }
+ }
+
+ if (mDebugDataProvider != null) dirty = null;
+
+ return dirty;
+ }
+
+ private long startBuildDisplayListProfiling() {
+ if (mProfileEnabled) {
+ mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
+ if (mProfileCurrentFrame >= mProfileData.length) {
+ mProfileCurrentFrame = 0;
+ }
+
+ return System.nanoTime();
+ }
+ return 0;
+ }
+
+ private void endBuildDisplayListProfiling(long getDisplayListStartTime) {
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - getDisplayListStartTime) * 0.000001f;
+ //noinspection PointlessArithmeticExpression
+ mProfileData[mProfileCurrentFrame] = total;
+ }
+ }
+
+ private int prepareFrame(Rect dirty) {
+ int status;
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
+ try {
+ status = onPreDraw(dirty);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ return status;
+ }
+
+ private int drawDisplayList(View.AttachInfo attachInfo, HardwareCanvas canvas,
+ DisplayList displayList, int status) {
+
+ long drawDisplayListStartTime = 0;
+ if (mProfileEnabled) {
+ drawDisplayListStartTime = System.nanoTime();
+ }
+
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
+ try {
+ status |= canvas.drawDisplayList(displayList, mRedrawClip,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - drawDisplayListStartTime) * 0.000001f;
+ mProfileData[mProfileCurrentFrame + 1] = total;
+ }
+
+ handleFunctorStatus(attachInfo, status);
+ return status;
+ }
+
+ private void swapBuffers(int status) {
+ if ((status & DisplayList.STATUS_DREW) == DisplayList.STATUS_DREW) {
+ long eglSwapBuffersStartTime = 0;
+ if (mProfileEnabled) {
+ eglSwapBuffersStartTime = System.nanoTime();
+ }
+
+ sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
+
+ if (mProfileEnabled) {
+ long now = System.nanoTime();
+ float total = (now - eglSwapBuffersStartTime) * 0.000001f;
+ mProfileData[mProfileCurrentFrame + 2] = total;
+ }
+
+ checkEglErrors();
+ }
+ }
+
+ private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) {
+ if (mDebugDirtyRegions) {
+ if (mDebugPaint == null) {
+ mDebugPaint = new Paint();
+ mDebugPaint.setColor(0x7fff0000);
+ }
+
+ if (dirty != null && (mFrameCount & 1) == 0) {
+ canvas.drawRect(dirty, mDebugPaint);
+ }
+ }
+ }
+
private void handleFunctorStatus(View.AttachInfo attachInfo, int status) {
// If the draw flag is set, functors will be invoked while executing
// the tree of display lists
@@ -1362,6 +1608,96 @@ public abstract class HardwareRenderer {
}
return SURFACE_STATE_SUCCESS;
}
+
+ private static int dpToPx(int dp, float density) {
+ return (int) (dp * density + 0.5f);
+ }
+
+ class DrawPerformanceDataProvider extends GraphDataProvider {
+ private final int mGraphType;
+
+ private int mVerticalUnit;
+ private int mHorizontalUnit;
+ private int mHorizontalMargin;
+ private int mThresholdStroke;
+
+ DrawPerformanceDataProvider(int graphType) {
+ mGraphType = graphType;
+ }
+
+ @Override
+ void prepare(DisplayMetrics metrics) {
+ final float density = metrics.density;
+
+ mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
+ mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
+ mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density);
+ mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
+ }
+
+ @Override
+ int getGraphType() {
+ return mGraphType;
+ }
+
+ @Override
+ int getVerticalUnitSize() {
+ return mVerticalUnit;
+ }
+
+ @Override
+ int getHorizontalUnitSize() {
+ return mHorizontalUnit;
+ }
+
+ @Override
+ int getHorizontaUnitMargin() {
+ return mHorizontalMargin;
+ }
+
+ @Override
+ float[] getData() {
+ return mProfileData;
+ }
+
+ @Override
+ float getThreshold() {
+ return 16;
+ }
+
+ @Override
+ int getFrameCount() {
+ return mProfileData.length / PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ int getElementCount() {
+ return PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ int getCurrentFrame() {
+ return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT;
+ }
+
+ @Override
+ void setupGraphPaint(Paint paint, int elementIndex) {
+ paint.setColor(PROFILE_DRAW_COLORS[elementIndex]);
+ if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
+ }
+
+ @Override
+ void setupThresholdPaint(Paint paint) {
+ paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR);
+ paint.setStrokeWidth(mThresholdStroke);
+ }
+
+ @Override
+ void setupCurrentFramePaint(Paint paint) {
+ paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR);
+ if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
+ }
+ }
}
/**
@@ -1370,6 +1706,8 @@ public abstract class HardwareRenderer {
static class Gl20Renderer extends GlRenderer {
private GLES20Canvas mGlCanvas;
+ private DisplayMetrics mDisplayMetrics;
+
private static EGLSurface sPbuffer;
private static final Object[] sPbufferLock = new Object[0];
@@ -1436,6 +1774,10 @@ public abstract class HardwareRenderer {
@Override
int[] getConfig(boolean dirtyRegions) {
+ //noinspection PointlessBooleanExpression,ConstantConditions
+ final int stencilSize = GLES20Canvas.getStencilSize();
+ final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
+
return new int[] {
EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL_RED_SIZE, 8,
@@ -1444,14 +1786,12 @@ public abstract class HardwareRenderer {
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_CONFIG_CAVEAT, EGL_NONE,
- // TODO: Find a better way to choose the stencil size
- EGL_STENCIL_SIZE, mShowOverdraw ? GLES20Canvas.getStencilSize() : 0,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
- (dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
+ EGL_STENCIL_SIZE, stencilSize,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
EGL_NONE
};
}
-
+
@Override
void initCaches() {
GLES20Canvas.initCaches();
@@ -1473,6 +1813,153 @@ public abstract class HardwareRenderer {
}
@Override
+ void drawProfileData(View.AttachInfo attachInfo) {
+ if (mDebugDataProvider != null) {
+ final GraphDataProvider provider = mDebugDataProvider;
+ initProfileDrawData(attachInfo, provider);
+
+ final int height = provider.getVerticalUnitSize();
+ final int margin = provider.getHorizontaUnitMargin();
+ final int width = provider.getHorizontalUnitSize();
+
+ int x = 0;
+ int count = 0;
+ int current = 0;
+
+ final float[] data = provider.getData();
+ final int elementCount = provider.getElementCount();
+ final int graphType = provider.getGraphType();
+
+ int totalCount = provider.getFrameCount() * elementCount;
+ if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) {
+ totalCount -= elementCount;
+ }
+
+ for (int i = 0; i < totalCount; i += elementCount) {
+ if (data[i] < 0.0f) break;
+
+ int index = count * 4;
+ if (i == provider.getCurrentFrame() * elementCount) current = index;
+
+ x += margin;
+ int x2 = x + width;
+
+ int y2 = mHeight;
+ int y1 = (int) (y2 - data[i] * height);
+
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS: {
+ for (int j = 0; j < elementCount; j++) {
+ //noinspection MismatchedReadAndWriteOfArray
+ final float[] r = mProfileShapes[j];
+ r[index] = x;
+ r[index + 1] = y1;
+ r[index + 2] = x2;
+ r[index + 3] = y2;
+
+ y2 = y1;
+ if (j < elementCount - 1) {
+ y1 = (int) (y2 - data[i + j + 1] * height);
+ }
+ }
+ } break;
+ case GraphDataProvider.GRAPH_TYPE_LINES: {
+ for (int j = 0; j < elementCount; j++) {
+ //noinspection MismatchedReadAndWriteOfArray
+ final float[] r = mProfileShapes[j];
+ r[index] = (x + x2) * 0.5f;
+ r[index + 1] = index == 0 ? y1 : r[index - 1];
+ r[index + 2] = r[index] + width;
+ r[index + 3] = y1;
+
+ y2 = y1;
+ if (j < elementCount - 1) {
+ y1 = (int) (y2 - data[i + j + 1] * height);
+ }
+ }
+ } break;
+ }
+
+
+ x += width;
+ count++;
+ }
+
+ x += margin;
+
+ drawGraph(graphType, count);
+ drawCurrentFrame(graphType, current);
+ drawThreshold(x, height);
+ }
+ }
+
+ private void drawGraph(int graphType, int count) {
+ for (int i = 0; i < mProfileShapes.length; i++) {
+ mDebugDataProvider.setupGraphPaint(mProfilePaint, i);
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS:
+ mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint);
+ break;
+ case GraphDataProvider.GRAPH_TYPE_LINES:
+ mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint);
+ break;
+ }
+ }
+ }
+
+ private void drawCurrentFrame(int graphType, int index) {
+ if (index >= 0) {
+ mDebugDataProvider.setupCurrentFramePaint(mProfilePaint);
+ switch (graphType) {
+ case GraphDataProvider.GRAPH_TYPE_BARS:
+ mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1],
+ mProfileShapes[2][index + 2], mProfileShapes[0][index + 3],
+ mProfilePaint);
+ break;
+ case GraphDataProvider.GRAPH_TYPE_LINES:
+ mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1],
+ mProfileShapes[2][index], mHeight, mProfilePaint);
+ break;
+ }
+ }
+ }
+
+ private void drawThreshold(int x, int height) {
+ float threshold = mDebugDataProvider.getThreshold();
+ if (threshold > 0.0f) {
+ mDebugDataProvider.setupThresholdPaint(mProfilePaint);
+ int y = (int) (mHeight - threshold * height);
+ mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint);
+ }
+ }
+
+ private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) {
+ if (mProfileShapes == null) {
+ final int elementCount = provider.getElementCount();
+ final int frameCount = provider.getFrameCount();
+
+ mProfileShapes = new float[elementCount][];
+ for (int i = 0; i < elementCount; i++) {
+ mProfileShapes[i] = new float[frameCount * 4];
+ }
+
+ mProfilePaint = new Paint();
+ }
+
+ mProfilePaint.reset();
+ if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) {
+ mProfilePaint.setAntiAlias(true);
+ }
+
+ if (mDisplayMetrics == null) {
+ mDisplayMetrics = new DisplayMetrics();
+ }
+
+ attachInfo.mDisplay.getMetrics(mDisplayMetrics);
+ provider.prepare(mDisplayMetrics);
+ }
+
+ @Override
void destroy(boolean full) {
try {
super.destroy(full);
@@ -1484,14 +1971,6 @@ public abstract class HardwareRenderer {
}
@Override
- void setup(int width, int height) {
- super.setup(width, height);
- if (mVsyncDisabled) {
- disableVsync();
- }
- }
-
- @Override
void pushLayerUpdate(HardwareLayer layer) {
mGlCanvas.pushLayerUpdate(layer);
}
@@ -1507,12 +1986,12 @@ public abstract class HardwareRenderer {
}
@Override
- HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
+ public HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
return new GLES20RenderLayer(width, height, isOpaque);
}
@Override
- SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
+ public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
return ((GLES20TextureLayer) layer).getSurfaceTexture();
}
diff --git a/core/java/android/view/IDisplayContentChangeListener.aidl b/core/java/android/view/IMagnificationCallbacks.aidl
index ef7edea..032d073 100644
--- a/core/java/android/view/IDisplayContentChangeListener.aidl
+++ b/core/java/android/view/IMagnificationCallbacks.aidl
@@ -1,7 +1,7 @@
/*
** Copyright 2012, The Android Open Source Project
**
-** Licensed under the Apache License, Version 2.0 (the "License")
+** 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
**
@@ -16,18 +16,14 @@
package android.view;
-import android.os.IBinder;
-import android.view.WindowInfo;
-import android.graphics.Rect;
+import android.graphics.Region;
/**
- * Interface for observing content changes on a display.
- *
* {@hide}
*/
-oneway interface IDisplayContentChangeListener {
- void onWindowTransition(int displayId, int transition, in WindowInfo info);
- void onRectangleOnScreenRequested(int displayId, in Rect rectangle, boolean immediate);
- void onWindowLayersChanged(int displayId);
+oneway interface IMagnificationCallbacks {
+ void onMagnifedBoundsChanged(in Region bounds);
+ void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
void onRotationChanged(int rotation);
+ void onUserContextChanged();
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 15bd46c..8ec07ef 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -45,7 +45,7 @@ oneway interface IWindow {
*/
void executeCommand(String command, String parameters, in ParcelFileDescriptor descriptor);
- void resized(in Rect frame, in Rect contentInsets,
+ void resized(in Rect frame, in Rect overscanInsets, in Rect contentInsets,
in Rect visibleInsets, boolean reportDraw, in Configuration newConfig);
void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 2b6cbcf..f0c6241 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -27,17 +27,17 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IRemoteCallback;
import android.view.IApplicationToken;
-import android.view.IDisplayContentChangeListener;
+import android.view.IMagnificationCallbacks;
import android.view.IOnKeyguardExitResult;
import android.view.IRotationWatcher;
import android.view.IWindowSession;
import android.view.KeyEvent;
import android.view.InputEvent;
+import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.IInputFilter;
-import android.view.WindowInfo;
/**
* System private interface to the window manager.
@@ -65,6 +65,8 @@ interface IWindowManager
void setForcedDisplayDensity(int displayId, int density);
void clearForcedDisplayDensity(int displayId);
+ void setOverscan(int displayId, int left, int top, int right, int bottom);
+
// Is the device configured to have a full system bar for larger screens?
boolean hasSystemNavBar();
@@ -74,7 +76,7 @@ interface IWindowManager
void setEventDispatching(boolean enabled);
void addWindowToken(IBinder token, int type);
void removeWindowToken(IBinder token);
- void addAppToken(int addPos, int userId, IApplicationToken token,
+ void addAppToken(int addPos, IApplicationToken token,
int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked);
void setAppGroupId(IBinder token, int groupId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
@@ -190,6 +192,13 @@ interface IWindowManager
void thawRotation();
/**
+ * Gets whether the rotation is frozen.
+ *
+ * @return Whether the rotation is frozen.
+ */
+ boolean isRotationFrozen();
+
+ /**
* Create a screenshot of the applications currently displayed.
*/
Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
@@ -221,48 +230,49 @@ interface IWindowManager
IBinder getFocusedWindowToken();
/**
- * Gets the compatibility scale of e window given its token.
- */
- float getWindowCompatibilityScale(IBinder windowToken);
-
- /**
* Sets an input filter for manipulating the input event stream.
*/
void setInputFilter(in IInputFilter filter);
/**
- * Sets the scale and offset for implementing accessibility magnification.
- */
- void magnifyDisplay(int dipslayId, float scale, float offsetX, float offsetY);
-
- /**
- * Adds a listener for display content changes.
+ * Gets the frame of a window given its token.
*/
- void addDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
+ void getWindowFrame(IBinder token, out Rect outFrame);
/**
- * Removes a listener for display content changes.
+ * Device is in safe mode.
*/
- void removeDisplayContentChangeListener(int displayId, IDisplayContentChangeListener listener);
+ boolean isSafeModeEnabled();
/**
- * Gets the info for a window given its token.
+ * Tell keyguard to show the assistant (Intent.ACTION_ASSIST) after asking for the user's
+ * credentials.
*/
- WindowInfo getWindowInfo(IBinder token);
+ void showAssistant();
/**
- * Gets the infos for all visible windows.
+ * Sets the display magnification callbacks. These callbacks notify
+ * the client for contextual changes related to display magnification.
+ *
+ * @param callbacks The magnification callbacks.
*/
- void getVisibleWindowsForDisplay(int displayId, out List<WindowInfo> outInfos);
+ void setMagnificationCallbacks(IMagnificationCallbacks callbacks);
/**
- * Device is in safe mode.
+ * Sets the magnification spec to be applied to all windows that can be
+ * magnified.
+ *
+ * @param spec The current magnification spec.
*/
- boolean isSafeModeEnabled();
+ void setMagnificationSpec(in MagnificationSpec spec);
/**
- * Tell keyguard to show the assistant (Intent.ACTION_ASSIST) after asking for the user's
- * credentials.
+ * Gets the magnification spec for a window given its token. If the
+ * window has a compatibility scale it is also folded in the returned
+ * magnification spec.
+ *
+ * @param windowToken The unique window token.
+ * @return The magnification spec if such or null.
*/
- void showAssistant();
+ MagnificationSpec getCompatibleMagnificationSpecForWindow(in IBinder windowToken);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index ff9dcce..0a8e609 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -63,6 +63,9 @@ interface IWindowSession {
* {@link WindowManagerGlobal#RELAYOUT_DEFER_SURFACE_DESTROY}.
* @param outFrame Rect in which is placed the new position/size on
* screen.
+ * @param outOverscanInsets Rect in which is placed the offsets from
+ * <var>outFrame</var> in which the content of the window are inside
+ * of the display's overlay region.
* @param outContentInsets Rect in which is placed the offsets from
* <var>outFrame</var> in which the content of the window should be
* placed. This can be used to modify the window layout to ensure its
@@ -84,7 +87,7 @@ interface IWindowSession {
*/
int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
- int flags, out Rect outFrame,
+ int flags, out Rect outFrame, out Rect outOverscanInsets,
out Rect outContentInsets, out Rect outVisibleInsets,
out Configuration outConfig, out Surface outSurface);
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 2cb724f..9e5f25a 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -180,6 +180,8 @@ public class KeyCharacterMap implements Parcelable {
private static final int ACCENT_CIRCUMFLEX_LEGACY = '^';
private static final int ACCENT_TILDE_LEGACY = '~';
+ private static final int CHAR_SPACE = ' ';
+
/**
* Maps Unicode combining diacritical to display-form dead key.
*/
@@ -473,14 +475,23 @@ public class KeyCharacterMap implements Parcelable {
}
/**
- * Get the character that is produced by putting accent on the character c.
+ * Get the character that is produced by combining the dead key producing accent
+ * with the key producing character c.
* For example, getDeadChar('`', 'e') returns &egrave;.
+ * getDeadChar('^', ' ') returns '^' and getDeadChar('^', '^') returns '^'.
*
* @param accent The accent character. eg. '`'
* @param c The basic character.
* @return The combined character, or 0 if the characters cannot be combined.
*/
public static int getDeadChar(int accent, int c) {
+ if (c == accent || CHAR_SPACE == c) {
+ // The same dead character typed twice or a dead character followed by a
+ // space should both produce the non-combining version of the combining char.
+ // In this case we don't even need to compute the combining character.
+ return accent;
+ }
+
int combining = sAccentToCombining.get(accent);
if (combining == 0) {
return 0;
@@ -495,7 +506,8 @@ public class KeyCharacterMap implements Parcelable {
sDeadKeyBuilder.append((char)c);
sDeadKeyBuilder.append((char)combining);
String result = Normalizer.normalize(sDeadKeyBuilder, Normalizer.Form.NFC);
- combined = result.length() == 1 ? result.charAt(0) : 0;
+ combined = result.codePointCount(0, result.length()) == 1
+ ? result.codePointAt(0) : 0;
sDeadKeyCache.put(combination, combined);
}
}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index c2a3e58..bb533bf 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -302,27 +302,27 @@ public class KeyEvent extends InputEvent implements Parcelable {
public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana)
/** Key code constant: A Button key.
* On a game controller, the A button should be either the button labeled A
- * or the first button on the upper row of controller buttons. */
+ * or the first button on the bottom row of controller buttons. */
public static final int KEYCODE_BUTTON_A = 96;
/** Key code constant: B Button key.
* On a game controller, the B button should be either the button labeled B
- * or the second button on the upper row of controller buttons. */
+ * or the second button on the bottom row of controller buttons. */
public static final int KEYCODE_BUTTON_B = 97;
/** Key code constant: C Button key.
* On a game controller, the C button should be either the button labeled C
- * or the third button on the upper row of controller buttons. */
+ * or the third button on the bottom row of controller buttons. */
public static final int KEYCODE_BUTTON_C = 98;
/** Key code constant: X Button key.
* On a game controller, the X button should be either the button labeled X
- * or the first button on the lower row of controller buttons. */
+ * or the first button on the upper row of controller buttons. */
public static final int KEYCODE_BUTTON_X = 99;
/** Key code constant: Y Button key.
* On a game controller, the Y button should be either the button labeled Y
- * or the second button on the lower row of controller buttons. */
+ * or the second button on the upper row of controller buttons. */
public static final int KEYCODE_BUTTON_Y = 100;
/** Key code constant: Z Button key.
* On a game controller, the Z button should be either the button labeled Z
- * or the third button on the lower row of controller buttons. */
+ * or the third button on the upper row of controller buttons. */
public static final int KEYCODE_BUTTON_Z = 101;
/** Key code constant: L1 Button key.
* On a game controller, the L1 button should be either the button labeled L1 (or L)
@@ -623,8 +623,14 @@ public class KeyEvent extends InputEvent implements Parcelable {
/** Key code constant: Assist key.
* Launches the global assist activity. Not delivered to applications. */
public static final int KEYCODE_ASSIST = 219;
+ /** Key code constant: Brightness Down key.
+ * Adjusts the screen brightness down. */
+ public static final int KEYCODE_BRIGHTNESS_DOWN = 220;
+ /** Key code constant: Brightness Up key.
+ * Adjusts the screen brightness up. */
+ public static final int KEYCODE_BRIGHTNESS_UP = 221;
- private static final int LAST_KEYCODE = KEYCODE_ASSIST;
+ private static final int LAST_KEYCODE = KEYCODE_BRIGHTNESS_UP;
// NOTE: If you add a new keycode here you must also add it to:
// isSystem()
@@ -866,6 +872,8 @@ public class KeyEvent extends InputEvent implements Parcelable {
names.append(KEYCODE_RO, "KEYCODE_RO");
names.append(KEYCODE_KANA, "KEYCODE_KANA");
names.append(KEYCODE_ASSIST, "KEYCODE_ASSIST");
+ names.append(KEYCODE_BRIGHTNESS_DOWN, "KEYCODE_BRIGHTNESS_DOWN");
+ names.append(KEYCODE_BRIGHTNESS_UP, "KEYCODE_BRIGHTNESS_UP");
};
// Symbolic names of all metakeys in bit order from least significant to most significant.
diff --git a/core/java/android/view/WindowInfo.aidl b/core/java/android/view/MagnificationSpec.aidl
index 23e927a..d5fbdef 100644
--- a/core/java/android/view/WindowInfo.aidl
+++ b/core/java/android/view/MagnificationSpec.aidl
@@ -17,4 +17,4 @@
package android.view;
-parcelable WindowInfo;
+parcelable MagnificationSpec;
diff --git a/core/java/android/view/MagnificationSpec.java b/core/java/android/view/MagnificationSpec.java
new file mode 100644
index 0000000..0ee6714
--- /dev/null
+++ b/core/java/android/view/MagnificationSpec.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pools.SynchronizedPool;
+
+/**
+ * This class represents spec for performing screen magnification.
+ *
+ * @hide
+ */
+public class MagnificationSpec implements Parcelable {
+ private static final int MAX_POOL_SIZE = 20;
+ private static final SynchronizedPool<MagnificationSpec> sPool =
+ new SynchronizedPool<MagnificationSpec>(MAX_POOL_SIZE);
+
+ public float scale = 1.0f;
+ public float offsetX;
+ public float offsetY;
+
+ private MagnificationSpec() {
+ /* do nothing - reducing visibility */
+ }
+
+ public void initialize(float scale, float offsetX, float offsetY) {
+ if (scale < 1) {
+ throw new IllegalArgumentException("Scale must be greater than or equal to one!");
+ }
+ this.scale = scale;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ }
+
+ public boolean isNop() {
+ return scale == 1.0f && offsetX == 0 && offsetY == 0;
+ }
+
+ public static MagnificationSpec obtain(MagnificationSpec other) {
+ MagnificationSpec info = obtain();
+ info.scale = other.scale;
+ info.offsetX = other.offsetX;
+ info.offsetY = other.offsetY;
+ return info;
+ }
+
+ public static MagnificationSpec obtain() {
+ MagnificationSpec spec = sPool.acquire();
+ return (spec != null) ? spec : new MagnificationSpec();
+ }
+
+ public void recycle() {
+ clear();
+ sPool.release(this);
+ }
+
+ public void clear() {
+ scale = 1.0f;
+ offsetX = 0.0f;
+ offsetY = 0.0f;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeFloat(scale);
+ parcel.writeFloat(offsetX);
+ parcel.writeFloat(offsetY);
+ recycle();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("<scale:");
+ builder.append(scale);
+ builder.append(",offsetX:");
+ builder.append(offsetX);
+ builder.append(",offsetY:");
+ builder.append(offsetY);
+ builder.append(">");
+ return builder.toString();
+ }
+
+ private void initFromParcel(Parcel parcel) {
+ scale = parcel.readFloat();
+ offsetX = parcel.readFloat();
+ offsetY = parcel.readFloat();
+ }
+
+ public static final Creator<MagnificationSpec> CREATOR = new Creator<MagnificationSpec>() {
+ @Override
+ public MagnificationSpec[] newArray(int size) {
+ return new MagnificationSpec[size];
+ }
+
+ @Override
+ public MagnificationSpec createFromParcel(Parcel parcel) {
+ MagnificationSpec spec = MagnificationSpec.obtain();
+ spec.initFromParcel(parcel);
+ return spec;
+ }
+ };
+}
diff --git a/core/java/android/view/SimulatedDpad.java b/core/java/android/view/SimulatedDpad.java
index b03e4c7..883fd49 100644
--- a/core/java/android/view/SimulatedDpad.java
+++ b/core/java/android/view/SimulatedDpad.java
@@ -186,7 +186,7 @@ class SimulatedDpad {
Intent intent =
((SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE))
- .getAssistIntent(mContext, UserHandle.USER_CURRENT_OR_SELF);
+ .getAssistIntent(mContext, false, UserHandle.USER_CURRENT_OR_SELF);
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 0a81a71..03a9b09 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -16,20 +16,15 @@
package android.view;
-import dalvik.system.CloseGuard;
-
import android.content.res.CompatibilityInfo.Translator;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.SurfaceTexture;
-import android.os.IBinder;
-import android.os.Parcelable;
import android.os.Parcel;
-import android.os.SystemProperties;
+import android.os.Parcelable;
import android.util.Log;
+import dalvik.system.CloseGuard;
/**
* Handle onto a raw buffer that is being managed by the screen compositor.
@@ -37,8 +32,19 @@ import android.util.Log;
public class Surface implements Parcelable {
private static final String TAG = "Surface";
- private static final boolean HEADLESS = "1".equals(
- SystemProperties.get("ro.config.headless", "0"));
+ private static native int nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
+ throws OutOfResourcesException;
+
+ private native Canvas nativeLockCanvas(int nativeObject, Rect dirty);
+ private native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas);
+
+ private static native void nativeRelease(int nativeObject);
+ private static native void nativeDestroy(int nativeObject);
+ private static native boolean nativeIsValid(int nativeObject);
+ private static native boolean nativeIsConsumerRunningBehind(int nativeObject);
+ private static native int nativeCopyFrom(int nativeObject, int surfaceControlNativeObject);
+ private static native int nativeReadFromParcel(int nativeObject, Parcel source);
+ private static native void nativeWriteToParcel(int nativeObject, Parcel dest);
public static final Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@@ -52,157 +58,11 @@ public class Surface implements Parcelable {
return null;
}
}
-
public Surface[] newArray(int size) {
return new Surface[size];
}
};
- /**
- * Rotation constant: 0 degree rotation (natural orientation)
- */
- public static final int ROTATION_0 = 0;
-
- /**
- * Rotation constant: 90 degree rotation.
- */
- public static final int ROTATION_90 = 1;
-
- /**
- * Rotation constant: 180 degree rotation.
- */
- public static final int ROTATION_180 = 2;
-
- /**
- * Rotation constant: 270 degree rotation.
- */
- public static final int ROTATION_270 = 3;
-
- /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
- * these are different from the logical display ids used elsewhere in the framework */
-
- /**
- * Built-in physical display id: Main display.
- * Use only with {@link #getBuiltInDisplay()}.
- * @hide
- */
- public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
-
- /**
- * Built-in physical display id: Attached HDMI display.
- * Use only with {@link #getBuiltInDisplay()}.
- * @hide
- */
- public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
-
- /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
-
- /**
- * Surface creation flag: Surface is created hidden
- * @hide */
- public static final int HIDDEN = 0x00000004;
-
- /**
- * Surface creation flag: The surface contains secure content, special
- * measures will be taken to disallow the surface's content to be copied
- * from another process. In particular, screenshots and VNC servers will
- * be disabled, but other measures can take place, for instance the
- * surface might not be hardware accelerated.
- * @hide
- */
- public static final int SECURE = 0x00000080;
-
- /**
- * Surface creation flag: Creates a surface where color components are interpreted
- * as "non pre-multiplied" by their alpha channel. Of course this flag is
- * meaningless for surfaces without an alpha channel. By default
- * surfaces are pre-multiplied, which means that each color component is
- * already multiplied by its alpha value. In this case the blending
- * equation used is:
- *
- * DEST = SRC + DEST * (1-SRC_ALPHA)
- *
- * By contrast, non pre-multiplied surfaces use the following equation:
- *
- * DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA)
- *
- * pre-multiplied surfaces must always be used if transparent pixels are
- * composited on top of each-other into the surface. A pre-multiplied
- * surface can never lower the value of the alpha component of a given
- * pixel.
- *
- * In some rare situations, a non pre-multiplied surface is preferable.
- * @hide
- */
- public static final int NON_PREMULTIPLIED = 0x00000100;
-
- /**
- * Surface creation flag: Indicates that the surface must be considered opaque,
- * even if its pixel format is set to translucent. This can be useful if an
- * application needs full RGBA 8888 support for instance but will
- * still draw every pixel opaque.
- * @hide
- */
- public static final int OPAQUE = 0x00000400;
-
- /**
- * Surface creation flag: Application requires a hardware-protected path to an
- * external display sink. If a hardware-protected path is not available,
- * then this surface will not be displayed on the external sink.
- * @hide
- */
- public static final int PROTECTED_APP = 0x00000800;
-
- // 0x1000 is reserved for an independent DRM protected flag in framework
-
- /**
- * Surface creation flag: Creates a normal surface.
- * This is the default.
- * @hide
- */
- public static final int FX_SURFACE_NORMAL = 0x00000000;
-
- /**
- * Surface creation flag: Creates a Blur surface.
- * Everything behind this surface is blurred by some amount.
- * The quality and refresh speed of the blur effect is not settable or guaranteed.
- * It is an error to lock a Blur surface, since it doesn't have a backing store.
- * @hide
- * @deprecated
- */
- @Deprecated
- public static final int FX_SURFACE_BLUR = 0x00010000;
-
- /**
- * Surface creation flag: Creates a Dim surface.
- * Everything behind this surface is dimmed by the amount specified
- * in {@link #setAlpha}. It is an error to lock a Dim surface, since it
- * doesn't have a backing store.
- * @hide
- */
- public static final int FX_SURFACE_DIM = 0x00020000;
-
- /**
- * @hide
- */
- public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
-
- /**
- * Mask used for FX values above.
- * @hide
- */
- public static final int FX_SURFACE_MASK = 0x000F0000;
-
- /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
-
- /**
- * Surface flag: Hide the surface.
- * Equivalent to calling hide().
- * @hide
- */
- public static final int SURFACE_HIDDEN = 0x01;
-
-
private final CloseGuard mCloseGuard = CloseGuard.get();
private String mName;
@@ -211,11 +71,10 @@ public class Surface implements Parcelable {
// server or system processes. When this class is parceled we defer to the
// mSurfaceControl to do the parceling. Otherwise we parcel the
// mNativeSurface.
- private int mNativeSurface; // Surface*
- private int mNativeSurfaceControl; // SurfaceControl*
+ int mNativeObject; // package scope only for SurfaceControl access
+
private int mGenerationId; // incremented each time mNativeSurface changes
private final Canvas mCanvas = new CompatibleCanvas();
- private int mCanvasSaveCount; // Canvas save count at time of lockCanvas()
// The Translator for density compatibility mode. This is used for scaling
// the canvas to perform the appropriate density transformation.
@@ -225,118 +84,34 @@ public class Surface implements Parcelable {
// non compatibility mode.
private Matrix mCompatibleMatrix;
- private int mWidth;
- private int mHeight;
-
- private native void nativeCreate(SurfaceSession session, String name,
- int w, int h, int format, int flags)
- throws OutOfResourcesException;
- private native void nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
- throws OutOfResourcesException;
- private native void nativeRelease();
- private native void nativeDestroy();
-
- private native boolean nativeIsValid();
- private native int nativeGetIdentity();
- private native boolean nativeIsConsumerRunningBehind();
-
- private native Canvas nativeLockCanvas(Rect dirty);
- private native void nativeUnlockCanvasAndPost(Canvas canvas);
-
- private static native Bitmap nativeScreenshot(IBinder displayToken,
- int width, int height, int minLayer, int maxLayer, boolean allLayers);
-
- private static native void nativeOpenTransaction();
- private static native void nativeCloseTransaction();
- private static native void nativeSetAnimationTransaction();
-
- private native void nativeSetLayer(int zorder);
- private native void nativeSetPosition(float x, float y);
- private native void nativeSetSize(int w, int h);
- private native void nativeSetTransparentRegionHint(Region region);
- private native void nativeSetAlpha(float alpha);
- private native void nativeSetMatrix(float dsdx, float dtdx, float dsdy, float dtdy);
- private native void nativeSetFlags(int flags, int mask);
- private native void nativeSetWindowCrop(Rect crop);
- private native void nativeSetLayerStack(int layerStack);
-
- private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
- private static native IBinder nativeCreateDisplay(String name, boolean secure);
- private static native void nativeSetDisplaySurface(
- IBinder displayToken, Surface surface);
- private static native void nativeSetDisplayLayerStack(
- IBinder displayToken, int layerStack);
- private static native void nativeSetDisplayProjection(
- IBinder displayToken, int orientation, Rect layerStackRect, Rect displayRect);
- private static native boolean nativeGetDisplayInfo(
- IBinder displayToken, PhysicalDisplayInfo outInfo);
- private static native void nativeBlankDisplay(IBinder displayToken);
- private static native void nativeUnblankDisplay(IBinder displayToken);
-
- private native void nativeCopyFrom(Surface other);
- private native void nativeTransferFrom(Surface other);
- private native void nativeReadFromParcel(Parcel source);
- private native void nativeWriteToParcel(Parcel dest);
-
/**
- * Create an empty surface, which will later be filled in by readFromParcel().
- * @hide
+ * Rotation constant: 0 degree rotation (natural orientation)
*/
- public Surface() {
- checkHeadless();
+ public static final int ROTATION_0 = 0;
- mCloseGuard.open("release");
- }
+ /**
+ * Rotation constant: 90 degree rotation.
+ */
+ public static final int ROTATION_90 = 1;
/**
- * Create a surface with a name.
- *
- * The surface creation flags specify what kind of surface to create and
- * certain options such as whether the surface can be assumed to be opaque
- * and whether it should be initially hidden. Surfaces should always be
- * created with the {@link #HIDDEN} flag set to ensure that they are not
- * made visible prematurely before all of the surface's properties have been
- * configured.
- *
- * Good practice is to first create the surface with the {@link #HIDDEN} flag
- * specified, open a transaction, set the surface layer, layer stack, alpha,
- * and position, call {@link #show} if appropriate, and close the transaction.
- *
- * @param session The surface session, must not be null.
- * @param name The surface name, must not be null.
- * @param w The surface initial width.
- * @param h The surface initial height.
- * @param flags The surface creation flags. Should always include {@link #HIDDEN}
- * in the creation flags.
- * @hide
+ * Rotation constant: 180 degree rotation.
*/
- public Surface(SurfaceSession session,
- String name, int w, int h, int format, int flags)
- throws OutOfResourcesException {
- if (session == null) {
- throw new IllegalArgumentException("session must not be null");
- }
- if (name == null) {
- throw new IllegalArgumentException("name must not be null");
- }
+ public static final int ROTATION_180 = 2;
- if ((flags & HIDDEN) == 0) {
- Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
- + "to ensure that they are not made visible prematurely before "
- + "all of the surface's properties have been configured. "
- + "Set the other properties and make the surface visible within "
- + "a transaction. New surface name: " + name,
- new Throwable());
- }
+ /**
+ * Rotation constant: 270 degree rotation.
+ */
+ public static final int ROTATION_270 = 3;
- checkHeadless();
- mName = name;
- mWidth = w;
- mHeight = h;
- nativeCreate(session, name, w, h, format, flags);
+ /**
+ * Create an empty surface, which will later be filled in by readFromParcel().
+ * @hide
+ */
+ public Surface() {
mCloseGuard.open("release");
}
@@ -355,11 +130,9 @@ public class Surface implements Parcelable {
throw new IllegalArgumentException("surfaceTexture must not be null");
}
- checkHeadless();
-
mName = surfaceTexture.toString();
try {
- nativeCreateFromSurfaceTexture(surfaceTexture);
+ mNativeObject = nativeCreateFromSurfaceTexture(surfaceTexture);
} catch (OutOfResourcesException ex) {
// We can't throw OutOfResourcesException because it would be an API change.
throw new RuntimeException(ex);
@@ -368,13 +141,20 @@ public class Surface implements Parcelable {
mCloseGuard.open("release");
}
+ private Surface(int nativeObject) {
+ mNativeObject = nativeObject;
+ mCloseGuard.open("release");
+ }
+
@Override
protected void finalize() throws Throwable {
try {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- nativeRelease();
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
} finally {
super.finalize();
}
@@ -386,7 +166,10 @@ public class Surface implements Parcelable {
* This will make the surface invalid.
*/
public void release() {
- nativeRelease();
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ mNativeObject = 0;
+ }
mCloseGuard.close();
}
@@ -397,7 +180,10 @@ public class Surface implements Parcelable {
* @hide
*/
public void destroy() {
- nativeDestroy();
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ mNativeObject = 0;
+ }
mCloseGuard.close();
}
@@ -408,7 +194,8 @@ public class Surface implements Parcelable {
* Otherwise returns false.
*/
public boolean isValid() {
- return nativeIsValid();
+ if (mNativeObject == 0) return false;
+ return nativeIsValid(mNativeObject);
}
/**
@@ -429,7 +216,8 @@ public class Surface implements Parcelable {
* @hide
*/
public boolean isConsumerRunningBehind() {
- return nativeIsConsumerRunningBehind();
+ checkNotReleased();
+ return nativeIsConsumerRunningBehind(mNativeObject);
}
/**
@@ -438,7 +226,7 @@ public class Surface implements Parcelable {
* After drawing into the provided {@link Canvas}, the caller should
* invoke {@link #unlockCanvasAndPost} to post the new contents to the surface.
*
- * @param dirty A rectangle that represents the dirty region that the caller wants
+ * @param inOutDirty A rectangle that represents the dirty region that the caller wants
* to redraw. This function may choose to expand the dirty rectangle if for example
* the surface has been resized or if the previous contents of the surface were
* not available. The caller should redraw the entire dirty region as represented
@@ -447,9 +235,10 @@ public class Surface implements Parcelable {
* entire surface should be redrawn.
* @return A canvas for drawing into the surface.
*/
- public Canvas lockCanvas(Rect dirty)
+ public Canvas lockCanvas(Rect inOutDirty)
throws OutOfResourcesException, IllegalArgumentException {
- return nativeLockCanvas(dirty);
+ checkNotReleased();
+ return nativeLockCanvas(mNativeObject, inOutDirty);
}
/**
@@ -459,7 +248,8 @@ public class Surface implements Parcelable {
* @param canvas The canvas previously obtained from {@link #lockCanvas}.
*/
public void unlockCanvasAndPost(Canvas canvas) {
- nativeUnlockCanvasAndPost(canvas);
+ checkNotReleased();
+ nativeUnlockCanvasAndPost(mNativeObject, canvas);
}
/**
@@ -482,202 +272,6 @@ public class Surface implements Parcelable {
}
}
- /**
- * Like {@link #screenshot(int, int, int, int)} but includes all
- * Surfaces in the screenshot.
- *
- * @hide
- */
- public static Bitmap screenshot(int width, int height) {
- // TODO: should take the display as a parameter
- IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN);
- return nativeScreenshot(displayToken, width, height, 0, 0, true);
- }
-
- /**
- * Copy the current screen contents into a bitmap and return it.
- *
- * @param width The desired width of the returned bitmap; the raw
- * screen will be scaled down to this size.
- * @param height The desired height of the returned bitmap; the raw
- * screen will be scaled down to this size.
- * @param minLayer The lowest (bottom-most Z order) surface layer to
- * include in the screenshot.
- * @param maxLayer The highest (top-most Z order) surface layer to
- * include in the screenshot.
- * @return Returns a Bitmap containing the screen contents, or null
- * if an error occurs.
- *
- * @hide
- */
- public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
- // TODO: should take the display as a parameter
- IBinder displayToken = getBuiltInDisplay(BUILT_IN_DISPLAY_ID_MAIN);
- return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
- }
-
- /*
- * set surface parameters.
- * needs to be inside open/closeTransaction block
- */
-
- /** start a transaction @hide */
- public static void openTransaction() {
- nativeOpenTransaction();
- }
-
- /** end a transaction @hide */
- public static void closeTransaction() {
- nativeCloseTransaction();
- }
-
- /** flag the transaction as an animation @hide */
- public static void setAnimationTransaction() {
- nativeSetAnimationTransaction();
- }
-
- /** @hide */
- public void setLayer(int zorder) {
- nativeSetLayer(zorder);
- }
-
- /** @hide */
- public void setPosition(int x, int y) {
- nativeSetPosition(x, y);
- }
-
- /** @hide */
- public void setPosition(float x, float y) {
- nativeSetPosition(x, y);
- }
-
- /** @hide */
- public void setSize(int w, int h) {
- mWidth = w;
- mHeight = h;
- nativeSetSize(w, h);
- }
-
- /** @hide */
- public int getWidth() {
- return mWidth;
- }
-
- /** @hide */
- public int getHeight() {
- return mHeight;
- }
-
- /** @hide */
- public void hide() {
- nativeSetFlags(SURFACE_HIDDEN, SURFACE_HIDDEN);
- }
-
- /** @hide */
- public void show() {
- nativeSetFlags(0, SURFACE_HIDDEN);
- }
-
- /** @hide */
- public void setTransparentRegionHint(Region region) {
- nativeSetTransparentRegionHint(region);
- }
-
- /** @hide */
- public void setAlpha(float alpha) {
- nativeSetAlpha(alpha);
- }
-
- /** @hide */
- public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
- nativeSetMatrix(dsdx, dtdx, dsdy, dtdy);
- }
-
- /** @hide */
- public void setFlags(int flags, int mask) {
- nativeSetFlags(flags, mask);
- }
-
- /** @hide */
- public void setWindowCrop(Rect crop) {
- nativeSetWindowCrop(crop);
- }
-
- /** @hide */
- public void setLayerStack(int layerStack) {
- nativeSetLayerStack(layerStack);
- }
-
- /** @hide */
- public static IBinder getBuiltInDisplay(int builtInDisplayId) {
- return nativeGetBuiltInDisplay(builtInDisplayId);
- }
-
- /** @hide */
- public static IBinder createDisplay(String name, boolean secure) {
- if (name == null) {
- throw new IllegalArgumentException("name must not be null");
- }
- return nativeCreateDisplay(name, secure);
- }
-
- /** @hide */
- public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeSetDisplaySurface(displayToken, surface);
- }
-
- /** @hide */
- public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeSetDisplayLayerStack(displayToken, layerStack);
- }
-
- /** @hide */
- public static void setDisplayProjection(IBinder displayToken,
- int orientation, Rect layerStackRect, Rect displayRect) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (layerStackRect == null) {
- throw new IllegalArgumentException("layerStackRect must not be null");
- }
- if (displayRect == null) {
- throw new IllegalArgumentException("displayRect must not be null");
- }
- nativeSetDisplayProjection(displayToken, orientation, layerStackRect, displayRect);
- }
-
- /** @hide */
- public static boolean getDisplayInfo(IBinder displayToken, PhysicalDisplayInfo outInfo) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- if (outInfo == null) {
- throw new IllegalArgumentException("outInfo must not be null");
- }
- return nativeGetDisplayInfo(displayToken, outInfo);
- }
-
- /** @hide */
- public static void blankDisplay(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeBlankDisplay(displayToken);
- }
-
- /** @hide */
- public static void unblankDisplay(IBinder displayToken) {
- if (displayToken == null) {
- throw new IllegalArgumentException("displayToken must not be null");
- }
- nativeUnblankDisplay(displayToken);
- }
/**
* Copy another surface to this one. This surface now holds a reference
@@ -688,13 +282,15 @@ public class Surface implements Parcelable {
* in to it.
* @hide
*/
- public void copyFrom(Surface other) {
+ public void copyFrom(SurfaceControl other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null");
}
- if (other != this) {
- nativeCopyFrom(other);
+ if (other.mNativeObject == 0) {
+ throw new NullPointerException(
+ "SurfaceControl native object is null. Are you using a released SurfaceControl?");
}
+ mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject);
}
/**
@@ -710,7 +306,13 @@ public class Surface implements Parcelable {
throw new IllegalArgumentException("other must not be null");
}
if (other != this) {
- nativeTransferFrom(other);
+ if (mNativeObject != 0) {
+ // release our reference to our native object
+ nativeRelease(mNativeObject);
+ }
+ // transfer the reference from other to us
+ mNativeObject = other.mNativeObject;
+ other.mNativeObject = 0;
}
}
@@ -723,9 +325,8 @@ public class Surface implements Parcelable {
if (source == null) {
throw new IllegalArgumentException("source must not be null");
}
-
mName = source.readString();
- nativeReadFromParcel(source);
+ mNativeObject = nativeReadFromParcel(mNativeObject, source);
}
@Override
@@ -733,9 +334,8 @@ public class Surface implements Parcelable {
if (dest == null) {
throw new IllegalArgumentException("dest must not be null");
}
-
dest.writeString(mName);
- nativeWriteToParcel(dest);
+ nativeWriteToParcel(mNativeObject, dest);
if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
release();
}
@@ -743,13 +343,7 @@ public class Surface implements Parcelable {
@Override
public String toString() {
- return "Surface(name=" + mName + ", identity=" + nativeGetIdentity() + ")";
- }
-
- private static void checkHeadless() {
- if (HEADLESS) {
- throw new UnsupportedOperationException("Device is headless");
- }
+ return "Surface(name=" + mName + ")";
}
/**
@@ -758,69 +352,36 @@ public class Surface implements Parcelable {
public static class OutOfResourcesException extends Exception {
public OutOfResourcesException() {
}
-
public OutOfResourcesException(String name) {
super(name);
}
}
/**
- * Describes the properties of a physical display known to surface flinger.
+ * Returns a human readable representation of a rotation.
+ *
+ * @param rotation The rotation.
+ * @return The rotation symbolic name.
+ *
* @hide
*/
- public static final class PhysicalDisplayInfo {
- public int width;
- public int height;
- public float refreshRate;
- public float density;
- public float xDpi;
- public float yDpi;
- public boolean secure;
-
- public PhysicalDisplayInfo() {
- }
-
- public PhysicalDisplayInfo(PhysicalDisplayInfo other) {
- copyFrom(other);
- }
-
- @Override
- public boolean equals(Object o) {
- return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o);
- }
-
- public boolean equals(PhysicalDisplayInfo other) {
- return other != null
- && width == other.width
- && height == other.height
- && refreshRate == other.refreshRate
- && density == other.density
- && xDpi == other.xDpi
- && yDpi == other.yDpi
- && secure == other.secure;
- }
-
- @Override
- public int hashCode() {
- return 0; // don't care
- }
-
- public void copyFrom(PhysicalDisplayInfo other) {
- width = other.width;
- height = other.height;
- refreshRate = other.refreshRate;
- density = other.density;
- xDpi = other.xDpi;
- yDpi = other.yDpi;
- secure = other.secure;
- }
-
- // For debugging purposes
- @Override
- public String toString() {
- return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
- + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure
- + "}";
+ public static String rotationToString(int rotation) {
+ switch (rotation) {
+ case Surface.ROTATION_0: {
+ return "ROTATION_0";
+ }
+ case Surface.ROTATION_90: {
+ return "ROATATION_90";
+ }
+ case Surface.ROTATION_180: {
+ return "ROATATION_180";
+ }
+ case Surface.ROTATION_270: {
+ return "ROATATION_270";
+ }
+ default: {
+ throw new IllegalArgumentException("Invalid rotation: " + rotation);
+ }
}
}
@@ -883,4 +444,9 @@ public class Surface implements Parcelable {
mOrigMatrix.set(m);
}
}
+
+ private void checkNotReleased() {
+ if (mNativeObject == 0) throw new NullPointerException(
+ "mNativeObject is null. Have you called release() already?");
+ }
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
new file mode 100644
index 0000000..ded2f47
--- /dev/null
+++ b/core/java/android/view/SurfaceControl.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import dalvik.system.CloseGuard;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.SystemProperties;
+import android.util.Log;
+
+/**
+ * SurfaceControl
+ * @hide
+ */
+public class SurfaceControl {
+ private static final String TAG = "SurfaceControl";
+
+ private static native int nativeCreate(SurfaceSession session, String name,
+ int w, int h, int format, int flags)
+ throws OutOfResourcesException;
+ private static native void nativeRelease(int nativeObject);
+ private static native void nativeDestroy(int nativeObject);
+
+ private static native Bitmap nativeScreenshot(IBinder displayToken,
+ int width, int height, int minLayer, int maxLayer, boolean allLayers);
+
+ private static native void nativeOpenTransaction();
+ private static native void nativeCloseTransaction();
+ private static native void nativeSetAnimationTransaction();
+
+ private static native void nativeSetLayer(int nativeObject, int zorder);
+ private static native void nativeSetPosition(int nativeObject, float x, float y);
+ private static native void nativeSetSize(int nativeObject, int w, int h);
+ private static native void nativeSetTransparentRegionHint(int nativeObject, Region region);
+ private static native void nativeSetAlpha(int nativeObject, float alpha);
+ private static native void nativeSetMatrix(int nativeObject, float dsdx, float dtdx, float dsdy, float dtdy);
+ private static native void nativeSetFlags(int nativeObject, int flags, int mask);
+ private static native void nativeSetWindowCrop(int nativeObject, int l, int t, int r, int b);
+ private static native void nativeSetLayerStack(int nativeObject, int layerStack);
+
+ private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
+ private static native IBinder nativeCreateDisplay(String name, boolean secure);
+ private static native void nativeSetDisplaySurface(
+ IBinder displayToken, int nativeSurfaceObject);
+ private static native void nativeSetDisplayLayerStack(
+ IBinder displayToken, int layerStack);
+ private static native void nativeSetDisplayProjection(
+ IBinder displayToken, int orientation,
+ int l, int t, int r, int b,
+ int L, int T, int R, int B);
+ private static native boolean nativeGetDisplayInfo(
+ IBinder displayToken, SurfaceControl.PhysicalDisplayInfo outInfo);
+ private static native void nativeBlankDisplay(IBinder displayToken);
+ private static native void nativeUnblankDisplay(IBinder displayToken);
+
+
+ private final CloseGuard mCloseGuard = CloseGuard.get();
+ private String mName;
+ int mNativeObject; // package visibility only for Surface.java access
+
+ private static final boolean HEADLESS = "1".equals(
+ SystemProperties.get("ro.config.headless", "0"));
+
+ /**
+ * Exception thrown when a surface couldn't be created or resized.
+ */
+ public static class OutOfResourcesException extends Exception {
+ public OutOfResourcesException() {
+ }
+ public OutOfResourcesException(String name) {
+ super(name);
+ }
+ }
+
+ /* flags used in constructor (keep in sync with ISurfaceComposerClient.h) */
+
+ /**
+ * Surface creation flag: Surface is created hidden
+ */
+ public static final int HIDDEN = 0x00000004;
+
+ /**
+ * Surface creation flag: The surface contains secure content, special
+ * measures will be taken to disallow the surface's content to be copied
+ * from another process. In particular, screenshots and VNC servers will
+ * be disabled, but other measures can take place, for instance the
+ * surface might not be hardware accelerated.
+ *
+ */
+ public static final int SECURE = 0x00000080;
+
+ /**
+ * Surface creation flag: Creates a surface where color components are interpreted
+ * as "non pre-multiplied" by their alpha channel. Of course this flag is
+ * meaningless for surfaces without an alpha channel. By default
+ * surfaces are pre-multiplied, which means that each color component is
+ * already multiplied by its alpha value. In this case the blending
+ * equation used is:
+ *
+ * DEST = SRC + DEST * (1-SRC_ALPHA)
+ *
+ * By contrast, non pre-multiplied surfaces use the following equation:
+ *
+ * DEST = SRC * SRC_ALPHA * DEST * (1-SRC_ALPHA)
+ *
+ * pre-multiplied surfaces must always be used if transparent pixels are
+ * composited on top of each-other into the surface. A pre-multiplied
+ * surface can never lower the value of the alpha component of a given
+ * pixel.
+ *
+ * In some rare situations, a non pre-multiplied surface is preferable.
+ *
+ */
+ public static final int NON_PREMULTIPLIED = 0x00000100;
+
+ /**
+ * Surface creation flag: Indicates that the surface must be considered opaque,
+ * even if its pixel format is set to translucent. This can be useful if an
+ * application needs full RGBA 8888 support for instance but will
+ * still draw every pixel opaque.
+ *
+ */
+ public static final int OPAQUE = 0x00000400;
+
+ /**
+ * Surface creation flag: Application requires a hardware-protected path to an
+ * external display sink. If a hardware-protected path is not available,
+ * then this surface will not be displayed on the external sink.
+ *
+ */
+ public static final int PROTECTED_APP = 0x00000800;
+
+ // 0x1000 is reserved for an independent DRM protected flag in framework
+
+ /**
+ * Surface creation flag: Creates a normal surface.
+ * This is the default.
+ *
+ */
+ public static final int FX_SURFACE_NORMAL = 0x00000000;
+
+ /**
+ * Surface creation flag: Creates a Blur surface.
+ * Everything behind this surface is blurred by some amount.
+ * The quality and refresh speed of the blur effect is not settable or guaranteed.
+ * It is an error to lock a Blur surface, since it doesn't have a backing store.
+ *
+ * @deprecated
+ */
+ @Deprecated
+ public static final int FX_SURFACE_BLUR = 0x00010000;
+
+ /**
+ * Surface creation flag: Creates a Dim surface.
+ * Everything behind this surface is dimmed by the amount specified
+ * in {@link #setAlpha}. It is an error to lock a Dim surface, since it
+ * doesn't have a backing store.
+ *
+ */
+ public static final int FX_SURFACE_DIM = 0x00020000;
+
+ /**
+ *
+ */
+ public static final int FX_SURFACE_SCREENSHOT = 0x00030000;
+
+ /**
+ * Mask used for FX values above.
+ *
+ */
+ public static final int FX_SURFACE_MASK = 0x000F0000;
+
+ /* flags used with setFlags() (keep in sync with ISurfaceComposer.h) */
+
+ /**
+ * Surface flag: Hide the surface.
+ * Equivalent to calling hide().
+ */
+ public static final int SURFACE_HIDDEN = 0x01;
+
+
+ /* built-in physical display ids (keep in sync with ISurfaceComposer.h)
+ * these are different from the logical display ids used elsewhere in the framework */
+
+ /**
+ * Built-in physical display id: Main display.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay()}.
+ */
+ public static final int BUILT_IN_DISPLAY_ID_MAIN = 0;
+
+ /**
+ * Built-in physical display id: Attached HDMI display.
+ * Use only with {@link SurfaceControl#getBuiltInDisplay()}.
+ */
+ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1;
+
+
+
+ /**
+ * Create a surface with a name.
+ *
+ * The surface creation flags specify what kind of surface to create and
+ * certain options such as whether the surface can be assumed to be opaque
+ * and whether it should be initially hidden. Surfaces should always be
+ * created with the {@link #HIDDEN} flag set to ensure that they are not
+ * made visible prematurely before all of the surface's properties have been
+ * configured.
+ *
+ * Good practice is to first create the surface with the {@link #HIDDEN} flag
+ * specified, open a transaction, set the surface layer, layer stack, alpha,
+ * and position, call {@link #show} if appropriate, and close the transaction.
+ *
+ * @param session The surface session, must not be null.
+ * @param name The surface name, must not be null.
+ * @param w The surface initial width.
+ * @param h The surface initial height.
+ * @param flags The surface creation flags. Should always include {@link #HIDDEN}
+ * in the creation flags.
+ */
+ public SurfaceControl(SurfaceSession session,
+ String name, int w, int h, int format, int flags)
+ throws OutOfResourcesException {
+ if (session == null) {
+ throw new IllegalArgumentException("session must not be null");
+ }
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+
+ if ((flags & SurfaceControl.HIDDEN) == 0) {
+ Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
+ + "to ensure that they are not made visible prematurely before "
+ + "all of the surface's properties have been configured. "
+ + "Set the other properties and make the surface visible within "
+ + "a transaction. New surface name: " + name,
+ new Throwable());
+ }
+
+ checkHeadless();
+
+ mName = name;
+ mNativeObject = nativeCreate(session, name, w, h, format, flags);
+ if (mNativeObject == 0) {
+ throw new OutOfResourcesException(
+ "Couldn't allocate SurfaceControl native object");
+ }
+
+ mCloseGuard.open("release");
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Surface(name=" + mName + ")";
+ }
+
+ /**
+ * Release the local reference to the server-side surface.
+ * Always call release() when you're done with a Surface.
+ * This will make the surface invalid.
+ */
+ public void release() {
+ if (mNativeObject != 0) {
+ nativeRelease(mNativeObject);
+ mNativeObject = 0;
+ }
+ mCloseGuard.close();
+ }
+
+ /**
+ * Free all server-side state associated with this surface and
+ * release this object's reference. This method can only be
+ * called from the process that created the service.
+ */
+ public void destroy() {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ mNativeObject = 0;
+ }
+ mCloseGuard.close();
+ }
+
+ private void checkNotReleased() {
+ if (mNativeObject == 0) throw new NullPointerException(
+ "mNativeObject is null. Have you called release() already?");
+ }
+
+ /*
+ * set surface parameters.
+ * needs to be inside open/closeTransaction block
+ */
+
+ /** start a transaction */
+ public static void openTransaction() {
+ nativeOpenTransaction();
+ }
+
+ /** end a transaction */
+ public static void closeTransaction() {
+ nativeCloseTransaction();
+ }
+
+ /** flag the transaction as an animation */
+ public static void setAnimationTransaction() {
+ nativeSetAnimationTransaction();
+ }
+
+ public void setLayer(int zorder) {
+ checkNotReleased();
+ nativeSetLayer(mNativeObject, zorder);
+ }
+
+ public void setPosition(float x, float y) {
+ checkNotReleased();
+ nativeSetPosition(mNativeObject, x, y);
+ }
+
+ public void setSize(int w, int h) {
+ checkNotReleased();
+ nativeSetSize(mNativeObject, w, h);
+ }
+
+ public void hide() {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
+ }
+
+ public void show() {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, 0, SURFACE_HIDDEN);
+ }
+
+ public void setTransparentRegionHint(Region region) {
+ checkNotReleased();
+ nativeSetTransparentRegionHint(mNativeObject, region);
+ }
+
+ public void setAlpha(float alpha) {
+ checkNotReleased();
+ nativeSetAlpha(mNativeObject, alpha);
+ }
+
+ public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+ checkNotReleased();
+ nativeSetMatrix(mNativeObject, dsdx, dtdx, dsdy, dtdy);
+ }
+
+ public void setFlags(int flags, int mask) {
+ checkNotReleased();
+ nativeSetFlags(mNativeObject, flags, mask);
+ }
+
+ public void setWindowCrop(Rect crop) {
+ checkNotReleased();
+ if (crop != null) {
+ nativeSetWindowCrop(mNativeObject,
+ crop.left, crop.top, crop.right, crop.bottom);
+ } else {
+ nativeSetWindowCrop(mNativeObject, 0, 0, 0, 0);
+ }
+ }
+
+ public void setLayerStack(int layerStack) {
+ checkNotReleased();
+ nativeSetLayerStack(mNativeObject, layerStack);
+ }
+
+ /*
+ * set display parameters.
+ * needs to be inside open/closeTransaction block
+ */
+
+ /**
+ * Describes the properties of a physical display known to surface flinger.
+ */
+ public static final class PhysicalDisplayInfo {
+ public int width;
+ public int height;
+ public float refreshRate;
+ public float density;
+ public float xDpi;
+ public float yDpi;
+ public boolean secure;
+
+ public PhysicalDisplayInfo() {
+ }
+
+ public PhysicalDisplayInfo(PhysicalDisplayInfo other) {
+ copyFrom(other);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof PhysicalDisplayInfo && equals((PhysicalDisplayInfo)o);
+ }
+
+ public boolean equals(PhysicalDisplayInfo other) {
+ return other != null
+ && width == other.width
+ && height == other.height
+ && refreshRate == other.refreshRate
+ && density == other.density
+ && xDpi == other.xDpi
+ && yDpi == other.yDpi
+ && secure == other.secure;
+ }
+
+ @Override
+ public int hashCode() {
+ return 0; // don't care
+ }
+
+ public void copyFrom(PhysicalDisplayInfo other) {
+ width = other.width;
+ height = other.height;
+ refreshRate = other.refreshRate;
+ density = other.density;
+ xDpi = other.xDpi;
+ yDpi = other.yDpi;
+ secure = other.secure;
+ }
+
+ // For debugging purposes
+ @Override
+ public String toString() {
+ return "PhysicalDisplayInfo{" + width + " x " + height + ", " + refreshRate + " fps, "
+ + "density " + density + ", " + xDpi + " x " + yDpi + " dpi, secure " + secure
+ + "}";
+ }
+ }
+
+ public static void unblankDisplay(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeUnblankDisplay(displayToken);
+ }
+
+ public static void blankDisplay(IBinder displayToken) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeBlankDisplay(displayToken);
+ }
+
+ public static boolean getDisplayInfo(IBinder displayToken, SurfaceControl.PhysicalDisplayInfo outInfo) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (outInfo == null) {
+ throw new IllegalArgumentException("outInfo must not be null");
+ }
+ return nativeGetDisplayInfo(displayToken, outInfo);
+ }
+
+ public static void setDisplayProjection(IBinder displayToken,
+ int orientation, Rect layerStackRect, Rect displayRect) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ if (layerStackRect == null) {
+ throw new IllegalArgumentException("layerStackRect must not be null");
+ }
+ if (displayRect == null) {
+ throw new IllegalArgumentException("displayRect must not be null");
+ }
+ nativeSetDisplayProjection(displayToken, orientation,
+ layerStackRect.left, layerStackRect.top, layerStackRect.right, layerStackRect.bottom,
+ displayRect.left, displayRect.top, displayRect.right, displayRect.bottom);
+ }
+
+ public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ nativeSetDisplayLayerStack(displayToken, layerStack);
+ }
+
+ public static void setDisplaySurface(IBinder displayToken, Surface surface) {
+ if (displayToken == null) {
+ throw new IllegalArgumentException("displayToken must not be null");
+ }
+ int nativeSurface = surface != null ? surface.mNativeObject : 0;
+ nativeSetDisplaySurface(displayToken, nativeSurface);
+ }
+
+ public static IBinder createDisplay(String name, boolean secure) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null");
+ }
+ return nativeCreateDisplay(name, secure);
+ }
+
+ public static IBinder getBuiltInDisplay(int builtInDisplayId) {
+ return nativeGetBuiltInDisplay(builtInDisplayId);
+ }
+
+
+ /**
+ * Copy the current screen contents into a bitmap and return it.
+ *
+ * @param width The desired width of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param height The desired height of the returned bitmap; the raw
+ * screen will be scaled down to this size.
+ * @param minLayer The lowest (bottom-most Z order) surface layer to
+ * include in the screenshot.
+ * @param maxLayer The highest (top-most Z order) surface layer to
+ * include in the screenshot.
+ * @return Returns a Bitmap containing the screen contents, or null
+ * if an error occurs.
+ *
+ */
+ public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false);
+ }
+
+ /**
+ * Like {@link SurfaceControl#screenshot(int, int, int, int)} but includes all
+ * Surfaces in the screenshot.
+ *
+ */
+ public static Bitmap screenshot(int width, int height) {
+ // TODO: should take the display as a parameter
+ IBinder displayToken = SurfaceControl.getBuiltInDisplay(SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
+ return nativeScreenshot(displayToken, width, height, 0, 0, true);
+ }
+
+ private static void checkHeadless() {
+ if (HEADLESS) {
+ throw new UnsupportedOperationException("Device is headless");
+ }
+ }
+}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 9008521..5d0f523 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -103,6 +103,7 @@ public class SurfaceView extends View {
MyWindow mWindow;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
+ final Rect mOverscanInsets = new Rect();
final Rect mContentInsets = new Rect();
final Configuration mConfiguration = new Configuration();
@@ -507,7 +508,7 @@ public class SurfaceView extends View {
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE,
WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
- mWinFrame, mContentInsets,
+ mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mConfiguration, mNewSurface);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportDrawNeeded = true;
@@ -642,7 +643,7 @@ public class SurfaceView extends View {
}
@Override
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
SurfaceView surfaceView = mSurfaceView.get();
if (surfaceView != null) {
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 82b3963..eb81f72 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -16,10 +16,7 @@
package android.view;
-import android.util.Poolable;
-import android.util.Pool;
-import android.util.Pools;
-import android.util.PoolableManager;
+import android.util.Pools.SynchronizedPool;
/**
* Helper for tracking the velocity of touch events, for implementing
@@ -31,30 +28,15 @@ import android.util.PoolableManager;
* {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
* and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
*/
-public final class VelocityTracker implements Poolable<VelocityTracker> {
- private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<VelocityTracker>() {
- public VelocityTracker newInstance() {
- return new VelocityTracker(null);
- }
-
- public void onAcquired(VelocityTracker element) {
- // Intentionally empty
- }
-
- public void onReleased(VelocityTracker element) {
- element.clear();
- }
- }, 2));
+public final class VelocityTracker {
+ private static final SynchronizedPool<VelocityTracker> sPool =
+ new SynchronizedPool<VelocityTracker>(2);
private static final int ACTIVE_POINTER_ID = -1;
private int mPtr;
private final String mStrategy;
- private VelocityTracker mNext;
- private boolean mIsPooled;
-
private static native int nativeInitialize(String strategy);
private static native void nativeDispose(int ptr);
private static native void nativeClear(int ptr);
@@ -73,7 +55,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
* @return Returns a new VelocityTracker.
*/
static public VelocityTracker obtain() {
- return sPool.acquire();
+ VelocityTracker instance = sPool.acquire();
+ return (instance != null) ? instance : new VelocityTracker(null);
}
/**
@@ -98,38 +81,11 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
*/
public void recycle() {
if (mStrategy == null) {
+ clear();
sPool.release(this);
}
}
- /**
- * @hide
- */
- public void setNextPoolable(VelocityTracker element) {
- mNext = element;
- }
-
- /**
- * @hide
- */
- public VelocityTracker getNextPoolable() {
- return mNext;
- }
-
- /**
- * @hide
- */
- public boolean isPooled() {
- return mIsPooled;
- }
-
- /**
- * @hide
- */
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
-
private VelocityTracker(String strategy) {
mPtr = nativeInitialize(strategy);
mStrategy = strategy;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0d2141f..ab8f934 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,6 +40,7 @@ import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -52,10 +53,7 @@ import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
import android.util.Property;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -691,6 +689,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public static final int NO_ID = -1;
+ private static boolean sUseBrokenMakeMeasureSpec = false;
+
/**
* This view does not want keystrokes. Use with TAKES_FOCUS_MASK when
* calling setFlags.
@@ -1561,9 +1561,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
int mAccessibilityViewId = NO_ID;
- /**
- * @hide
- */
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
/**
@@ -1870,6 +1867,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
/**
+ * Default horizontal layout direction.
+ * @hide
+ */
+ static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR;
+
+ /**
* Indicates that the view is tracking some sort of transient state
* that the app should not need to be aware of, but that the framework
* should take special care to preserve.
@@ -1918,6 +1921,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT;
/**
+ * Default resolved text direction
+ * @hide
+ */
+ static final int TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_FIRST_STRONG;
+
+ /**
* Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED)
* @hide
*/
@@ -1969,7 +1978,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
static final int PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT =
- TEXT_DIRECTION_FIRST_STRONG << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
+ TEXT_DIRECTION_RESOLVED_DEFAULT << PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
/*
* Default text alignment. The text alignment of this View is inherited from its parent.
@@ -2028,6 +2037,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static final int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
/**
+ * Default resolved text alignment
+ * @hide
+ */
+ static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = TEXT_ALIGNMENT_GRAVITY;
+
+ /**
* Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED)
* @hide
*/
@@ -2077,7 +2092,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Indicates whether if the view text alignment has been resolved to gravity
*/
private static final int PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT =
- TEXT_ALIGNMENT_GRAVITY << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ TEXT_ALIGNMENT_RESOLVED_DEFAULT << PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
// Accessiblity constants for mPrivateFlags2
@@ -2515,8 +2530,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* The undefined cursor position.
+ *
+ * @hide
*/
- private static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
+ public static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
/**
* Indicates that the screen has changed state and is now off.
@@ -3237,6 +3254,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
+
+ if (!sUseBrokenMakeMeasureSpec && context.getApplicationInfo().targetSdkVersion <=
+ Build.VERSION_CODES.JELLY_BEAN_MR1 ) {
+ // Older apps may need this compatibility hack for measurement.
+ sUseBrokenMakeMeasureSpec = true;
+ }
}
/**
@@ -4370,10 +4393,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
mPrivateFlags |= PFLAG_FOCUSED;
+ View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
+
if (mParent != null) {
mParent.requestChildFocus(this, this);
}
+ if (mAttachInfo != null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
+ }
+
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
@@ -4480,7 +4509,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
refreshDrawableState();
- ensureInputFocusOnFirstFocusable();
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(this);
+ }
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
notifyAccessibilityStateChanged();
@@ -4488,11 +4519,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- void ensureInputFocusOnFirstFocusable() {
+ void notifyGlobalFocusCleared(View oldFocus) {
+ if (oldFocus != null && mAttachInfo != null) {
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, null);
+ }
+ }
+
+ boolean rootViewRequestFocus() {
View root = getRootView();
if (root != null) {
- root.requestFocus();
+ return root.requestFocus();
}
+ return false;
}
/**
@@ -4838,13 +4876,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
event.setEnabled(isEnabled());
event.setContentDescription(mContentDescription);
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
- ArrayList<View> focusablesTempList = mAttachInfo.mTempArrayList;
- getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
- FOCUSABLES_ALL);
- event.setItemCount(focusablesTempList.size());
- event.setCurrentItemIndex(focusablesTempList.indexOf(this));
- focusablesTempList.clear();
+ switch (event.getEventType()) {
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
+ ArrayList<View> focusablesTempList = (mAttachInfo != null)
+ ? mAttachInfo.mTempArrayList : new ArrayList<View>();
+ getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD, FOCUSABLES_ALL);
+ event.setItemCount(focusablesTempList.size());
+ event.setCurrentItemIndex(focusablesTempList.indexOf(this));
+ if (mAttachInfo != null) {
+ focusablesTempList.clear();
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ event.setFromIndex(getAccessibilitySelectionStart());
+ event.setToIndex(getAccessibilitySelectionEnd());
+ event.setItemCount(text.length());
+ }
+ } break;
}
}
@@ -4986,6 +5036,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (label != null) {
info.setLabeledBy(label);
}
+
+ if ((mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS) != 0) {
+ try {
+ String viewId = getResources().getResourceName(mID);
+ info.setViewIdResourceName(viewId);
+ } catch (Resources.NotFoundException nfe) {
+ /* ignore */
+ }
+ }
}
if (mLabelForId != View.NO_ID) {
@@ -5041,7 +5101,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
}
- if (mContentDescription != null && mContentDescription.length() > 0) {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text != null && text.length() > 0) {
+ info.setTextSelection(getAccessibilitySelectionStart(), getAccessibilitySelectionEnd());
+
+ info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
@@ -5615,20 +5679,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
mUserPaddingStart = UNDEFINED_PADDING;
mUserPaddingEnd = UNDEFINED_PADDING;
- if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
- || mAttachInfo == null
- || (mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
- internalSetPadding(insets.left, insets.top, insets.right, insets.bottom);
- return true;
- } else {
- internalSetPadding(0, 0, 0, 0);
- return false;
+ Rect localInsets = sThreadLocal.get();
+ if (localInsets == null) {
+ localInsets = new Rect();
+ sThreadLocal.set(localInsets);
}
+ boolean res = computeFitSystemWindows(insets, localInsets);
+ internalSetPadding(localInsets.left, localInsets.top,
+ localInsets.right, localInsets.bottom);
+ return res;
}
return false;
}
/**
+ * @hide Compute the insets that should be consumed by this view and the ones
+ * that should propagate to those under it.
+ */
+ protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
+ if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
+ || mAttachInfo == null
+ || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
+ && !mAttachInfo.mOverscanRequested)) {
+ outLocalInsets.set(inoutInsets);
+ inoutInsets.set(0, 0, 0, 0);
+ return true;
+ } else {
+ // The application wants to take care of fitting system window for
+ // the content... however we still need to take care of any overscan here.
+ final Rect overscan = mAttachInfo.mOverscanInsets;
+ outLocalInsets.set(overscan);
+ inoutInsets.left -= overscan.left;
+ inoutInsets.top -= overscan.top;
+ inoutInsets.right -= overscan.right;
+ inoutInsets.bottom -= overscan.bottom;
+ return false;
+ }
+ }
+
+ /**
* Sets whether or not this view should account for system screen decorations
* such as the status bar and inset its content; that is, controlling whether
* the default implementation of {@link #fitSystemWindows(Rect)} will be
@@ -5923,7 +6012,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < JELLY_BEAN_MR1) {
mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
- return LAYOUT_DIRECTION_LTR;
+ return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
}
return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
@@ -6817,7 +6906,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public boolean includeForAccessibility() {
if (mAttachInfo != null) {
- return mAttachInfo.mIncludeNotImportantViews || isImportantForAccessibility();
+ return (mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0
+ || isImportantForAccessibility();
}
return false;
}
@@ -6966,21 +7057,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (arguments != null) {
final int granularity = arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
- return nextAtGranularity(granularity);
+ final boolean extendSelection = arguments.getBoolean(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+ return nextAtGranularity(granularity, extendSelection);
}
} break;
case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
if (arguments != null) {
final int granularity = arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
- return previousAtGranularity(granularity);
+ final boolean extendSelection = arguments.getBoolean(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
+ return previousAtGranularity(granularity, extendSelection);
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text == null) {
+ return false;
+ }
+ final int start = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
+ final int end = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
+ // Only cursor position can be specified (selection length == 0)
+ if ((getAccessibilitySelectionStart() != start
+ || getAccessibilitySelectionEnd() != end)
+ && (start == end)) {
+ setAccessibilitySelection(start, end);
+ notifyAccessibilityStateChanged();
+ return true;
}
} break;
}
return false;
}
- private boolean nextAtGranularity(int granularity) {
+ private boolean nextAtGranularity(int granularity, boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility();
if (text == null || text.length() == 0) {
return false;
@@ -6989,21 +7102,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (iterator == null) {
return false;
}
- final int current = getAccessibilityCursorPosition();
+ int current = getAccessibilitySelectionEnd();
+ if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+ current = 0;
+ }
final int[] range = iterator.following(current);
if (range == null) {
return false;
}
final int start = range[0];
final int end = range[1];
- setAccessibilityCursorPosition(end);
+ if (extendSelection && isAccessibilitySelectionExtendable()) {
+ int selectionStart = getAccessibilitySelectionStart();
+ if (selectionStart == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+ selectionStart = start;
+ }
+ setAccessibilitySelection(selectionStart, end);
+ } else {
+ setAccessibilitySelection(end, end);
+ }
sendViewTextTraversedAtGranularityEvent(
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
granularity, start, end);
return true;
}
- private boolean previousAtGranularity(int granularity) {
+ private boolean previousAtGranularity(int granularity, boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility();
if (text == null || text.length() == 0) {
return false;
@@ -7012,15 +7136,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (iterator == null) {
return false;
}
- int current = getAccessibilityCursorPosition();
+ int current = getAccessibilitySelectionStart();
if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
current = text.length();
- setAccessibilityCursorPosition(current);
- } else if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
- // When traversing by character we always put the cursor after the character
- // to ease edit and have to compensate before asking the for previous segment.
- current--;
- setAccessibilityCursorPosition(current);
}
final int[] range = iterator.preceding(current);
if (range == null) {
@@ -7028,11 +7146,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
final int start = range[0];
final int end = range[1];
- // Always put the cursor after the character to ease edit.
- if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
- setAccessibilityCursorPosition(end);
+ if (extendSelection && isAccessibilitySelectionExtendable()) {
+ int selectionEnd = getAccessibilitySelectionEnd();
+ if (selectionEnd == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
+ selectionEnd = end;
+ }
+ setAccessibilitySelection(start, selectionEnd);
} else {
- setAccessibilityCursorPosition(start);
+ setAccessibilitySelection(start, start);
}
sendViewTextTraversedAtGranularityEvent(
AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
@@ -7052,17 +7173,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Gets whether accessibility selection can be extended.
+ *
+ * @return If selection is extensible.
+ *
* @hide
*/
- public int getAccessibilityCursorPosition() {
+ public boolean isAccessibilitySelectionExtendable() {
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ public int getAccessibilitySelectionStart() {
return mAccessibilityCursorPosition;
}
/**
* @hide
*/
- public void setAccessibilityCursorPosition(int position) {
- mAccessibilityCursorPosition = position;
+ public int getAccessibilitySelectionEnd() {
+ return getAccessibilitySelectionStart();
+ }
+
+ /**
+ * @hide
+ */
+ public void setAccessibilitySelection(int start, int end) {
+ if (start == end && end == mAccessibilityCursorPosition) {
+ return;
+ }
+ if (start >= 0 && start == end && end <= getIterableTextForAccessibility().length()) {
+ mAccessibilityCursorPosition = start;
+ } else {
+ mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
+ }
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
}
private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
@@ -9806,8 +9953,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
outRect.set(mLeft, mTop, mRight, mBottom);
} else {
final RectF tmpRect = mAttachInfo.mTmpTransformRect;
- tmpRect.set(-info.mPivotX, -info.mPivotY,
- getWidth() - info.mPivotX, getHeight() - info.mPivotY);
+ tmpRect.set(0, 0, getWidth(), getHeight());
info.mMatrix.mapRect(tmpRect);
outRect.set((int) tmpRect.left + mLeft, (int) tmpRect.top + mTop,
(int) tmpRect.right + mLeft, (int) tmpRect.bottom + mTop);
@@ -9928,7 +10074,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mTop += offset;
mBottom += offset;
if (mDisplayList != null) {
- mDisplayList.offsetTopBottom(offset);
+ mDisplayList.offsetTopAndBottom(offset);
invalidateViewProperty(false, false);
} else {
if (!matrixIsIdentity) {
@@ -9976,7 +10122,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mLeft += offset;
mRight += offset;
if (mDisplayList != null) {
- mDisplayList.offsetLeftRight(offset);
+ mDisplayList.offsetLeftAndRight(offset);
invalidateViewProperty(false, false);
} else {
if (!matrixIsIdentity) {
@@ -10749,7 +10895,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
+ final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
info.target = this;
info.left = left;
info.top = top;
@@ -10798,7 +10944,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
- final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.acquire();
+ final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain();
info.target = this;
info.left = left;
info.top = top;
@@ -11564,8 +11710,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
imm.focusIn(this);
}
- if (mAttachInfo != null && mDisplayList != null) {
- mAttachInfo.mViewRootImpl.dequeueDisplayList(mDisplayList);
+ if (mDisplayList != null) {
+ mDisplayList.clearDirty();
}
}
@@ -11691,11 +11837,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// later to get the correct resolved value
if (!canResolveLayoutDirection()) return false;
- View parent = ((View) mParent);
// Parent has not yet resolved, LTR is still the default
- if (!parent.isLayoutDirectionResolved()) return false;
+ if (!mParent.isLayoutDirectionResolved()) return false;
- if (parent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
+ if (mParent.getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL;
}
break;
@@ -11728,8 +11873,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public boolean canResolveLayoutDirection() {
switch (getRawLayoutDirection()) {
case LAYOUT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof ViewGroup) &&
- ((ViewGroup) mParent).canResolveLayoutDirection();
+ return (mParent != null) && mParent.canResolveLayoutDirection();
default:
return true;
}
@@ -11757,8 +11901,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @return true if layout direction has been resolved.
+ * @hide
*/
- private boolean isLayoutDirectionResolved() {
+ public boolean isLayoutDirectionResolved() {
return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED;
}
@@ -11845,6 +11990,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mAttachInfo != null) {
if (mDisplayList != null) {
+ mDisplayList.markDirty();
mAttachInfo.mViewRootImpl.enqueueDisplayList(mDisplayList);
}
mAttachInfo.mViewRootImpl.cancelInvalidate(this);
@@ -12548,8 +12694,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * @return The HardwareRenderer associated with that view or null if hardware rendering
- * is not supported or this this has not been attached to a window.
+ * @return The {@link HardwareRenderer} associated with that view or null if
+ * hardware rendering is not supported or this view is not attached
+ * to a window.
*
* @hide
*/
@@ -12604,15 +12751,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
boolean caching = false;
- final HardwareCanvas canvas = displayList.start();
int width = mRight - mLeft;
int height = mBottom - mTop;
+ int layerType = getLayerType();
+
+ final HardwareCanvas canvas = displayList.start(width, height);
try {
- canvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- canvas.onPreDraw(null);
- int layerType = getLayerType();
if (!isLayer && layerType != LAYER_TYPE_NONE) {
if (layerType == LAYER_TYPE_HARDWARE) {
final HardwareLayer layer = getHardwareLayer();
@@ -12650,8 +12795,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
} finally {
- canvas.onPostDraw();
-
displayList.end();
displayList.setCaching(caching);
if (isLayer) {
@@ -12696,7 +12839,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private void clearDisplayList() {
if (mDisplayList != null) {
- mDisplayList.invalidate();
mDisplayList.clear();
}
}
@@ -13272,7 +13414,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
alpha = transform.getAlpha();
}
if ((transformType & Transformation.TYPE_MATRIX) != 0) {
- displayList.setStaticMatrix(transform.getMatrix());
+ displayList.setMatrix(transform.getMatrix());
}
}
}
@@ -13347,8 +13489,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
transformToApply = parent.mChildTransformation;
} else {
- if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) == PFLAG3_VIEW_IS_ANIMATING_TRANSFORM &&
- mDisplayList != null) {
+ if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) ==
+ PFLAG3_VIEW_IS_ANIMATING_TRANSFORM && mDisplayList != null) {
// No longer animating: clear out old animation matrix
mDisplayList.setAnimationMatrix(null);
mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
@@ -13545,7 +13687,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN &&
- !useDisplayListProperties) {
+ !useDisplayListProperties && layerType == LAYER_TYPE_NONE) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
} else {
@@ -13977,6 +14119,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Return true if o is a ViewGroup that is laying out using optical bounds.
+ * @hide
+ */
+ public static boolean isLayoutModeOptical(Object o) {
+ return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
+ }
+
+ private boolean setOpticalFrame(int left, int top, int right, int bottom) {
+ Insets parentInsets = mParent instanceof View ?
+ ((View) mParent).getOpticalInsets() : Insets.NONE;
+ Insets childInsets = getOpticalInsets();
+ return setFrame(
+ left + parentInsets.left - childInsets.left,
+ top + parentInsets.top - childInsets.top,
+ right + parentInsets.left + childInsets.right,
+ bottom + parentInsets.top + childInsets.bottom);
+ }
+
+ /**
* Assign a size and position to a view and all of its
* descendants
*
@@ -14002,7 +14163,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
- boolean changed = setFrame(l, t, r, b);
+ boolean changed = isLayoutModeOptical(mParent) ?
+ setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
@@ -14444,6 +14606,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (mBackground instanceof ColorDrawable) {
((ColorDrawable) mBackground.mutate()).setColor(color);
computeOpaqueFlags();
+ mBackgroundResource = 0;
} else {
setBackground(new ColorDrawable(color));
}
@@ -14818,6 +14981,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (mUserPaddingStart != UNDEFINED_PADDING || mUserPaddingEnd != UNDEFINED_PADDING);
}
+ Insets computeOpticalInsets() {
+ return (mBackground == null) ? Insets.NONE : mBackground.getOpticalInsets();
+ }
+
/**
* @hide
*/
@@ -14841,19 +15008,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public Insets getOpticalInsets() {
if (mLayoutInsets == null) {
- mLayoutInsets = (mBackground == null) ? Insets.NONE : mBackground.getLayoutInsets();
+ mLayoutInsets = computeOpticalInsets();
}
return mLayoutInsets;
}
/**
- * @hide
- */
- public void setLayoutInsets(Insets layoutInsets) {
- mLayoutInsets = layoutInsets;
- }
-
- /**
* Changes the selection state of this view. A view can be selected or not.
* Note that selection is not the same as focus. Views are typically
* selected in the context of an AdapterView like ListView or GridView;
@@ -15460,17 +15620,50 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Returns whether the view hierarchy is currently undergoing a layout pass. This
+ * information is useful to avoid situations such as calling {@link #requestLayout()} during
+ * a layout pass.
+ *
+ * @return whether the view hierarchy is currently undergoing a layout pass
+ */
+ public boolean isInLayout() {
+ ViewRootImpl viewRoot = getViewRootImpl();
+ return (viewRoot != null && viewRoot.isInLayout());
+ }
+
+ /**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
- * tree.
+ * tree. This should not be called while the view hierarchy is currently in a layout
+ * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
+ * end of the current layout pass (and then layout will run again) or after the current
+ * frame is drawn and the next layout occurs.
+ *
+ * <p>Subclasses which override this method should call the superclass method to
+ * handle possible request-during-layout errors correctly.</p>
*/
public void requestLayout() {
+ if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
+ // Only trigger request-during-layout logic if this is the view requesting it,
+ // not the views in its parent hierarchy
+ ViewRootImpl viewRoot = getViewRootImpl();
+ if (viewRoot != null && viewRoot.isInLayout()) {
+ if (!viewRoot.requestLayoutDuringLayout(this)) {
+ return;
+ }
+ }
+ mAttachInfo.mViewRequestingLayout = this;
+ }
+
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
+ if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
+ mAttachInfo.mViewRequestingLayout = null;
+ }
}
/**
@@ -15504,6 +15697,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #onMeasure(int, int)
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
+ boolean optical = isLayoutModeOptical(this);
+ if (optical != isLayoutModeOptical(mParent)) {
+ Insets insets = getOpticalInsets();
+ int oWidth = insets.left + insets.right;
+ int oHeight = insets.top + insets.bottom;
+ widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
+ heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
+ }
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
@@ -15595,6 +15796,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@link #MEASURED_STATE_TOO_SMALL}.
*/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
+ boolean optical = isLayoutModeOptical(this);
+ if (optical != isLayoutModeOptical(mParent)) {
+ Insets insets = getOpticalInsets();
+ int opticalWidth = insets.left + insets.right;
+ int opticalHeight = insets.top + insets.bottom;
+
+ measuredWidth += optical ? opticalWidth : -opticalWidth;
+ measuredHeight += optical ? opticalHeight : -opticalHeight;
+ }
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
@@ -16722,16 +16932,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return false;
}
- View parent = ((View) mParent);
// Parent has not yet resolved, so we still return the default
- if (!parent.isTextDirectionResolved()) {
+ if (!mParent.isTextDirectionResolved()) {
mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
// Resolution will need to happen again later
return false;
}
// Set current resolved direction to the same value as the parent's one
- final int parentResolvedDirection = parent.getTextDirection();
+ final int parentResolvedDirection = mParent.getTextDirection();
switch (parentResolvedDirection) {
case TEXT_DIRECTION_FIRST_STRONG:
case TEXT_DIRECTION_ANY_RTL:
@@ -16772,12 +16981,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Check if text direction resolution can be done.
*
* @return true if text direction resolution can be done otherwise return false.
+ *
+ * @hide
*/
- private boolean canResolveTextDirection() {
+ public boolean canResolveTextDirection() {
switch (getRawTextDirection()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof View) &&
- ((View) mParent).canResolveTextDirection();
+ return (mParent != null) && mParent.canResolveTextDirection();
default:
return true;
}
@@ -16807,8 +17017,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @return true if text direction is resolved.
+ *
+ * @hide
*/
- private boolean isTextDirectionResolved() {
+ public boolean isTextDirectionResolved() {
return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED;
}
@@ -16931,16 +17143,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// Resolution will need to happen again later
return false;
}
- View parent = (View) mParent;
// Parent has not yet resolved, so we still return the default
- if (!parent.isTextAlignmentResolved()) {
+ if (!mParent.isTextAlignmentResolved()) {
mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
// Resolution will need to happen again later
return false;
}
- final int parentResolvedTextAlignment = parent.getTextAlignment();
+ final int parentResolvedTextAlignment = mParent.getTextAlignment();
switch (parentResolvedTextAlignment) {
case TEXT_ALIGNMENT_GRAVITY:
case TEXT_ALIGNMENT_TEXT_START:
@@ -16985,12 +17196,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Check if text alignment resolution can be done.
*
* @return true if text alignment resolution can be done otherwise return false.
+ *
+ * @hide
*/
- private boolean canResolveTextAlignment() {
+ public boolean canResolveTextAlignment() {
switch (getRawTextAlignment()) {
case TEXT_DIRECTION_INHERIT:
- return (mParent != null) && (mParent instanceof View) &&
- ((View) mParent).canResolveTextAlignment();
+ return (mParent != null) && mParent.canResolveTextAlignment();
default:
return true;
}
@@ -17020,8 +17232,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @return true if text alignment is resolved.
+ *
+ * @hide
*/
- private boolean isTextAlignmentResolved() {
+ public boolean isTextAlignmentResolved() {
return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED;
}
@@ -17266,12 +17480,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
* </ul>
*
+ * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
+ * implementation was such that the order of arguments did not matter
+ * and overflow in either value could impact the resulting MeasureSpec.
+ * {@link android.widget.RelativeLayout} was affected by this bug.
+ * Apps targeting API levels greater than 17 will get the fixed, more strict
+ * behavior.</p>
+ *
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(int size, int mode) {
- return size + mode;
+ if (sUseBrokenMakeMeasureSpec) {
+ return size + mode;
+ } else {
+ return (size & ~MODE_MASK) | (mode & MODE_MASK);
+ }
}
/**
@@ -17296,6 +17521,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return (measureSpec & ~MODE_MASK);
}
+ static int adjust(int measureSpec, int delta) {
+ return makeMeasureSpec(getSize(measureSpec + delta), getMode(measureSpec));
+ }
+
/**
* Returns a String representation of the specified measure
* specification.
@@ -17633,25 +17862,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* POOL_LIMIT objects that get reused. This reduces memory allocations
* whenever possible.
*/
- static class InvalidateInfo implements Poolable<InvalidateInfo> {
+ static class InvalidateInfo {
private static final int POOL_LIMIT = 10;
- private static final Pool<InvalidateInfo> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<InvalidateInfo>() {
- public InvalidateInfo newInstance() {
- return new InvalidateInfo();
- }
-
- public void onAcquired(InvalidateInfo element) {
- }
-
- public void onReleased(InvalidateInfo element) {
- element.target = null;
- }
- }, POOL_LIMIT)
- );
- private InvalidateInfo mNext;
- private boolean mIsPooled;
+ private static final SynchronizedPool<InvalidateInfo> sPool =
+ new SynchronizedPool<InvalidateInfo>(POOL_LIMIT);
View target;
@@ -17660,29 +17875,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int right;
int bottom;
- public void setNextPoolable(InvalidateInfo element) {
- mNext = element;
+ public static InvalidateInfo obtain() {
+ InvalidateInfo instance = sPool.acquire();
+ return (instance != null) ? instance : new InvalidateInfo();
}
- public InvalidateInfo getNextPoolable() {
- return mNext;
- }
-
- static InvalidateInfo acquire() {
- return sPool.acquire();
- }
-
- void release() {
+ public void recycle() {
+ target = null;
sPool.release(this);
}
-
- public boolean isPooled() {
- return mIsPooled;
- }
-
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
}
final IWindowSession mSession;
@@ -17743,6 +17944,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* For windows that are full-screen but using insets to layout inside
+ * of the screen areas, these are the current insets to appear inside
+ * the overscan area of the display.
+ */
+ final Rect mOverscanInsets = new Rect();
+
+ /**
+ * For windows that are full-screen but using insets to layout inside
* of the screen decorations, these are the current insets for the
* content of the window.
*/
@@ -17844,6 +18052,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean mHasSystemUiListeners;
/**
+ * Set if the window has requested to extend into the overscan region
+ * via WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN.
+ */
+ boolean mOverscanRequested;
+
+ /**
* Set if the visibility of any views has changed.
*/
boolean mViewVisibilityChanged;
@@ -17926,10 +18140,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int mAccessibilityWindowId = View.NO_ID;
/**
- * Whether to ingore not exposed for accessibility Views when
- * reporting the view tree to accessibility services.
+ * Flags related to accessibility processing.
+ *
+ * @see AccessibilityNodeInfo#FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+ * @see AccessibilityNodeInfo#FLAG_REPORT_VIEW_IDS
*/
- boolean mIncludeNotImportantViews;
+ int mAccessibilityFetchFlags;
/**
* The drawable for highlighting accessibility focus.
@@ -17947,6 +18163,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final Point mPoint = new Point();
/**
+ * Used to track which View originated a requestLayout() call, used when
+ * requestLayout() is called during layout.
+ */
+ View mViewRequestingLayout;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 023e58f..987ff78 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -45,6 +45,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -406,7 +407,7 @@ public class ViewDebug {
view = view.getRootView();
if (REMOTE_COMMAND_DUMP.equalsIgnoreCase(command)) {
- dump(view, clientStream);
+ dump(view, false, true, clientStream);
} else if (REMOTE_COMMAND_CAPTURE_LAYERS.equalsIgnoreCase(command)) {
captureLayers(view, new DataOutputStream(clientStream));
} else {
@@ -425,7 +426,8 @@ public class ViewDebug {
}
}
- private static View findView(View root, String parameter) {
+ /** @hide */
+ public static View findView(View root, String parameter) {
// Look by type/hashcode
if (parameter.indexOf('@') != -1) {
final String[] ids = parameter.split("@");
@@ -488,7 +490,8 @@ public class ViewDebug {
}
}
- private static void profileViewAndChildren(final View view, BufferedWriter out)
+ /** @hide */
+ public static void profileViewAndChildren(final View view, BufferedWriter out)
throws IOException {
profileViewAndChildren(view, out, true);
}
@@ -623,7 +626,8 @@ public class ViewDebug {
return duration[0];
}
- private static void captureLayers(View root, final DataOutputStream clientStream)
+ /** @hide */
+ public static void captureLayers(View root, final DataOutputStream clientStream)
throws IOException {
try {
@@ -695,10 +699,21 @@ public class ViewDebug {
view.getViewRootImpl().outputDisplayList(view);
}
+ /** @hide */
+ public static void outputDisplayList(View root, View target) {
+ root.getViewRootImpl().outputDisplayList(target);
+ }
+
private static void capture(View root, final OutputStream clientStream, String parameter)
throws IOException {
final View captureView = findView(root, parameter);
+ capture(root, clientStream, captureView);
+ }
+
+ /** @hide */
+ public static void capture(View root, final OutputStream clientStream, View captureView)
+ throws IOException {
Bitmap b = performViewCapture(captureView, false);
if (b == null) {
@@ -752,14 +767,20 @@ public class ViewDebug {
return null;
}
- private static void dump(View root, OutputStream clientStream) throws IOException {
+ /**
+ * Dumps the view hierarchy starting from the given view.
+ * @hide
+ */
+ public static void dump(View root, boolean skipChildren, boolean includeProperties,
+ OutputStream clientStream) throws IOException {
BufferedWriter out = null;
try {
out = new BufferedWriter(new OutputStreamWriter(clientStream, "utf-8"), 32 * 1024);
View view = root.getRootView();
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
- dumpViewHierarchyWithProperties(group.getContext(), group, out, 0);
+ dumpViewHierarchy(group.getContext(), group, out, 0,
+ skipChildren, includeProperties);
}
out.write("DONE.");
out.newLine();
@@ -804,9 +825,13 @@ public class ViewDebug {
return view.getClass().getName().equals(className) && view.hashCode() == hashCode;
}
- private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group,
- BufferedWriter out, int level) {
- if (!dumpViewWithProperties(context, group, out, level)) {
+ private static void dumpViewHierarchy(Context context, ViewGroup group,
+ BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+ if (!dumpView(context, group, out, level, includeProperties)) {
+ return;
+ }
+
+ if (skipChildren) {
return;
}
@@ -814,9 +839,10 @@ public class ViewDebug {
for (int i = 0; i < count; i++) {
final View view = group.getChildAt(i);
if (view instanceof ViewGroup) {
- dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1);
+ dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
+ includeProperties);
} else {
- dumpViewWithProperties(context, view, out, level + 1);
+ dumpView(context, view, out, level + 1, includeProperties);
}
}
if (group instanceof HierarchyHandler) {
@@ -824,8 +850,8 @@ public class ViewDebug {
}
}
- private static boolean dumpViewWithProperties(Context context, View view,
- BufferedWriter out, int level) {
+ private static boolean dumpView(Context context, View view,
+ BufferedWriter out, int level, boolean includeProperties) {
try {
for (int i = 0; i < level; i++) {
@@ -835,7 +861,9 @@ public class ViewDebug {
out.write('@');
out.write(Integer.toHexString(view.hashCode()));
out.write(' ');
- dumpViewProperties(context, view, out);
+ if (includeProperties) {
+ dumpViewProperties(context, view, out);
+ }
out.newLine();
} catch (IOException e) {
Log.w("View", "Error while dumping hierarchy tree");
@@ -1347,4 +1375,68 @@ public class ViewDebug {
sb.append(capturedViewExportMethods(view, klass, ""));
Log.d(tag, sb.toString());
}
+
+ /**
+ * Invoke a particular method on given view.
+ * The given method is always invoked on the UI thread. The caller thread will stall until the
+ * method invocation is complete. Returns an object equal to the result of the method
+ * invocation, null if the method is declared to return void
+ * @throws Exception if the method invocation caused any exception
+ * @hide
+ */
+ public static Object invokeViewMethod(final View view, final Method method,
+ final Object[] args) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final AtomicReference<Object> result = new AtomicReference<Object>();
+ final AtomicReference<Throwable> exception = new AtomicReference<Throwable>();
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ result.set(method.invoke(view, args));
+ } catch (InvocationTargetException e) {
+ exception.set(e.getCause());
+ } catch (Exception e) {
+ exception.set(e);
+ }
+
+ latch.countDown();
+ }
+ });
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (exception.get() != null) {
+ throw new RuntimeException(exception.get());
+ }
+
+ return result.get();
+ }
+
+ /**
+ * @hide
+ */
+ public static void setLayoutParameter(final View view, final String param, final int value)
+ throws NoSuchFieldException, IllegalAccessException {
+ final ViewGroup.LayoutParams p = view.getLayoutParams();
+ final Field f = p.getClass().getField(param);
+ if (f.getType() != int.class) {
+ throw new RuntimeException("Only integer layout parameters can be set. Field "
+ + param + " is of type " + f.getType().getSimpleName());
+ }
+
+ f.set(p, Integer.valueOf(value));
+
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ view.setLayoutParams(p);
+ }
+ });
+ }
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index dbbcde6..5105fda 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -35,6 +35,7 @@ import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -83,6 +84,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final String TAG = "ViewGroup";
private static final boolean DBG = false;
+ /** @hide */
+ public static boolean DEBUG_DRAW = false;
/**
* Views which have been hidden or removed which need to be animated on
@@ -180,10 +183,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
})
protected int mGroupFlags;
- /*
- * The layout mode: either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}
+ /**
+ * Either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*/
- private int mLayoutMode = CLIP_BOUNDS;
+ private int mLayoutMode = DEFAULT_LAYOUT_MODE;
/**
* NOTE: If you change the flags below make sure to reflect the changes
@@ -356,20 +359,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Clip bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
* {@link #getRight() right} and {@link #getBottom() bottom}.
- *
- * @hide
*/
- public static final int CLIP_BOUNDS = 0;
+ public static final int LAYOUT_MODE_CLIP_BOUNDS = 0;
/**
* This constant is a {@link #setLayoutMode(int) layoutMode}.
* Optical bounds describe where a widget appears to be. They sit inside the clip
* bounds which need to cover a larger area to allow other effects,
* such as shadows and glows, to be drawn.
- *
- * @hide
*/
- public static final int OPTICAL_BOUNDS = 1;
+ public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1;
+
+ /** @hide */
+ public static int DEFAULT_LAYOUT_MODE = LAYOUT_MODE_CLIP_BOUNDS;
/**
* We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
@@ -434,7 +436,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
private boolean debugDraw() {
- return mAttachInfo != null && mAttachInfo.mDebugLayout;
+ return DEBUG_DRAW || mAttachInfo != null && mAttachInfo.mDebugLayout;
}
private void initViewGroup() {
@@ -504,6 +506,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
setLayoutTransition(new LayoutTransition());
}
break;
+ case R.styleable.ViewGroup_layoutMode:
+ setLayoutMode(a.getInt(attr, DEFAULT_LAYOUT_MODE));
+ break;
}
}
@@ -2420,7 +2425,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
- visibility | (child.mViewFlags&VISIBILITY_MASK));
+ visibility | (child.mViewFlags & VISIBILITY_MASK));
}
}
@@ -2682,20 +2687,89 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return b;
}
- private static void drawRect(Canvas canvas, int x1, int y1, int x2, int y2, int color) {
- Paint paint = getDebugPaint();
- paint.setColor(color);
+ /** Return true if this ViewGroup is laying out using optical bounds. */
+ boolean isLayoutModeOptical() {
+ return mLayoutMode == LAYOUT_MODE_OPTICAL_BOUNDS;
+ }
+
+ Insets computeOpticalInsets() {
+ if (isLayoutModeOptical()) {
+ int left = 0;
+ int top = 0;
+ int right = 0;
+ int bottom = 0;
+ for (int i = 0; i < mChildrenCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == VISIBLE) {
+ Insets insets = child.getOpticalInsets();
+ left = Math.max(left, insets.left);
+ top = Math.max(top, insets.top);
+ right = Math.max(right, insets.right);
+ bottom = Math.max(bottom, insets.bottom);
+ }
+ }
+ return Insets.of(left, top, right, bottom);
+ } else {
+ return Insets.NONE;
+ }
+ }
+
+ private static void fillRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+ if (x1 != x2 && y1 != y2) {
+ if (x1 > x2) {
+ int tmp = x1; x1 = x2; x2 = tmp;
+ }
+ if (y1 > y2) {
+ int tmp = y1; y1 = y2; y2 = tmp;
+ }
+ canvas.drawRect(x1, y1, x2, y2, paint);
+ }
+ }
+
+ private static int sign(int x) {
+ return (x >= 0) ? 1 : -1;
+ }
+
+ private static void drawCorner(Canvas c, Paint paint, int x1, int y1, int dx, int dy, int lw) {
+ fillRect(c, paint, x1, y1, x1 + dx, y1 + lw * sign(dy));
+ fillRect(c, paint, x1, y1, x1 + lw * sign(dx), y1 + dy);
+ }
+
+ private int dipsToPixels(int dips) {
+ float scale = getContext().getResources().getDisplayMetrics().density;
+ return (int) (dips * scale + 0.5f);
+ }
+
+ private void drawRectCorners(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint,
+ int lineLength, int lineWidth) {
+ drawCorner(canvas, paint, x1, y1, lineLength, lineLength, lineWidth);
+ drawCorner(canvas, paint, x1, y2, lineLength, -lineLength, lineWidth);
+ drawCorner(canvas, paint, x2, y1, -lineLength, lineLength, lineWidth);
+ drawCorner(canvas, paint, x2, y2, -lineLength, -lineLength, lineWidth);
+ }
+
+ private static void fillDifference(Canvas canvas,
+ int x2, int y2, int x3, int y3,
+ int dx1, int dy1, int dx2, int dy2, Paint paint) {
+ int x1 = x2 - dx1;
+ int y1 = y2 - dy1;
+
+ int x4 = x3 + dx2;
+ int y4 = y3 + dy2;
- canvas.drawLines(getDebugLines(x1, y1, x2, y2), paint);
+ fillRect(canvas, paint, x1, y1, x4, y2);
+ fillRect(canvas, paint, x1, y2, x2, y3);
+ fillRect(canvas, paint, x3, y2, x4, y3);
+ fillRect(canvas, paint, x1, y3, x4, y4);
}
/**
* @hide
*/
- protected void onDebugDrawMargins(Canvas canvas) {
+ protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
- c.getLayoutParams().onDebugDraw(c, canvas);
+ c.getLayoutParams().onDebugDraw(c, canvas, paint);
}
}
@@ -2703,26 +2777,45 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @hide
*/
protected void onDebugDraw(Canvas canvas) {
+ Paint paint = getDebugPaint();
+
// Draw optical bounds
- if (getLayoutMode() == OPTICAL_BOUNDS) {
+ {
+ paint.setColor(Color.RED);
+ paint.setStyle(Paint.Style.STROKE);
+
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
Insets insets = c.getOpticalInsets();
- drawRect(canvas,
- c.getLeft() + insets.left,
- c.getTop() + insets.top,
- c.getRight() - insets.right,
- c.getBottom() - insets.bottom, Color.RED);
+
+ drawRect(canvas, paint,
+ c.getLeft() + insets.left,
+ c.getTop() + insets.top,
+ c.getRight() - insets.right - 1,
+ c.getBottom() - insets.bottom - 1);
}
}
// Draw margins
- onDebugDrawMargins(canvas);
+ {
+ paint.setColor(Color.argb(63, 255, 0, 255));
+ paint.setStyle(Paint.Style.FILL);
- // Draw bounds
- for (int i = 0; i < getChildCount(); i++) {
- View c = getChildAt(i);
- drawRect(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(), Color.BLUE);
+ onDebugDrawMargins(canvas, paint);
+ }
+
+ // Draw clip bounds
+ {
+ paint.setColor(Color.rgb(63, 127, 255));
+ paint.setStyle(Paint.Style.FILL);
+
+ int lineLength = dipsToPixels(8);
+ int lineWidth = dipsToPixels(1);
+ for (int i = 0; i < getChildCount(); i++) {
+ View c = getChildAt(i);
+ drawRectCorners(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(),
+ paint, lineLength, lineWidth);
+ }
}
}
@@ -3604,7 +3697,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
clearChildFocus = true;
}
- view.clearAccessibilityFocus();
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -3620,20 +3715,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
childHasTransientStateChanged(view, false);
}
- onViewRemoved(view);
-
needGlobalAttributesUpdate(false);
removeFromArray(index);
if (clearChildFocus) {
clearChildFocus(view);
- ensureInputFocusOnFirstFocusable();
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(this);
+ }
}
- if (view.isAccessibilityFocused()) {
- view.clearAccessibilityFocus();
- }
+ onViewRemoved(view);
}
/**
@@ -3672,7 +3765,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private void removeViewsInternal(int start, int count) {
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
- View clearChildFocus = null;
+ boolean clearChildFocus = false;
final View[] children = mChildren;
final int end = start + count;
@@ -3686,10 +3779,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (view == focused) {
view.unFocus();
- clearChildFocus = view;
+ clearChildFocus = true;
}
- view.clearAccessibilityFocus();
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -3712,9 +3807,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
removeFromArray(start, count);
- if (clearChildFocus != null) {
- clearChildFocus(clearChildFocus);
- ensureInputFocusOnFirstFocusable();
+ if (clearChildFocus) {
+ clearChildFocus(focused);
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(focused);
+ }
}
}
@@ -3756,7 +3853,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final View focused = mFocused;
final boolean detach = mAttachInfo != null;
- View clearChildFocus = null;
+ boolean clearChildFocus = false;
needGlobalAttributesUpdate(false);
@@ -3769,10 +3866,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (view == focused) {
view.unFocus();
- clearChildFocus = view;
+ clearChildFocus = true;
}
- view.clearAccessibilityFocus();
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
cancelTouchTarget(view);
cancelHoverTarget(view);
@@ -3794,9 +3893,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
children[i] = null;
}
- if (clearChildFocus != null) {
- clearChildFocus(clearChildFocus);
- ensureInputFocusOnFirstFocusable();
+ if (clearChildFocus) {
+ clearChildFocus(focused);
+ if (!rootViewRequestFocus()) {
+ notifyGlobalFocusCleared(focused);
+ }
}
}
@@ -4312,7 +4413,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
v.mTop += offset;
v.mBottom += offset;
if (v.mDisplayList != null) {
- v.mDisplayList.offsetTopBottom(offset);
+ v.mDisplayList.offsetTopAndBottom(offset);
invalidateViewProperty(false, false);
}
}
@@ -4610,13 +4711,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Returns the basis of alignment during layout operations on this view group:
- * either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}.
+ * either {@link #LAYOUT_MODE_CLIP_BOUNDS} or {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
*
* @return the layout mode to use during layout operations
*
* @see #setLayoutMode(int)
- *
- * @hide
*/
public int getLayoutMode() {
return mLayoutMode;
@@ -4624,15 +4723,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
/**
* Sets the basis of alignment during the layout of this view group.
- * Valid values are either {@link #CLIP_BOUNDS} or {@link #OPTICAL_BOUNDS}.
+ * Valid values are either {@link #LAYOUT_MODE_CLIP_BOUNDS} or
+ * {@link #LAYOUT_MODE_OPTICAL_BOUNDS}.
* <p>
- * The default is {@link #CLIP_BOUNDS}.
+ * The default is {@link #LAYOUT_MODE_CLIP_BOUNDS}.
*
* @param layoutMode the layout mode to use during layout operations
*
* @see #getLayoutMode()
- *
- * @hide
*/
public void setLayoutMode(int layoutMode) {
if (mLayoutMode != layoutMode) {
@@ -5650,7 +5748,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*
* @hide
*/
- public void onDebugDraw(View view, Canvas canvas) {
+ public void onDebugDraw(View view, Canvas canvas, Paint paint) {
}
/**
@@ -5998,12 +6096,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @hide
*/
@Override
- public void onDebugDraw(View view, Canvas canvas) {
- drawRect(canvas,
- view.getLeft() - leftMargin,
- view.getTop() - topMargin,
- view.getRight() + rightMargin,
- view.getBottom() + bottomMargin, Color.MAGENTA);
+ public void onDebugDraw(View view, Canvas canvas, Paint paint) {
+ Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE;
+
+ fillDifference(canvas,
+ view.getLeft() + oi.left,
+ view.getTop() + oi.top,
+ view.getRight() - oi.right,
+ view.getBottom() - oi.bottom,
+ leftMargin,
+ topMargin,
+ rightMargin,
+ bottomMargin,
+ paint);
}
}
@@ -6119,50 +6224,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final int MAX_POOL_SIZE = 32;
- private static final Object sPoolLock = new Object();
-
- private static ChildListForAccessibility sPool;
-
- private static int sPoolSize;
-
- private boolean mIsPooled;
-
- private ChildListForAccessibility mNext;
+ private static final SynchronizedPool<ChildListForAccessibility> sPool =
+ new SynchronizedPool<ChildListForAccessibility>(MAX_POOL_SIZE);
private final ArrayList<View> mChildren = new ArrayList<View>();
private final ArrayList<ViewLocationHolder> mHolders = new ArrayList<ViewLocationHolder>();
public static ChildListForAccessibility obtain(ViewGroup parent, boolean sort) {
- ChildListForAccessibility list = null;
- synchronized (sPoolLock) {
- if (sPool != null) {
- list = sPool;
- sPool = list.mNext;
- list.mNext = null;
- list.mIsPooled = false;
- sPoolSize--;
- } else {
- list = new ChildListForAccessibility();
- }
- list.init(parent, sort);
- return list;
+ ChildListForAccessibility list = sPool.acquire();
+ if (list == null) {
+ list = new ChildListForAccessibility();
}
+ list.init(parent, sort);
+ return list;
}
public void recycle() {
- if (mIsPooled) {
- throw new IllegalStateException("Instance already recycled.");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize < MAX_POOL_SIZE) {
- mNext = sPool;
- mIsPooled = true;
- sPool = this;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
public int getChildCount() {
@@ -6216,15 +6296,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final int MAX_POOL_SIZE = 32;
- private static final Object sPoolLock = new Object();
-
- private static ViewLocationHolder sPool;
-
- private static int sPoolSize;
-
- private boolean mIsPooled;
-
- private ViewLocationHolder mNext;
+ private static final SynchronizedPool<ViewLocationHolder> sPool =
+ new SynchronizedPool<ViewLocationHolder>(MAX_POOL_SIZE);
private final Rect mLocation = new Rect();
@@ -6233,35 +6306,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private int mLayoutDirection;
public static ViewLocationHolder obtain(ViewGroup root, View view) {
- ViewLocationHolder holder = null;
- synchronized (sPoolLock) {
- if (sPool != null) {
- holder = sPool;
- sPool = holder.mNext;
- holder.mNext = null;
- holder.mIsPooled = false;
- sPoolSize--;
- } else {
- holder = new ViewLocationHolder();
- }
- holder.init(root, view);
- return holder;
+ ViewLocationHolder holder = sPool.acquire();
+ if (holder == null) {
+ holder = new ViewLocationHolder();
}
+ holder.init(root, view);
+ return holder;
}
public void recycle() {
- if (mIsPooled) {
- throw new IllegalStateException("Instance already recycled.");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize < MAX_POOL_SIZE) {
- mNext = sPool;
- mIsPooled = true;
- sPool = this;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
@Override
@@ -6337,14 +6392,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return sDebugPaint;
}
- private static float[] getDebugLines(int x1, int y1, int x2, int y2) {
+ private void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
if (sDebugLines== null) {
sDebugLines = new float[16];
}
- x2--;
- y2--;
-
sDebugLines[0] = x1;
sDebugLines[1] = y1;
sDebugLines[2] = x2;
@@ -6353,18 +6405,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
sDebugLines[4] = x2;
sDebugLines[5] = y1;
sDebugLines[6] = x2;
- sDebugLines[7] = y2 + 1;
+ sDebugLines[7] = y2;
- sDebugLines[8] = x2 + 1;
+ sDebugLines[8] = x2;
sDebugLines[9] = y2;
sDebugLines[10] = x1;
sDebugLines[11] = y2;
- sDebugLines[12] = x1;
- sDebugLines[13] = y2;
+ sDebugLines[12] = x1;
+ sDebugLines[13] = y2;
sDebugLines[14] = x1;
sDebugLines[15] = y1;
- return sDebugLines;
+ canvas.drawLines(sDebugLines, paint);
}
}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index ddff91d..4b70bc0 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -295,4 +295,105 @@ public interface ViewParent {
* @hide
*/
public void childAccessibilityStateChanged(View child);
+
+ /**
+ * Tells if this view parent can resolve the layout direction.
+ * See {@link View#setLayoutDirection(int)}
+ *
+ * @return True if this view parent can resolve the layout direction.
+ *
+ * @hide
+ */
+ public boolean canResolveLayoutDirection();
+
+ /**
+ * Tells if this view parent layout direction is resolved.
+ * See {@link View#setLayoutDirection(int)}
+ *
+ * @return True if this view parent layout direction is resolved.
+ *
+ * @hide
+ */
+ public boolean isLayoutDirectionResolved();
+
+ /**
+ * Return this view parent layout direction. See {@link View#getLayoutDirection()}
+ *
+ * @return {@link View#LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
+ * {@link View#LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+ *
+ * @hide
+ */
+ public int getLayoutDirection();
+
+ /**
+ * Tells if this view parent can resolve the text direction.
+ * See {@link View#setTextDirection(int)}
+ *
+ * @return True if this view parent can resolve the text direction.
+ *
+ * @hide
+ */
+ public boolean canResolveTextDirection();
+
+ /**
+ * Tells if this view parent text direction is resolved.
+ * See {@link View#setTextDirection(int)}
+ *
+ * @return True if this view parent text direction is resolved.
+ *
+ * @hide
+ */
+ public boolean isTextDirectionResolved();
+
+ /**
+ * Return this view parent text direction. See {@link View#getTextDirection()}
+ *
+ * @return the resolved text direction. Returns one of:
+ *
+ * {@link View#TEXT_DIRECTION_FIRST_STRONG}
+ * {@link View#TEXT_DIRECTION_ANY_RTL},
+ * {@link View#TEXT_DIRECTION_LTR},
+ * {@link View#TEXT_DIRECTION_RTL},
+ * {@link View#TEXT_DIRECTION_LOCALE}
+ *
+ * @hide
+ */
+ public int getTextDirection();
+
+ /**
+ * Tells if this view parent can resolve the text alignment.
+ * See {@link View#setTextAlignment(int)}
+ *
+ * @return True if this view parent can resolve the text alignment.
+ *
+ * @hide
+ */
+ public boolean canResolveTextAlignment();
+
+ /**
+ * Tells if this view parent text alignment is resolved.
+ * See {@link View#setTextAlignment(int)}
+ *
+ * @return True if this view parent text alignment is resolved.
+ *
+ * @hide
+ */
+ public boolean isTextAlignmentResolved();
+
+ /**
+ * Return this view parent text alignment. See {@link android.view.View#getTextAlignment()}
+ *
+ * @return the resolved text alignment. Returns one of:
+ *
+ * {@link View#TEXT_ALIGNMENT_GRAVITY},
+ * {@link View#TEXT_ALIGNMENT_CENTER},
+ * {@link View#TEXT_ALIGNMENT_TEXT_START},
+ * {@link View#TEXT_ALIGNMENT_TEXT_END},
+ * {@link View#TEXT_ALIGNMENT_VIEW_START},
+ * {@link View#TEXT_ALIGNMENT_VIEW_END}
+ *
+ * @hide
+ */
+ public int getTextAlignment();
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3b91e00..b8fae86 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -114,7 +114,7 @@ public final class ViewRootImpl implements ViewParent,
* Set this system property to true to force the view hierarchy to render
* at 60 Hz. This can be used to measure the potential framerate.
*/
- private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering";
+ private static final String PROPERTY_PROFILE_RENDERING = "viewancestor.profile_rendering";
private static final boolean MEASURE_LATENCY = false;
private static LatencyTimer lt;
@@ -139,6 +139,7 @@ public final class ViewRootImpl implements ViewParent,
final IWindowSession mWindowSession;
final Display mDisplay;
+ final String mBasePackageName;
long mLastTrackballTime = 0;
final TrackballAxis mTrackballAxisX = new TrackballAxis();
@@ -169,9 +170,6 @@ public final class ViewRootImpl implements ViewParent,
int mSeq;
View mView;
- View mFocusedView;
- View mRealFocusedView; // this is not set to null in touch mode
- View mOldFocusedView;
View mAccessibilityFocusedHost;
AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
@@ -231,6 +229,7 @@ public final class ViewRootImpl implements ViewParent,
boolean mIsDrawing;
int mLastSystemUiVisibility;
int mClientWindowLayoutFlags;
+ boolean mLastOverscanRequested;
/** @hide */
public static final int EVENT_NOT_HANDLED = 0;
@@ -264,6 +263,7 @@ public final class ViewRootImpl implements ViewParent,
// These are accessed by multiple threads.
final Rect mWinFrame; // frame given by window manager.
+ final Rect mPendingOverscanInsets = new Rect();
final Rect mPendingVisibleInsets = new Rect();
final Rect mPendingContentInsets = new Rect();
final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets
@@ -276,7 +276,7 @@ public final class ViewRootImpl implements ViewParent,
boolean mScrollMayChange;
int mSoftInputMode;
- View mLastScrolledFocus;
+ WeakReference<View> mLastScrolledFocus;
int mScrollY;
int mCurScrollY;
Scroller mScroller;
@@ -296,15 +296,15 @@ public final class ViewRootImpl implements ViewParent,
final PointF mLastTouchPoint = new PointF();
private boolean mProfileRendering;
- private Thread mRenderProfiler;
- private volatile boolean mRenderProfilingEnabled;
+ private Choreographer.FrameCallback mRenderProfiler;
+ private boolean mRenderProfilingEnabled;
// Variables to track frames per second, enabled via DEBUG_FPS flag
private long mFpsStartTime = -1;
private long mFpsPrevTime = -1;
private int mFpsNumFrames;
- private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>(24);
+ private final ArrayList<DisplayList> mDisplayLists = new ArrayList<DisplayList>();
/**
* see {@link #playSoundEffect(int)}
@@ -324,6 +324,10 @@ public final class ViewRootImpl implements ViewParent,
private final int mDensity;
private final int mNoncompatDensity;
+ private boolean mInLayout = false;
+ ArrayList<View> mLayoutRequesters = new ArrayList<View>();
+ boolean mHandlingLayoutInLayoutRequest = false;
+
private int mViewLayoutDirectionInitial;
/**
@@ -354,6 +358,7 @@ public final class ViewRootImpl implements ViewParent,
// allow the spawning of threads.
mWindowSession = WindowManagerGlobal.getWindowSession(context.getMainLooper());
mDisplay = display;
+ mBasePackageName = context.getBasePackageName();
CompatibilityInfoHolder cih = display.getCompatibilityInfo();
mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
@@ -385,8 +390,6 @@ public final class ViewRootImpl implements ViewParent,
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
- mProfileRendering = Boolean.parseBoolean(
- SystemProperties.get(PROPERTY_PROFILE_RENDERING, "false"));
mChoreographer = Choreographer.getInstance();
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -478,6 +481,9 @@ public final class ViewRootImpl implements ViewParent,
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
+ if (mWindowAttributes.packageName == null) {
+ mWindowAttributes.packageName = mBasePackageName;
+ }
attrs = mWindowAttributes;
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
@@ -562,6 +568,7 @@ public final class ViewRootImpl implements ViewParent,
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
}
+ mPendingOverscanInsets.set(0, 0, 0, 0);
mPendingContentInsets.set(mAttachInfo.mContentInsets);
mPendingVisibleInsets.set(0, 0, 0, 0);
if (DEBUG_LAYOUT) Log.v(TAG, "Added window " + mWindow);
@@ -740,6 +747,7 @@ public final class ViewRootImpl implements ViewParent,
final boolean translucent = attrs.format != PixelFormat.OPAQUE;
mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
+ mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
mAttachInfo.mHardwareAccelerated = mAttachInfo.mHardwareAccelerationRequested
= mAttachInfo.mHardwareRenderer != null;
@@ -774,6 +782,9 @@ public final class ViewRootImpl implements ViewParent,
attrs.systemUiVisibility = mWindowAttributes.systemUiVisibility;
attrs.subtreeSystemUiVisibility = mWindowAttributes.subtreeSystemUiVisibility;
mWindowAttributesChangesFlag = mWindowAttributes.copyFrom(attrs);
+ if (mWindowAttributes.packageName == null) {
+ mWindowAttributes.packageName = mBasePackageName;
+ }
mWindowAttributes.flags |= compatibleWindowFlag;
applyKeepScreenOnFlag(mWindowAttributes);
@@ -830,9 +841,11 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void requestLayout() {
- checkThread();
- mLayoutRequested = true;
- scheduleTraversals();
+ if (!mHandlingLayoutInLayoutRequest) {
+ checkThread();
+ mLayoutRequested = true;
+ scheduleTraversals();
+ }
}
@Override
@@ -1245,6 +1258,9 @@ public final class ViewRootImpl implements ViewParent,
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
+ if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
+ insetsChanged = true;
+ }
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
@@ -1310,15 +1326,20 @@ public final class ViewRootImpl implements ViewParent,
}
}
- if (params != null && (host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
- if (!PixelFormat.formatHasAlpha(params.format)) {
- params.format = PixelFormat.TRANSLUCENT;
+ if (params != null) {
+ if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
+ if (!PixelFormat.formatHasAlpha(params.format)) {
+ params.format = PixelFormat.TRANSLUCENT;
+ }
}
+ mAttachInfo.mOverscanRequested = (params.flags
+ & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
}
if (mFitSystemWindowsRequested) {
mFitSystemWindowsRequested = false;
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
+ mLastOverscanRequested = mAttachInfo.mOverscanRequested;
host.fitSystemWindows(mFitSystemWindowsInsets);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
@@ -1373,7 +1394,6 @@ public final class ViewRootImpl implements ViewParent,
boolean hwInitialized = false;
boolean contentInsetsChanged = false;
- boolean visibleInsetsChanged;
boolean hadSurface = mSurface.isValid();
try {
@@ -1386,6 +1406,7 @@ public final class ViewRootImpl implements ViewParent,
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString()
+ + " overscan=" + mPendingOverscanInsets.toShortString()
+ " content=" + mPendingContentInsets.toShortString()
+ " visible=" + mPendingVisibleInsets.toShortString()
+ " surface=" + mSurface);
@@ -1397,9 +1418,11 @@ public final class ViewRootImpl implements ViewParent,
mPendingConfiguration.seq = 0;
}
+ final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
+ mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
- visibleInsetsChanged = !mPendingVisibleInsets.equals(
+ final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
if (contentInsetsChanged) {
if (mWidth > 0 && mHeight > 0 && lp != null &&
@@ -1427,8 +1450,6 @@ public final class ViewRootImpl implements ViewParent,
}
// TODO: should handle create/resize failure
layerCanvas = mResizeBuffer.start(hwRendererCanvas);
- layerCanvas.setViewport(mWidth, mHeight);
- layerCanvas.onPreDraw(null);
final int restoreCount = layerCanvas.save();
int yoff;
@@ -1465,9 +1486,6 @@ public final class ViewRootImpl implements ViewParent,
} catch (OutOfMemoryError e) {
Log.w(TAG, "Not enough memory for content change anim buffer", e);
} finally {
- if (layerCanvas != null) {
- layerCanvas.onPostDraw();
- }
if (mResizeBuffer != null) {
mResizeBuffer.end(hwRendererCanvas);
if (!completed) {
@@ -1481,9 +1499,18 @@ public final class ViewRootImpl implements ViewParent,
if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
+ mAttachInfo.mContentInsets);
}
+ if (overscanInsetsChanged) {
+ mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
+ if (DEBUG_LAYOUT) Log.v(TAG, "Overscan insets changing to: "
+ + mAttachInfo.mOverscanInsets);
+ // Need to relayout with content insets.
+ contentInsetsChanged = true;
+ }
if (contentInsetsChanged || mLastSystemUiVisibility !=
- mAttachInfo.mSystemUiVisibility || mFitSystemWindowsRequested) {
+ mAttachInfo.mSystemUiVisibility || mFitSystemWindowsRequested
+ || mLastOverscanRequested != mAttachInfo.mOverscanRequested) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
+ mLastOverscanRequested = mAttachInfo.mOverscanRequested;
mFitSystemWindowsRequested = false;
mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets);
host.fitSystemWindows(mFitSystemWindowsInsets);
@@ -1512,16 +1539,7 @@ public final class ViewRootImpl implements ViewParent,
hwInitialized = mAttachInfo.mHardwareRenderer.initialize(
mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException initializing HW surface", e);
- try {
- if (!mWindowSession.outOfMemory(mWindow) &&
- Process.myUid() != Process.SYSTEM_UID) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
- }
- } catch (RemoteException ex) {
- }
- mLayoutRequested = true; // ask wm for a new surface next time.
+ handleOutOfResourcesException(e);
return;
}
}
@@ -1529,7 +1547,9 @@ public final class ViewRootImpl implements ViewParent,
} else if (!mSurface.isValid()) {
// If the surface has been removed, then reset the scroll
// positions.
- mLastScrolledFocus = null;
+ if (mLastScrolledFocus != null) {
+ mLastScrolledFocus.clear();
+ }
mScrollY = mCurScrollY = 0;
if (mScroller != null) {
mScroller.abortAnimation();
@@ -1546,15 +1566,7 @@ public final class ViewRootImpl implements ViewParent,
try {
mAttachInfo.mHardwareRenderer.updateSurface(mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException updating HW surface", e);
- try {
- if (!mWindowSession.outOfMemory(mWindow)) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
- }
- } catch (RemoteException ex) {
- }
- mLayoutRequested = true; // ask wm for a new surface next time.
+ handleOutOfResourcesException(e);
return;
}
}
@@ -1718,7 +1730,7 @@ public final class ViewRootImpl implements ViewParent,
boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
- performLayout();
+ performLayout(lp, desiredWindowWidth, desiredWindowHeight);
// By this point all views have been sized and positionned
// We can compute the transparent area
@@ -1805,13 +1817,11 @@ public final class ViewRootImpl implements ViewParent,
if (mView != null) {
if (!mView.hasFocus()) {
mView.requestFocus(View.FOCUS_FORWARD);
- mFocusedView = mRealFocusedView = mView.findFocus();
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: requested focused view="
- + mFocusedView);
+ + mView.findFocus());
} else {
- mRealFocusedView = mView.findFocus();
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "First: existing focused view="
- + mRealFocusedView);
+ + mView.findFocus());
}
}
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_ANIMATING) != 0) {
@@ -1878,6 +1888,19 @@ public final class ViewRootImpl implements ViewParent,
mIsInTraversal = false;
}
+ private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
+ Log.e(TAG, "OutOfResourcesException initializing HW surface", e);
+ try {
+ if (!mWindowSession.outOfMemory(mWindow) &&
+ Process.myUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, "No processes killed for memory; killing self");
+ Process.killProcess(Process.myPid());
+ }
+ } catch (RemoteException ex) {
+ }
+ mLayoutRequested = true; // ask wm for a new surface next time.
+ }
+
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
@@ -1887,9 +1910,66 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private void performLayout() {
+ /**
+ * Called by {@link android.view.View#isInLayout()} to determine whether the view hierarchy
+ * is currently undergoing a layout pass.
+ *
+ * @return whether the view hierarchy is currently undergoing a layout pass
+ */
+ boolean isInLayout() {
+ return mInLayout;
+ }
+
+ /**
+ * Called by {@link android.view.View#requestLayout()} if the view hierarchy is currently
+ * undergoing a layout pass. requestLayout() should not generally be called during layout,
+ * unless the container hierarchy knows what it is doing (i.e., it is fine as long as
+ * all children in that container hierarchy are measured and laid out at the end of the layout
+ * pass for that container). If requestLayout() is called anyway, we handle it correctly
+ * by registering all requesters during a frame as it proceeds. At the end of the frame,
+ * we check all of those views to see if any still have pending layout requests, which
+ * indicates that they were not correctly handled by their container hierarchy. If that is
+ * the case, we clear all such flags in the tree, to remove the buggy flag state that leads
+ * to blank containers, and force a second request/measure/layout pass in this frame. If
+ * more requestLayout() calls are received during that second layout pass, we post those
+ * requests to the next frame to avoid possible infinite loops.
+ *
+ * <p>The return value from this method indicates whether the request should proceed
+ * (if it is a request during the first layout pass) or should be skipped and posted to the
+ * next frame (if it is a request during the second layout pass).</p>
+ *
+ * @param view the view that requested the layout.
+ *
+ * @return true if request should proceed, false otherwise.
+ */
+ boolean requestLayoutDuringLayout(final View view) {
+ if (view.mParent == null || view.mAttachInfo == null) {
+ // Would not normally trigger another layout, so just let it pass through as usual
+ return true;
+ }
+ if (!mHandlingLayoutInLayoutRequest) {
+ if (!mLayoutRequesters.contains(view)) {
+ mLayoutRequesters.add(view);
+ }
+ return true;
+ } else {
+ Log.w("View", "requestLayout() called by " + view + " during second layout pass: " +
+ "posting to next frame");
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ view.requestLayout();
+ }
+ });
+ return false;
+ }
+ }
+
+ private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
+ int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
+ mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
@@ -1900,9 +1980,72 @@ public final class ViewRootImpl implements ViewParent,
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+
+ mInLayout = false;
+ int numViewsRequestingLayout = mLayoutRequesters.size();
+ if (numViewsRequestingLayout > 0) {
+ // requestLayout() was called during layout.
+ // If no layout-request flags are set on the requesting views, there is no problem.
+ // If some requests are still pending, then we need to clear those flags and do
+ // a full request/measure/layout pass to handle this situation.
+
+ // Check state of layout flags for all requesters
+ ArrayList<View> mValidLayoutRequesters = null;
+ for (int i = 0; i < numViewsRequestingLayout; ++i) {
+ View view = mLayoutRequesters.get(i);
+ if ((view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) == View.PFLAG_FORCE_LAYOUT) {
+ while (view != null && view.mAttachInfo != null && view.mParent != null &&
+ (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
+ if ((view.mViewFlags & View.VISIBILITY_MASK) != View.GONE) {
+ // Only trigger new requests for non-GONE views
+ Log.w(TAG, "requestLayout() improperly called during " +
+ "layout: running second layout pass for " + view);
+ if (mValidLayoutRequesters == null) {
+ mValidLayoutRequesters = new ArrayList<View>();
+ }
+ mValidLayoutRequesters.add(view);
+ break;
+ }
+ if (view.mParent instanceof View) {
+ view = (View) view.mParent;
+ } else {
+ view = null;
+ }
+ }
+ }
+ }
+ if (mValidLayoutRequesters != null) {
+ // Clear flags throughout hierarchy, walking up from each flagged requester
+ for (int i = 0; i < numViewsRequestingLayout; ++i) {
+ View view = mLayoutRequesters.get(i);
+ while (view != null &&
+ (view.mPrivateFlags & View.PFLAG_FORCE_LAYOUT) != 0) {
+ view.mPrivateFlags &= ~View.PFLAG_FORCE_LAYOUT;
+ if (view.mParent instanceof View) {
+ view = (View) view.mParent;
+ } else {
+ view = null;
+ }
+ }
+ }
+ // Process fresh layout requests, then measure and layout
+ mHandlingLayoutInLayoutRequest = true;
+ int numValidRequests = mValidLayoutRequesters.size();
+ for (int i = 0; i < numValidRequests; ++i) {
+ mValidLayoutRequesters.get(i).requestLayout();
+ }
+ measureHierarchy(host, lp, mView.getContext().getResources(),
+ desiredWindowWidth, desiredWindowHeight);
+ mInLayout = true;
+ host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
+ mHandlingLayoutInLayoutRequest = false;
+ }
+ mLayoutRequesters.clear();
+ }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ mInLayout = false;
}
public void requestTransparentRegion(View child) {
@@ -1985,31 +2128,25 @@ public final class ViewRootImpl implements ViewParent,
private void profileRendering(boolean enabled) {
if (mProfileRendering) {
mRenderProfilingEnabled = enabled;
- if (mRenderProfiler == null) {
- mRenderProfiler = new Thread(new Runnable() {
- @Override
- public void run() {
- Log.d(TAG, "Starting profiling thread");
- while (mRenderProfilingEnabled) {
- mAttachInfo.mHandler.post(new Runnable() {
- @Override
- public void run() {
- mDirty.set(0, 0, mWidth, mHeight);
- scheduleTraversals();
- }
- });
- try {
- // TODO: This should use vsync when we get an API
- Thread.sleep(15);
- } catch (InterruptedException e) {
- Log.d(TAG, "Exiting profiling thread");
- }
+
+ if (mRenderProfiler != null) {
+ mChoreographer.removeFrameCallback(mRenderProfiler);
+ }
+ if (mRenderProfilingEnabled) {
+ if (mRenderProfiler == null) {
+ mRenderProfiler = new Choreographer.FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ mDirty.set(0, 0, mWidth, mHeight);
+ scheduleTraversals();
+ if (mRenderProfilingEnabled) {
+ mChoreographer.postFrameCallback(mRenderProfiler);
+ }
}
- }
- }, "Rendering Profiler");
- mRenderProfiler.start();
+ };
+ }
+ mChoreographer.postFrameCallback(mRenderProfiler);
} else {
- mRenderProfiler.interrupt();
mRenderProfiler = null;
}
}
@@ -2085,7 +2222,7 @@ public final class ViewRootImpl implements ViewParent,
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
- if (surface == null || !surface.isValid()) {
+ if (!surface.isValid()) {
return;
}
@@ -2166,6 +2303,8 @@ public final class ViewRootImpl implements ViewParent,
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
+ invalidateDisplayLists();
+
attachInfo.mTreeObserver.dispatchOnDraw();
if (!dirty.isEmpty() || mIsAnimating) {
@@ -2184,8 +2323,35 @@ public final class ViewRootImpl implements ViewParent,
animating ? null : mCurrentDirty)) {
mPreviousDirty.set(0, 0, mWidth, mHeight);
}
- } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
- return;
+ } else {
+ // If we get here with a disabled & requested hardware renderer, something went
+ // wrong (an invalidate posted right before we destroyed the hardware surface
+ // for instance) so we should just bail out. Locking the surface with software
+ // rendering at this point would lock it forever and prevent hardware renderer
+ // from doing its job when it comes back.
+ // Before we request a new frame we must however attempt to reinitiliaze the
+ // hardware renderer if it's in requested state. This would happen after an
+ // eglTerminate() for instance.
+ if (attachInfo.mHardwareRenderer != null &&
+ !attachInfo.mHardwareRenderer.isEnabled() &&
+ attachInfo.mHardwareRenderer.isRequested()) {
+
+ try {
+ attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
+ mHolder.getSurface());
+ } catch (Surface.OutOfResourcesException e) {
+ handleOutOfResourcesException(e);
+ return;
+ }
+
+ mFullRedrawNeeded = true;
+ scheduleTraversals();
+ return;
+ }
+
+ if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
+ return;
+ }
}
}
@@ -2201,18 +2367,6 @@ public final class ViewRootImpl implements ViewParent,
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
- // If we get here with a disabled & requested hardware renderer, something went
- // wrong (an invalidate posted right before we destroyed the hardware surface
- // for instance) so we should just bail out. Locking the surface with software
- // rendering at this point would lock it forever and prevent hardware renderer
- // from doing its job when it comes back.
- if (attachInfo.mHardwareRenderer != null && !attachInfo.mHardwareRenderer.isEnabled() &&
- attachInfo.mHardwareRenderer.isRequested()) {
- mFullRedrawNeeded = true;
- scheduleTraversals();
- return false;
- }
-
// Draw with software renderer.
Canvas canvas;
try {
@@ -2231,15 +2385,7 @@ public final class ViewRootImpl implements ViewParent,
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
- Log.e(TAG, "OutOfResourcesException locking surface", e);
- try {
- if (!mWindowSession.outOfMemory(mWindow)) {
- Slog.w(TAG, "No processes killed for memory; killing self");
- Process.killProcess(Process.myPid());
- }
- } catch (RemoteException ex) {
- }
- mLayoutRequested = true; // ask wm for a new surface next time.
+ handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not lock surface", e);
@@ -2376,8 +2522,9 @@ public final class ViewRootImpl implements ViewParent,
for (int i = 0; i < count; i++) {
final DisplayList displayList = displayLists.get(i);
- displayList.invalidate();
- displayList.clear();
+ if (displayList.isDirty()) {
+ displayList.clear();
+ }
}
displayLists.clear();
@@ -2403,17 +2550,12 @@ public final class ViewRootImpl implements ViewParent,
// requestChildRectangleOnScreen() call (in which case 'rectangle'
// is non-null and we just want to scroll to whatever that
// rectangle is).
- View focus = mRealFocusedView;
-
- // When in touch mode, focus points to the previously focused view,
- // which may have been removed from the view hierarchy. The following
- // line checks whether the view is still in our hierarchy.
- if (focus == null || focus.mAttachInfo != mAttachInfo) {
- mRealFocusedView = null;
+ View focus = mView.findFocus();
+ if (focus == null) {
return false;
}
-
- if (focus != mLastScrolledFocus) {
+ View lastScrolledFocus = (mLastScrolledFocus != null) ? mLastScrolledFocus.get() : null;
+ if (lastScrolledFocus != null && focus != lastScrolledFocus) {
// If the focus has changed, then ignore any requests to scroll
// to a rectangle; first we want to make sure the entire focus
// view is visible.
@@ -2422,8 +2564,7 @@ public final class ViewRootImpl implements ViewParent,
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Eval scroll: focus=" + focus
+ " rectangle=" + rectangle + " ci=" + ci
+ " vi=" + vi);
- if (focus == mLastScrolledFocus && !mScrollMayChange
- && rectangle == null) {
+ if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
// Optimization: if the focus hasn't changed since last
// time, and no layout has happened, then just leave things
// as they are.
@@ -2433,7 +2574,7 @@ public final class ViewRootImpl implements ViewParent,
// We need to determine if the currently focused view is
// within the visible part of the window and, if not, apply
// a pan so it can be seen.
- mLastScrolledFocus = focus;
+ mLastScrolledFocus = new WeakReference<View>(focus);
mScrollMayChange = false;
if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Need to scroll?");
// Try to find the rectangle from the focus view.
@@ -2560,33 +2701,19 @@ public final class ViewRootImpl implements ViewParent,
}
public void requestChildFocus(View child, View focused) {
- checkThread();
-
if (DEBUG_INPUT_RESIZE) {
Log.v(TAG, "Request child focus: focus now " + focused);
}
-
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, focused);
+ checkThread();
scheduleTraversals();
-
- mFocusedView = mRealFocusedView = focused;
}
public void clearChildFocus(View child) {
- checkThread();
-
if (DEBUG_INPUT_RESIZE) {
Log.v(TAG, "Clearing child focus");
}
-
- mOldFocusedView = mFocusedView;
-
- // Invoke the listener only if there is no view to take focus
- if (focusSearch(null, View.FOCUS_FORWARD) == null) {
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mOldFocusedView, null);
- }
-
- mFocusedView = mRealFocusedView = null;
+ checkThread();
+ scheduleTraversals();
}
@Override
@@ -2603,14 +2730,13 @@ public final class ViewRootImpl implements ViewParent,
// the one case where will transfer focus away from the current one
// is if the current view is a view group that prefers to give focus
// to its children first AND the view is a descendant of it.
- mFocusedView = mView.findFocus();
- boolean descendantsHaveDibsOnFocus =
- (mFocusedView instanceof ViewGroup) &&
- (((ViewGroup) mFocusedView).getDescendantFocusability() ==
- ViewGroup.FOCUS_AFTER_DESCENDANTS);
- if (descendantsHaveDibsOnFocus && isViewDescendantOf(v, mFocusedView)) {
- // If a view gets the focus, the listener will be invoked from requestChildFocus()
- v.requestFocus();
+ View focused = mView.findFocus();
+ if (focused instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) focused;
+ if (group.getDescendantFocusability() == ViewGroup.FOCUS_AFTER_DESCENDANTS
+ && isViewDescendantOf(v, focused)) {
+ v.requestFocus();
+ }
}
}
}
@@ -2751,11 +2877,10 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_UPDATE_CONFIGURATION = 18;
private final static int MSG_PROCESS_INPUT_EVENTS = 19;
private final static int MSG_DISPATCH_SCREEN_STATE = 20;
- private final static int MSG_INVALIDATE_DISPLAY_LIST = 21;
- private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 22;
- private final static int MSG_DISPATCH_DONE_ANIMATING = 23;
- private final static int MSG_INVALIDATE_WORLD = 24;
- private final static int MSG_WINDOW_MOVED = 25;
+ private final static int MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST = 21;
+ private final static int MSG_DISPATCH_DONE_ANIMATING = 22;
+ private final static int MSG_INVALIDATE_WORLD = 23;
+ private final static int MSG_WINDOW_MOVED = 24;
final class ViewRootHandler extends Handler {
@Override
@@ -2801,8 +2926,6 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_PROCESS_INPUT_EVENTS";
case MSG_DISPATCH_SCREEN_STATE:
return "MSG_DISPATCH_SCREEN_STATE";
- case MSG_INVALIDATE_DISPLAY_LIST:
- return "MSG_INVALIDATE_DISPLAY_LIST";
case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST:
return "MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST";
case MSG_DISPATCH_DONE_ANIMATING:
@@ -2822,7 +2945,7 @@ public final class ViewRootImpl implements ViewParent,
case MSG_INVALIDATE_RECT:
final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
info.target.invalidate(info.left, info.top, info.right, info.bottom);
- info.release();
+ info.recycle();
break;
case MSG_IME_FINISHED_EVENT:
handleImeFinishedEvent(msg.arg1, msg.arg2 != 0);
@@ -2841,6 +2964,7 @@ public final class ViewRootImpl implements ViewParent,
// Recycled in the fall through...
SomeArgs args = (SomeArgs) msg.obj;
if (mWinFrame.equals(args.arg1)
+ && mPendingOverscanInsets.equals(args.arg5)
&& mPendingContentInsets.equals(args.arg2)
&& mPendingVisibleInsets.equals(args.arg3)
&& args.arg4 == null) {
@@ -2857,6 +2981,7 @@ public final class ViewRootImpl implements ViewParent,
}
mWinFrame.set((Rect) args.arg1);
+ mPendingOverscanInsets.set((Rect) args.arg5);
mPendingContentInsets.set((Rect) args.arg2);
mPendingVisibleInsets.set((Rect) args.arg3);
@@ -2905,10 +3030,8 @@ public final class ViewRootImpl implements ViewParent,
mSurface != null && mSurface.isValid()) {
mFullRedrawNeeded = true;
try {
- if (mAttachInfo.mHardwareRenderer.initializeIfNeeded(
- mWidth, mHeight, mHolder.getSurface())) {
- mFullRedrawNeeded = true;
- }
+ mAttachInfo.mHardwareRenderer.initializeIfNeeded(
+ mWidth, mHeight, mHolder.getSurface());
} catch (Surface.OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException locking surface", e);
try {
@@ -3009,7 +3132,7 @@ public final class ViewRootImpl implements ViewParent,
handleDragEvent(event);
} break;
case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo)msg.obj);
+ handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
} break;
case MSG_UPDATE_CONFIGURATION: {
Configuration config = (Configuration)msg.obj;
@@ -3023,9 +3146,6 @@ public final class ViewRootImpl implements ViewParent,
handleScreenStateChange(msg.arg1 == 1);
}
} break;
- case MSG_INVALIDATE_DISPLAY_LIST: {
- invalidateDisplayLists();
- } break;
case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: {
setAccessibilityFocus(null, null);
} break;
@@ -3094,7 +3214,6 @@ public final class ViewRootImpl implements ViewParent,
// set yet.
final View focused = mView.findFocus();
if (focused != null && !focused.isFocusableInTouchMode()) {
-
final ViewGroup ancestorToTakeFocus =
findAncestorToTakeFocusInTouchMode(focused);
if (ancestorToTakeFocus != null) {
@@ -3103,10 +3222,7 @@ public final class ViewRootImpl implements ViewParent,
return ancestorToTakeFocus.requestFocus();
} else {
// nothing appropriate to have focus in touch mode, clear it out
- mView.unFocus();
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
- mFocusedView = null;
- mOldFocusedView = null;
+ focused.unFocus();
return true;
}
}
@@ -3141,12 +3257,11 @@ public final class ViewRootImpl implements ViewParent,
private boolean leaveTouchMode() {
if (mView != null) {
if (mView.hasFocus()) {
- // i learned the hard way to not trust mFocusedView :)
- mFocusedView = mView.findFocus();
- if (!(mFocusedView instanceof ViewGroup)) {
+ View focusedView = mView.findFocus();
+ if (!(focusedView instanceof ViewGroup)) {
// some view has focus, let it keep it
return false;
- } else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
+ } else if (((ViewGroup) focusedView).getDescendantFocusability() !=
ViewGroup.FOCUS_AFTER_DESCENDANTS) {
// some view group has focus, and doesn't prefer its children
// over itself for focus, so let them keep it.
@@ -3940,7 +4055,7 @@ public final class ViewRootImpl implements ViewParent,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mWinFrame, mPendingContentInsets, mPendingVisibleInsets,
+ mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mSurface);
//Log.d(TAG, "<<<<<< BACK FROM relayout");
if (restore) {
@@ -3949,6 +4064,7 @@ public final class ViewRootImpl implements ViewParent,
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
+ mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets);
mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
}
@@ -4066,6 +4182,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (mAdded && !mFirst) {
+ invalidateDisplayLists();
destroyHardwareRenderer();
if (mView != null) {
@@ -4098,14 +4215,30 @@ public final class ViewRootImpl implements ViewParent,
}
public void loadSystemProperties() {
- boolean layout = SystemProperties.getBoolean(
- View.DEBUG_LAYOUT_PROPERTY, false);
- if (layout != mAttachInfo.mDebugLayout) {
- mAttachInfo.mDebugLayout = layout;
- if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
- mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Profiling
+ mProfileRendering = SystemProperties.getBoolean(PROPERTY_PROFILE_RENDERING, false);
+ profileRendering(mAttachInfo.mHasWindowFocus);
+
+ // Hardware rendering
+ if (mAttachInfo.mHardwareRenderer != null) {
+ if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mHolder.getSurface())) {
+ invalidate();
+ }
+ }
+
+ // Layout debugging
+ boolean layout = SystemProperties.getBoolean(View.DEBUG_LAYOUT_PROPERTY, false);
+ if (layout != mAttachInfo.mDebugLayout) {
+ mAttachInfo.mDebugLayout = layout;
+ if (!mHandler.hasMessages(MSG_INVALIDATE_WORLD)) {
+ mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_WORLD, 200);
+ }
+ }
}
- }
+ });
}
private void destroyHardwareRenderer() {
@@ -4137,7 +4270,7 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendMessage(msg);
}
- public void dispatchResized(Rect frame, Rect contentInsets,
+ public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (DEBUG_LAYOUT) Log.v(TAG, "Resizing " + this + ": frame=" + frame.toShortString()
+ " contentInsets=" + contentInsets.toShortString()
@@ -4146,6 +4279,7 @@ public final class ViewRootImpl implements ViewParent,
Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(frame);
+ mTranslator.translateRectInScreenToAppWindow(overscanInsets);
mTranslator.translateRectInScreenToAppWindow(contentInsets);
mTranslator.translateRectInScreenToAppWindow(visibleInsets);
}
@@ -4155,6 +4289,7 @@ public final class ViewRootImpl implements ViewParent,
args.arg2 = sameProcessCall ? new Rect(contentInsets) : contentInsets;
args.arg3 = sameProcessCall ? new Rect(visibleInsets) : visibleInsets;
args.arg4 = sameProcessCall && newConfig != null ? new Configuration(newConfig) : newConfig;
+ args.arg5 = sameProcessCall ? new Rect(overscanInsets) : overscanInsets;
msg.obj = args;
mHandler.sendMessage(msg);
}
@@ -4437,7 +4572,7 @@ public final class ViewRootImpl implements ViewParent,
AttachInfo.InvalidateInfo info = mViewRects.get(i);
if (info.target == view) {
mViewRects.remove(i);
- info.release();
+ info.recycle();
}
}
@@ -4478,7 +4613,7 @@ public final class ViewRootImpl implements ViewParent,
for (int i = 0; i < viewRectCount; i++) {
final View.AttachInfo.InvalidateInfo info = mTempViewRects[i];
info.target.invalidate(info.left, info.top, info.right, info.bottom);
- info.release();
+ info.recycle();
}
}
@@ -4513,19 +4648,6 @@ public final class ViewRootImpl implements ViewParent,
public void enqueueDisplayList(DisplayList displayList) {
mDisplayLists.add(displayList);
-
- mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST);
- Message msg = mHandler.obtainMessage(MSG_INVALIDATE_DISPLAY_LIST);
- mHandler.sendMessage(msg);
- }
-
- public void dequeueDisplayList(DisplayList displayList) {
- if (mDisplayLists.remove(displayList)) {
- displayList.invalidate();
- if (mDisplayLists.size() == 0) {
- mHandler.removeMessages(MSG_INVALIDATE_DISPLAY_LIST);
- }
- }
}
public void cancelInvalidate(View view) {
@@ -4727,6 +4849,51 @@ public final class ViewRootImpl implements ViewParent,
postSendWindowContentChangedCallback(child);
}
+ @Override
+ public boolean canResolveLayoutDirection() {
+ return true;
+ }
+
+ @Override
+ public boolean isLayoutDirectionResolved() {
+ return true;
+ }
+
+ @Override
+ public int getLayoutDirection() {
+ return View.LAYOUT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ @Override
+ public boolean canResolveTextDirection() {
+ return true;
+ }
+
+ @Override
+ public boolean isTextDirectionResolved() {
+ return true;
+ }
+
+ @Override
+ public int getTextDirection() {
+ return View.TEXT_DIRECTION_RESOLVED_DEFAULT;
+ }
+
+ @Override
+ public boolean canResolveTextAlignment() {
+ return true;
+ }
+
+ @Override
+ public boolean isTextAlignmentResolved() {
+ return true;
+ }
+
+ @Override
+ public int getTextAlignment() {
+ return View.TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+ }
+
private View getCommonPredecessor(View first, View second) {
if (mAttachInfo != null) {
if (mTempHashSet == null) {
@@ -4857,11 +5024,11 @@ public final class ViewRootImpl implements ViewParent,
mWindowSession = viewAncestor.mWindowSession;
}
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
- viewAncestor.dispatchResized(frame, contentInsets,
+ viewAncestor.dispatchResized(frame, overscanInsets, contentInsets,
visibleInsets, reportDraw, newConfig);
}
}
@@ -5362,12 +5529,13 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5399,14 +5567,16 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
- int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId,
+ String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ .findAccessibilityNodeInfosByViewIdClientThread(accessibilityNodeId,
+ viewId, interactionId, callback, flags, interrogatingPid,
+ interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5420,12 +5590,13 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
- interactionId, callback, flags, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid,
+ spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5439,12 +5610,12 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void findFocus(long accessibilityNodeId, int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findFocusClientThread(accessibilityNodeId, focusType, interactionId, callback,
- flags, interrogatingPid, interrogatingTid);
+ flags, interrogatingPid, interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5458,12 +5629,12 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void focusSearch(long accessibilityNodeId, int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
- int interrogatingPid, long interrogatingTid) {
+ int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.focusSearchClientThread(accessibilityNodeId, direction, interactionId,
- callback, flags, interrogatingPid, interrogatingTid);
+ callback, flags, interrogatingPid, interrogatingTid, spec);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 001d020..e711b94 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -218,12 +218,14 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private static class WarningDialogReceiver extends BroadcastReceiver
implements DialogInterface.OnDismissListener {
- private Context mContext;
- private Dialog mDialog;
+ private final Context mContext;
+ private final Dialog mDialog;
+ private final VolumePanel mVolumePanel;
- WarningDialogReceiver(Context context, Dialog dialog) {
+ WarningDialogReceiver(Context context, Dialog dialog, VolumePanel volumePanel) {
mContext = context;
mDialog = dialog;
+ mVolumePanel = volumePanel;
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(this, filter);
}
@@ -231,16 +233,20 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
@Override
public void onReceive(Context context, Intent intent) {
mDialog.cancel();
- synchronized (sConfirmSafeVolumeLock) {
- sConfirmSafeVolumeDialog = null;
- }
+ cleanUp();
}
public void onDismiss(DialogInterface unused) {
mContext.unregisterReceiver(this);
+ cleanUp();
+ }
+
+ private void cleanUp() {
synchronized (sConfirmSafeVolumeLock) {
sConfirmSafeVolumeDialog = null;
}
+ mVolumePanel.forceTimeout();
+ mVolumePanel.updateStates();
}
}
@@ -276,7 +282,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
mDialog = new Dialog(context, R.style.Theme_Panel_Volume) {
public boolean onTouchEvent(MotionEvent event) {
- if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
+ sConfirmSafeVolumeDialog == null) {
forceTimeout();
return true;
}
@@ -330,6 +337,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
listenToRingerMode();
}
+ public void setLayoutDirection(int layoutDirection) {
+ mPanel.setLayoutDirection(layoutDirection);
+ updateStates();
+ }
+
private void listenToRingerMode() {
final IntentFilter filter = new IntentFilter();
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
@@ -453,6 +465,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
private void updateSlider(StreamControl sc) {
sc.seekbarView.setProgress(getStreamVolume(sc.streamType));
final boolean muted = isMuted(sc.streamType);
+ // Force reloading the image resource
+ sc.icon.setImageDrawable(null);
sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
if (sc.streamType == AudioManager.STREAM_RING &&
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
@@ -462,7 +476,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
// never disable touch interactions for remote playback, the muting is not tied to
// the state of the phone.
sc.seekbarView.setEnabled(true);
- } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
+ } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
+ (sConfirmSafeVolumeDialog != null)) {
sc.seekbarView.setEnabled(false);
} else {
sc.seekbarView.setEnabled(true);
@@ -491,7 +506,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
}
- private void updateStates() {
+ public void updateStates() {
final int count = mSliderGroup.getChildCount();
for (int i = 0; i < count; i++) {
StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag();
@@ -563,9 +578,9 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
postMuteChanged(STREAM_MASTER, flags);
}
- public void postDisplaySafeVolumeWarning() {
+ public void postDisplaySafeVolumeWarning(int flags) {
if (hasMessages(MSG_DISPLAY_SAFE_VOLUME_WARNING)) return;
- obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget();
+ obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
}
/**
@@ -599,7 +614,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
removeMessages(MSG_FREE_RESOURCES);
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
-
resetTimeout();
}
@@ -705,7 +719,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
(streamType != mAudioManager.getMasterStreamType() &&
streamType != AudioService.STREAM_REMOTE_MUSIC &&
- isMuted(streamType))) {
+ isMuted(streamType)) ||
+ sConfirmSafeVolumeDialog != null) {
sc.seekbarView.setEnabled(false);
} else {
sc.seekbarView.setEnabled(true);
@@ -803,7 +818,6 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
removeMessages(MSG_FREE_RESOURCES);
sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);
-
resetTimeout();
}
@@ -839,30 +853,34 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
}
}
- protected void onDisplaySafeVolumeWarning() {
- synchronized (sConfirmSafeVolumeLock) {
- if (sConfirmSafeVolumeDialog != null) {
- return;
+ protected void onDisplaySafeVolumeWarning(int flags) {
+ if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) {
+ synchronized (sConfirmSafeVolumeLock) {
+ if (sConfirmSafeVolumeDialog != null) {
+ return;
+ }
+ sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
+ .setMessage(com.android.internal.R.string.safe_media_volume_warning)
+ .setPositiveButton(com.android.internal.R.string.yes,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mAudioService.disableSafeMediaVolume();
+ }
+ })
+ .setNegativeButton(com.android.internal.R.string.no, null)
+ .setIconAttribute(android.R.attr.alertDialogIcon)
+ .create();
+ final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
+ sConfirmSafeVolumeDialog, this);
+
+ sConfirmSafeVolumeDialog.setOnDismissListener(warning);
+ sConfirmSafeVolumeDialog.getWindow().setType(
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ sConfirmSafeVolumeDialog.show();
}
- sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
- .setMessage(com.android.internal.R.string.safe_media_volume_warning)
- .setPositiveButton(com.android.internal.R.string.yes,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- mAudioService.disableSafeMediaVolume();
- }
- })
- .setNegativeButton(com.android.internal.R.string.no, null)
- .setIconAttribute(android.R.attr.alertDialogIcon)
- .create();
- final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
- sConfirmSafeVolumeDialog);
-
- sConfirmSafeVolumeDialog.setOnDismissListener(warning);
- sConfirmSafeVolumeDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
- sConfirmSafeVolumeDialog.show();
+ updateStates();
}
+ resetTimeout();
}
/**
@@ -958,6 +976,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
mDialog.dismiss();
mActiveStreamType = -1;
}
+ synchronized (sConfirmSafeVolumeLock) {
+ if (sConfirmSafeVolumeDialog != null) {
+ sConfirmSafeVolumeDialog.dismiss();
+ }
+ }
break;
}
case MSG_RINGER_MODE_CHANGED: {
@@ -981,7 +1004,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
break;
case MSG_DISPLAY_SAFE_VOLUME_WARNING:
- onDisplaySafeVolumeWarning();
+ onDisplaySafeVolumeWarning(msg.arg1);
break;
}
}
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
deleted file mode 100644
index 7d16e14..0000000
--- a/core/java/android/view/WindowInfo.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Information the state of a window.
- *
- * @hide
- */
-public class WindowInfo implements Parcelable {
-
- private static final int MAX_POOL_SIZE = 20;
-
- private static int UNDEFINED = -1;
-
- private static Object sPoolLock = new Object();
- private static WindowInfo sPool;
- private static int sPoolSize;
-
- private WindowInfo mNext;
- private boolean mInPool;
-
- public IBinder token;
-
- public final Rect frame = new Rect();
-
- public final Rect touchableRegion = new Rect();
-
- public int type = UNDEFINED;
-
- public float compatibilityScale = UNDEFINED;
-
- public boolean visible;
-
- public int displayId = UNDEFINED;
-
- public int layer = UNDEFINED;
-
- private WindowInfo() {
- /* do nothing - reduce visibility */
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeStrongBinder(token);
- parcel.writeParcelable(frame, 0);
- parcel.writeParcelable(touchableRegion, 0);
- parcel.writeInt(type);
- parcel.writeFloat(compatibilityScale);
- parcel.writeInt(visible ? 1 : 0);
- parcel.writeInt(displayId);
- parcel.writeInt(layer);
- recycle();
- }
-
- private void initFromParcel(Parcel parcel) {
- token = parcel.readStrongBinder();
- frame.set((Rect) parcel.readParcelable(null));
- touchableRegion.set((Rect) parcel.readParcelable(null));
- type = parcel.readInt();
- compatibilityScale = parcel.readFloat();
- visible = (parcel.readInt() == 1);
- displayId = parcel.readInt();
- layer = parcel.readInt();
- }
-
- public static WindowInfo obtain(WindowInfo other) {
- WindowInfo info = obtain();
- info.token = other.token;
- info.frame.set(other.frame);
- info.touchableRegion.set(other.touchableRegion);
- info.type = other.type;
- info.compatibilityScale = other.compatibilityScale;
- info.visible = other.visible;
- info.displayId = other.displayId;
- info.layer = other.layer;
- return info;
- }
-
- public static WindowInfo obtain() {
- synchronized (sPoolLock) {
- if (sPoolSize > 0) {
- WindowInfo info = sPool;
- sPool = info.mNext;
- info.mNext = null;
- info.mInPool = false;
- sPoolSize--;
- return info;
- } else {
- return new WindowInfo();
- }
- }
- }
-
- public void recycle() {
- if (mInPool) {
- throw new IllegalStateException("Already recycled.");
- }
- clear();
- synchronized (sPoolLock) {
- if (sPoolSize < MAX_POOL_SIZE) {
- mNext = sPool;
- sPool = this;
- mInPool = true;
- sPoolSize++;
- }
- }
- }
-
- private void clear() {
- token = null;
- frame.setEmpty();
- touchableRegion.setEmpty();
- type = UNDEFINED;
- compatibilityScale = UNDEFINED;
- visible = false;
- displayId = UNDEFINED;
- layer = UNDEFINED;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("Window [token:").append((token != null) ? token.hashCode() : null);
- builder.append(", displayId:").append(displayId);
- builder.append(", type:").append(type);
- builder.append(", visible:").append(visible);
- builder.append(", layer:").append(layer);
- builder.append(", compatibilityScale:").append(compatibilityScale);
- builder.append(", frame:").append(frame);
- builder.append(", touchableRegion:").append(touchableRegion);
- builder.append("]");
- return builder.toString();
- }
-
- /**
- * @see Parcelable.Creator
- */
- public static final Parcelable.Creator<WindowInfo> CREATOR =
- new Parcelable.Creator<WindowInfo>() {
- public WindowInfo createFromParcel(Parcel parcel) {
- WindowInfo info = WindowInfo.obtain();
- info.initFromParcel(parcel);
- return info;
- }
-
- public WindowInfo[] newArray(int size) {
- return new WindowInfo[size];
- }
- };
-}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 6a67d8b..792188b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -612,10 +612,23 @@ public interface WindowManager extends ViewManager {
/** Window flag: allow window to extend outside of the screen. */
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
- /** Window flag: Hide all screen decorations (e.g. status bar) while
+ /**
+ * Window flag: Hide all screen decorations (e.g. status bar) while
* this window is displayed. This allows the window to use the entire
* display space for itself -- the status bar will be hidden when
- * an app window with this flag set is on the top layer. */
+ * an app window with this flag set is on the top layer.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowFullscreen} attribute; this attribute
+ * is automatically set for you in the standard fullscreen themes
+ * such as {@link android.R.style#Theme_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Black_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Light_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_NoActionBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_Fullscreen},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Fullscreen}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Fullscreen}.</p>
+ */
public static final int FLAG_FULLSCREEN = 0x00000400;
/** Window flag: Override {@link #FLAG_FULLSCREEN and force the
@@ -697,6 +710,17 @@ public interface WindowManager extends ViewManager {
* to actually see the wallpaper behind it; this flag just ensures
* that the wallpaper surface will be there if this window actually
* has translucent regions.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowShowWallpaper} attribute; this attribute
+ * is automatically set for you in the standard wallpaper themes
+ * such as {@link android.R.style#Theme_Wallpaper},
+ * {@link android.R.style#Theme_Wallpaper_NoTitleBar},
+ * {@link android.R.style#Theme_Wallpaper_NoTitleBar_Fullscreen},
+ * {@link android.R.style#Theme_Holo_Wallpaper},
+ * {@link android.R.style#Theme_Holo_Wallpaper_NoTitleBar},
+ * {@link android.R.style#Theme_DeviceDefault_Wallpaper}, and
+ * {@link android.R.style#Theme_DeviceDefault_Wallpaper_NoTitleBar}.</p>
*/
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
@@ -735,20 +759,20 @@ public interface WindowManager extends ViewManager {
/**
* <p>Indicates whether this window should be hardware accelerated.
* Requesting hardware acceleration does not guarantee it will happen.</p>
- *
+ *
* <p>This flag can be controlled programmatically <em>only</em> to enable
* hardware acceleration. To enable hardware acceleration for a given
* window programmatically, do the following:</p>
- *
+ *
* <pre>
* Window w = activity.getWindow(); // in Activity's onCreate() for instance
* w.setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
* WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
* </pre>
- *
+ *
* <p>It is important to remember that this flag <strong>must</strong>
* be set before setting the content view of your activity or dialog.</p>
- *
+ *
* <p>This flag cannot be used to disable hardware acceleration after it
* was enabled in your manifest using
* {@link android.R.attr#hardwareAccelerated}. If you need to selectively
@@ -756,13 +780,48 @@ public interface WindowManager extends ViewManager {
* for instance), make sure it is turned off in your manifest and enable it
* on your activity or dialog when you need it instead, using the method
* described above.</p>
- *
+ *
* <p>This flag is automatically set by the system if the
* {@link android.R.attr#hardwareAccelerated android:hardwareAccelerated}
* XML attribute is set to true on an activity or on the application.</p>
*/
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
+ /**
+ * Window flag: allow window contents to extend in to the screen's
+ * overscan area, if there is one. The window should still correctly
+ * position its contents to take the overscan area into account.
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowOverscan} attribute; this attribute
+ * is automatically set for you in the standard overscan themes
+ * such as {@link android.R.style#Theme_NoTitleBar_Overscan},
+ * {@link android.R.style#Theme_Black_NoTitleBar_Overscan},
+ * {@link android.R.style#Theme_Light_NoTitleBar_Overscan},
+ * {@link android.R.style#Theme_Holo_NoActionBar_Overscan},
+ * {@link android.R.style#Theme_Holo_Light_NoActionBar_Overscan},
+ * {@link android.R.style#Theme_DeviceDefault_NoActionBar_Overscan}, and
+ * {@link android.R.style#Theme_DeviceDefault_Light_NoActionBar_Overscan}.</p>
+ *
+ * <p>When this flag is enabled for a window, its normal content may be obscured
+ * to some degree by the overscan region of the display. To ensure key parts of
+ * that content are visible to the user, you can use
+ * {@link View#setFitsSystemWindows(boolean) View.setFitsSystemWindows(boolean)}
+ * to set the point in the view hierarchy where the appropriate offsets should
+ * be applied. (This can be done either by directly calling this function, using
+ * the {@link android.R.attr#fitsSystemWindows} attribute in your view hierarchy,
+ * or implementing you own {@link View#fitSystemWindows(android.graphics.Rect)
+ * View.fitSystemWindows(Rect)} method).</p>
+ *
+ * <p>This mechanism for positioning content elements is identical to its equivalent
+ * use with layout and {@link View#setSystemUiVisibility(int)
+ * View.setSystemUiVisibility(int)}; here is an example layout that will correctly
+ * position its UI elements with this overscan flag is set:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/layout/overscan_activity.xml complete}
+ */
+ public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
+
// ----- HIDDEN FLAGS.
// These start at the high bit and go down.
@@ -1085,6 +1144,11 @@ public interface WindowManager extends ViewManager {
* {@link #SOFT_INPUT_ADJUST_UNSPECIFIED},
* {@link #SOFT_INPUT_ADJUST_RESIZE}, or
* {@link #SOFT_INPUT_ADJUST_PAN}.
+ * </ul>
+ *
+ *
+ * <p>This flag can be controlled in your theme through the
+ * {@link android.R.attr#windowSoftInputMode} attribute.</p>
*/
public int softInputMode;
@@ -1187,6 +1251,37 @@ public interface WindowManager extends ViewManager {
public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;
/**
+ * Value for {@link #rotationAnimation} to define the animation used to
+ * specify that this window will rotate in or out following a rotation.
+ */
+ public static final int ROTATION_ANIMATION_ROTATE = 0;
+
+ /**
+ * Value for {@link #rotationAnimation} to define the animation used to
+ * specify that this window will fade in or out following a rotation.
+ */
+ public static final int ROTATION_ANIMATION_CROSSFADE = 1;
+
+ /**
+ * Value for {@link #rotationAnimation} to define the animation used to
+ * specify that this window will immediately disappear or appear following
+ * a rotation.
+ */
+ public static final int ROTATION_ANIMATION_JUMPCUT = 2;
+
+ /**
+ * Define the animation used on this window for entry or exit following
+ * a rotation. This only works if the incoming and outgoing topmost
+ * opaque windows have the #FLAG_FULLSCREEN bit set and are not covered
+ * by other windows.
+ *
+ * @see #ROTATION_ANIMATION_ROTATE
+ * @see #ROTATION_ANIMATION_CROSSFADE
+ * @see #ROTATION_ANIMATION_JUMPCUT
+ */
+ public int rotationAnimation = ROTATION_ANIMATION_ROTATE;
+
+ /**
* Identifier for this window. This will usually be filled in for
* you.
*/
@@ -1361,6 +1456,7 @@ public interface WindowManager extends ViewManager {
out.writeFloat(dimAmount);
out.writeFloat(screenBrightness);
out.writeFloat(buttonBrightness);
+ out.writeInt(rotationAnimation);
out.writeStrongBinder(token);
out.writeString(packageName);
TextUtils.writeToParcel(mTitle, out, parcelableFlags);
@@ -1402,6 +1498,7 @@ public interface WindowManager extends ViewManager {
dimAmount = in.readFloat();
screenBrightness = in.readFloat();
buttonBrightness = in.readFloat();
+ rotationAnimation = in.readInt();
token = in.readStrongBinder();
packageName = in.readString();
mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -1426,18 +1523,19 @@ public interface WindowManager extends ViewManager {
public static final int SOFT_INPUT_MODE_CHANGED = 1<<9;
public static final int SCREEN_ORIENTATION_CHANGED = 1<<10;
public static final int SCREEN_BRIGHTNESS_CHANGED = 1<<11;
+ public static final int ROTATION_ANIMATION_CHANGED = 1<<12;
/** {@hide} */
- public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<12;
+ public static final int BUTTON_BRIGHTNESS_CHANGED = 1<<13;
/** {@hide} */
- public static final int SYSTEM_UI_VISIBILITY_CHANGED = 1<<13;
+ public static final int SYSTEM_UI_VISIBILITY_CHANGED = 1<<14;
/** {@hide} */
- public static final int SYSTEM_UI_LISTENER_CHANGED = 1<<14;
+ public static final int SYSTEM_UI_LISTENER_CHANGED = 1<<15;
/** {@hide} */
- public static final int INPUT_FEATURES_CHANGED = 1<<15;
+ public static final int INPUT_FEATURES_CHANGED = 1<<16;
/** {@hide} */
- public static final int PRIVATE_FLAGS_CHANGED = 1<<16;
+ public static final int PRIVATE_FLAGS_CHANGED = 1<<17;
/** {@hide} */
- public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<17;
+ public static final int USER_ACTIVITY_TIMEOUT_CHANGED = 1<<18;
/** {@hide} */
public static final int EVERYTHING_CHANGED = 0xffffffff;
@@ -1537,6 +1635,10 @@ public interface WindowManager extends ViewManager {
buttonBrightness = o.buttonBrightness;
changes |= BUTTON_BRIGHTNESS_CHANGED;
}
+ if (rotationAnimation != o.rotationAnimation) {
+ rotationAnimation = o.rotationAnimation;
+ changes |= ROTATION_ANIMATION_CHANGED;
+ }
if (screenOrientation != o.screenOrientation) {
screenOrientation = o.screenOrientation;
@@ -1639,6 +1741,10 @@ public interface WindowManager extends ViewManager {
sb.append(" bbrt=");
sb.append(buttonBrightness);
}
+ if (rotationAnimation != ROTATION_ANIMATION_ROTATE) {
+ sb.append(" rotAnim=");
+ sb.append(rotationAnimation);
+ }
if ((flags & FLAG_COMPATIBLE_WINDOW) != 0) {
sb.append(" compatible=true");
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index e8945aa..7eb26fa 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -160,6 +160,29 @@ public final class WindowManagerGlobal {
}
}
+ public String[] getViewRootNames() {
+ synchronized (mLock) {
+ if (mRoots == null) return new String[0];
+ String[] mViewRoots = new String[mRoots.length];
+ int i = 0;
+ for (ViewRootImpl root : mRoots) {
+ mViewRoots[i++] = getWindowName(root);
+ }
+ return mViewRoots;
+ }
+ }
+
+ public View getRootView(String name) {
+ synchronized (mLock) {
+ if (mRoots == null) return null;
+ for (ViewRootImpl root : mRoots) {
+ if (name.equals(getWindowName(root))) return root.getView();
+ }
+ }
+
+ return null;
+ }
+
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 26739b3..c0044b6 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -135,6 +135,16 @@ public interface WindowManagerPolicy {
*/
public interface WindowState {
/**
+ * Return the uid of the app that owns this window.
+ */
+ int getOwningUid();
+
+ /**
+ * Return the package name of the app that owns this window.
+ */
+ String getOwningPackage();
+
+ /**
* Perform standard frame computation. The result can be obtained with
* getFrame() if so desired. Must be called with the window manager
* lock held.
@@ -144,6 +154,8 @@ public interface WindowManagerPolicy {
* @param displayFrame The frame of the overall display in which this
* window can appear, used for constraining the overall dimensions
* of the window.
+ * @param overlayFrame The frame within the display that is inside
+ * of the overlay region.
* @param contentFrame The frame within the display in which we would
* like active content to appear. This will cause windows behind to
* be resized to match the given content frame.
@@ -155,7 +167,7 @@ public interface WindowManagerPolicy {
* are visible.
*/
public void computeFrameLw(Rect parentFrame, Rect displayFrame,
- Rect contentFrame, Rect visibleFrame);
+ Rect overlayFrame, Rect contentFrame, Rect visibleFrame);
/**
* Retrieve the current frame of the window that has been assigned by
@@ -183,6 +195,15 @@ public interface WindowManagerPolicy {
public Rect getDisplayFrameLw();
/**
+ * Retrieve the frame of the area inside the overscan region of the
+ * display that this window was last laid out in. Must be called with the
+ * window manager lock held.
+ *
+ * @return Rect The rectangle holding the display overscan frame.
+ */
+ public Rect getOverscanFrameLw();
+
+ /**
* Retrieve the frame of the content area that this window was last
* laid out in. This is the area in which the content of the window
* should be placed. It will be smaller than the display frame to
@@ -401,61 +422,19 @@ public interface WindowManagerPolicy {
public void rebootSafeMode(boolean confirm);
}
- /**
- * Bit mask that is set for all enter transition.
- */
- public final int TRANSIT_ENTER_MASK = 0x1000;
-
- /**
- * Bit mask that is set for all exit transitions.
- */
- public final int TRANSIT_EXIT_MASK = 0x2000;
-
- /** Not set up for a transition. */
- public final int TRANSIT_UNSET = -1;
- /** No animation for transition. */
- public final int TRANSIT_NONE = 0;
/** Window has been added to the screen. */
- public final int TRANSIT_ENTER = 1 | TRANSIT_ENTER_MASK;
+ public static final int TRANSIT_ENTER = 1;
/** Window has been removed from the screen. */
- public final int TRANSIT_EXIT = 2 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_EXIT = 2;
/** Window has been made visible. */
- public final int TRANSIT_SHOW = 3 | TRANSIT_ENTER_MASK;
- /** Window has been made invisible. */
- public final int TRANSIT_HIDE = 4 | TRANSIT_EXIT_MASK;
+ public static final int TRANSIT_SHOW = 3;
+ /** Window has been made invisible.
+ * TODO: Consider removal as this is unused. */
+ public static final int TRANSIT_HIDE = 4;
/** The "application starting" preview window is no longer needed, and will
* animate away to show the real window. */
- public final int TRANSIT_PREVIEW_DONE = 5;
- /** A window in a new activity is being opened on top of an existing one
- * in the same task. */
- public final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK;
- /** The window in the top-most activity is being closed to reveal the
- * previous activity in the same task. */
- public final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK;
- /** A window in a new task is being opened on top of an existing one
- * in another activity's task. */
- public final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK;
- /** A window in the top-most activity is being closed to reveal the
- * previous activity in a different task. */
- public final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK;
- /** A window in an existing task is being displayed on top of an existing one
- * in another activity's task. */
- public final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK;
- /** A window in an existing task is being put below all other tasks. */
- public final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK;
- /** A window in a new activity that doesn't have a wallpaper is being
- * opened on top of one that does, effectively closing the wallpaper. */
- public final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK;
- /** A window in a new activity that does have a wallpaper is being
- * opened on one that didn't, effectively opening the wallpaper. */
- public final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK;
- /** A window in a new activity is being opened on top of an existing one,
- * and both are on top of the wallpaper. */
- public final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK;
- /** The window in the top-most activity is being closed to reveal the
- * previous activity, and both are on top of he wallpaper. */
- public final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
-
+ public static final int TRANSIT_PREVIEW_DONE = 5;
+
// NOTE: screen off reasons are in order of significance, with more
// important ones lower than less important ones.
@@ -490,15 +469,23 @@ public interface WindowManagerPolicy {
public void setInitialDisplaySize(Display display, int width, int height, int density);
/**
+ * Called by window manager to set the overscan region that should be used for the
+ * given display.
+ */
+ public void setDisplayOverscan(Display display, int left, int top, int right, int bottom);
+
+ /**
* Check permissions when adding a window.
*
- * @param attrs The window's LayoutParams.
+ * @param attrs The window's LayoutParams.
+ * @param outAppOp First element will be filled with the app op corresponding to
+ * this window, or OP_NONE.
*
* @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
* else an error code, usually
* {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
*/
- public int checkAddPermission(WindowManager.LayoutParams attrs);
+ public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
/**
* Check permissions when adding a window.
@@ -708,6 +695,31 @@ public interface WindowManagerPolicy {
public int selectAnimationLw(WindowState win, int transit);
/**
+ * Determine the animation to run for a rotation transition based on the
+ * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
+ * and whether it is currently fullscreen and frontmost.
+ *
+ * @param anim The exiting animation resource id is stored in anim[0], the
+ * entering animation resource id is stored in anim[1].
+ */
+ public void selectRotationAnimationLw(int anim[]);
+
+ /**
+ * Validate whether the current top fullscreen has specified the same
+ * {@link WindowManager.LayoutParams#rotationAnimation} value as that
+ * being passed in from the previous top fullscreen window.
+ *
+ * @param exitAnimId exiting resource id from the previous window.
+ * @param enterAnimId entering resource id from the previous window.
+ * @param forceDefault For rotation animations only, if true ignore the
+ * animation values and just return false.
+ * @return true if the previous values are still valid, false if they
+ * should be replaced with the default.
+ */
+ public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+ boolean forceDefault);
+
+ /**
* Create and return an animation to re-display a force hidden window.
*/
public Animation createForceHideEnterAnimation(boolean onWallpaper);
@@ -933,6 +945,7 @@ public interface WindowManagerPolicy {
* @see android.app.KeyguardManager.KeyguardLock#disableKeyguard()
* @see android.app.KeyguardManager.KeyguardLock#reenableKeyguard()
*/
+ @SuppressWarnings("javadoc")
public void enableKeyguard(boolean enabled);
/**
@@ -948,6 +961,7 @@ public interface WindowManagerPolicy {
* @param callback Callback to send the result back.
* @see android.app.KeyguardManager#exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult)
*/
+ @SuppressWarnings("javadoc")
void exitKeyguardSecurely(OnKeyguardExitResult callback);
/**
@@ -1077,6 +1091,16 @@ public interface WindowManagerPolicy {
public void keepScreenOnStoppedLw();
/**
+ * Gets the current user rotation mode.
+ *
+ * @return The rotation mode.
+ *
+ * @see WindowManagerPolicy#USER_ROTATION_LOCKED
+ * @see WindowManagerPolicy#USER_ROTATION_FREE
+ */
+ public int getUserRotationMode();
+
+ /**
* Inform the policy that the user has chosen a preferred orientation ("rotation lock").
*
* @param mode One of {@link WindowManagerPolicy#USER_ROTATION_LOCKED} or
@@ -1112,14 +1136,6 @@ public interface WindowManagerPolicy {
public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
/**
- * Returns whether magnification can be applied to the given window type.
- *
- * @param attrs The window's LayoutParams.
- * @return Whether magnification can be applied.
- */
- public boolean canMagnifyWindowLw(WindowManager.LayoutParams attrs);
-
- /**
* Called when the current user changes. Guaranteed to be called before the broadcast
* of the new user id is made to all listeners.
*
@@ -1142,4 +1158,23 @@ public interface WindowManagerPolicy {
* {@link android.content.Intent#ACTION_ASSIST}
*/
public void showAssistant();
+
+ /**
+ * Returns whether a given window type can be magnified.
+ *
+ * @param windowType The window type.
+ * @return True if the window can be magnified.
+ */
+ public boolean canMagnifyWindow(int windowType);
+
+ /**
+ * Returns whether a given window type is considered a top level one.
+ * A top level window does not have a container, i.e. attached window,
+ * or if it has a container it is laid out as a top-level window, not
+ * as a child of its container.
+ *
+ * @param windowType The window type.
+ * @return True if the window is a top level one.
+ */
+ public boolean isTopLevelWindow(int windowType);
}
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
deleted file mode 100644
index bf77c67..0000000
--- a/core/java/android/view/WindowOrientationListener.java
+++ /dev/null
@@ -1,715 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.SystemProperties;
-import android.util.FloatMath;
-import android.util.Log;
-import android.util.Slog;
-
-/**
- * A special helper class used by the WindowManager
- * for receiving notifications from the SensorManager when
- * the orientation of the device has changed.
- *
- * NOTE: If changing anything here, please run the API demo
- * "App/Activity/Screen Orientation" to ensure that all orientation
- * modes still work correctly.
- *
- * You can also visualize the behavior of the WindowOrientationListener.
- * Refer to frameworks/base/tools/orientationplot/README.txt for details.
- *
- * @hide
- */
-public abstract class WindowOrientationListener {
- private static final String TAG = "WindowOrientationListener";
- private static final boolean LOG = SystemProperties.getBoolean(
- "debug.orientation.log", false);
-
- private static final boolean USE_GRAVITY_SENSOR = false;
-
- private SensorManager mSensorManager;
- private boolean mEnabled;
- private int mRate;
- private Sensor mSensor;
- private SensorEventListenerImpl mSensorEventListener;
- int mCurrentRotation = -1;
-
- /**
- * Creates a new WindowOrientationListener.
- *
- * @param context for the WindowOrientationListener.
- */
- public WindowOrientationListener(Context context) {
- this(context, SensorManager.SENSOR_DELAY_UI);
- }
-
- /**
- * Creates a new WindowOrientationListener.
- *
- * @param context for the WindowOrientationListener.
- * @param rate at which sensor events are processed (see also
- * {@link android.hardware.SensorManager SensorManager}). Use the default
- * value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
- * SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
- *
- * This constructor is private since no one uses it.
- */
- private WindowOrientationListener(Context context, int rate) {
- mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
- mRate = rate;
- mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
- ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
- if (mSensor != null) {
- // Create listener only if sensors do exist
- mSensorEventListener = new SensorEventListenerImpl(this);
- }
- }
-
- /**
- * Enables the WindowOrientationListener so it will monitor the sensor and call
- * {@link #onOrientationChanged} when the device orientation changes.
- */
- public void enable() {
- if (mSensor == null) {
- Log.w(TAG, "Cannot detect sensors. Not enabled");
- return;
- }
- if (mEnabled == false) {
- if (LOG) {
- Log.d(TAG, "WindowOrientationListener enabled");
- }
- mSensorEventListener.reset();
- mSensorManager.registerListener(mSensorEventListener, mSensor, mRate);
- mEnabled = true;
- }
- }
-
- /**
- * Disables the WindowOrientationListener.
- */
- public void disable() {
- if (mSensor == null) {
- Log.w(TAG, "Cannot detect sensors. Invalid disable");
- return;
- }
- if (mEnabled == true) {
- if (LOG) {
- Log.d(TAG, "WindowOrientationListener disabled");
- }
- mSensorManager.unregisterListener(mSensorEventListener);
- mEnabled = false;
- }
- }
-
- /**
- * Sets the current rotation.
- *
- * @param rotation The current rotation.
- */
- public void setCurrentRotation(int rotation) {
- mCurrentRotation = rotation;
- }
-
- /**
- * Gets the proposed rotation.
- *
- * This method only returns a rotation if the orientation listener is certain
- * of its proposal. If the rotation is indeterminate, returns -1.
- *
- * @return The proposed rotation, or -1 if unknown.
- */
- public int getProposedRotation() {
- if (mEnabled) {
- return mSensorEventListener.getProposedRotation();
- }
- return -1;
- }
-
- /**
- * Returns true if sensor is enabled and false otherwise
- */
- public boolean canDetectOrientation() {
- return mSensor != null;
- }
-
- /**
- * Called when the rotation view of the device has changed.
- *
- * This method is called whenever the orientation becomes certain of an orientation.
- * It is called each time the orientation determination transitions from being
- * uncertain to being certain again, even if it is the same orientation as before.
- *
- * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
- * @see Surface
- */
- public abstract void onProposedRotationChanged(int rotation);
-
- /**
- * This class filters the raw accelerometer data and tries to detect actual changes in
- * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
- * but here's the outline:
- *
- * - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in
- * cartesian space because the orientation calculations are sensitive to the
- * absolute magnitude of the acceleration. In particular, there are singularities
- * in the calculation as the magnitude approaches 0. By performing the low-pass
- * filtering early, we can eliminate most spurious high-frequency impulses due to noise.
- *
- * - Convert the acceleromter vector from cartesian to spherical coordinates.
- * Since we're dealing with rotation of the device, this is the sensible coordinate
- * system to work in. The zenith direction is the Z-axis, the direction the screen
- * is facing. The radial distance is referred to as the magnitude below.
- * The elevation angle is referred to as the "tilt" below.
- * The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
- * the Y-axis).
- * See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
- *
- * - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
- * The orientation angle is not meaningful when the device is nearly horizontal.
- * The tilt angle thresholds are set differently for each orientation and different
- * limits are applied when the device is facing down as opposed to when it is facing
- * forward or facing up.
- *
- * - When the orientation angle reaches a certain threshold, consider transitioning
- * to the corresponding orientation. These thresholds have some hysteresis built-in
- * to avoid oscillations between adjacent orientations.
- *
- * - Wait for the device to settle for a little bit. Once that happens, issue the
- * new orientation proposal.
- *
- * Details are explained inline.
- *
- * See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
- * signal processing background.
- */
- static final class SensorEventListenerImpl implements SensorEventListener {
- // We work with all angles in degrees in this class.
- private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
-
- // Number of nanoseconds per millisecond.
- private static final long NANOS_PER_MS = 1000000;
-
- // Indices into SensorEvent.values for the accelerometer sensor.
- private static final int ACCELEROMETER_DATA_X = 0;
- private static final int ACCELEROMETER_DATA_Y = 1;
- private static final int ACCELEROMETER_DATA_Z = 2;
-
- private final WindowOrientationListener mOrientationListener;
-
- // The minimum amount of time that a predicted rotation must be stable before it
- // is accepted as a valid rotation proposal. This value can be quite small because
- // the low-pass filter already suppresses most of the noise so we're really just
- // looking for quick confirmation that the last few samples are in agreement as to
- // the desired orientation.
- private static final long PROPOSAL_SETTLE_TIME_NANOS = 40 * NANOS_PER_MS;
-
- // The minimum amount of time that must have elapsed since the device last exited
- // the flat state (time since it was picked up) before the proposed rotation
- // can change.
- private static final long PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS = 500 * NANOS_PER_MS;
-
- // The minimum amount of time that must have elapsed since the device stopped
- // swinging (time since device appeared to be in the process of being put down
- // or put away into a pocket) before the proposed rotation can change.
- private static final long PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS = 300 * NANOS_PER_MS;
-
- // The minimum amount of time that must have elapsed since the device stopped
- // undergoing external acceleration before the proposed rotation can change.
- private static final long PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS =
- 500 * NANOS_PER_MS;
-
- // If the tilt angle remains greater than the specified angle for a minimum of
- // the specified time, then the device is deemed to be lying flat
- // (just chillin' on a table).
- private static final float FLAT_ANGLE = 75;
- private static final long FLAT_TIME_NANOS = 1000 * NANOS_PER_MS;
-
- // If the tilt angle has increased by at least delta degrees within the specified amount
- // of time, then the device is deemed to be swinging away from the user
- // down towards flat (tilt = 90).
- private static final float SWING_AWAY_ANGLE_DELTA = 20;
- private static final long SWING_TIME_NANOS = 300 * NANOS_PER_MS;
-
- // The maximum sample inter-arrival time in milliseconds.
- // If the acceleration samples are further apart than this amount in time, we reset the
- // state of the low-pass filter and orientation properties. This helps to handle
- // boundary conditions when the device is turned on, wakes from suspend or there is
- // a significant gap in samples.
- private static final long MAX_FILTER_DELTA_TIME_NANOS = 1000 * NANOS_PER_MS;
-
- // The acceleration filter time constant.
- //
- // This time constant is used to tune the acceleration filter such that
- // impulses and vibrational noise (think car dock) is suppressed before we
- // try to calculate the tilt and orientation angles.
- //
- // The filter time constant is related to the filter cutoff frequency, which is the
- // frequency at which signals are attenuated by 3dB (half the passband power).
- // Each successive octave beyond this frequency is attenuated by an additional 6dB.
- //
- // Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
- // is given by Fc = 1 / (2pi * t).
- //
- // The higher the time constant, the lower the cutoff frequency, so more noise
- // will be suppressed.
- //
- // Filtering adds latency proportional the time constant (inversely proportional
- // to the cutoff frequency) so we don't want to make the time constant too
- // large or we can lose responsiveness. Likewise we don't want to make it too
- // small or we do a poor job suppressing acceleration spikes.
- // Empirically, 100ms seems to be too small and 500ms is too large.
- private static final float FILTER_TIME_CONSTANT_MS = 200.0f;
-
- /* State for orientation detection. */
-
- // Thresholds for minimum and maximum allowable deviation from gravity.
- //
- // If the device is undergoing external acceleration (being bumped, in a car
- // that is turning around a corner or a plane taking off) then the magnitude
- // may be substantially more or less than gravity. This can skew our orientation
- // detection by making us think that up is pointed in a different direction.
- //
- // Conversely, if the device is in freefall, then there will be no gravity to
- // measure at all. This is problematic because we cannot detect the orientation
- // without gravity to tell us which way is up. A magnitude near 0 produces
- // singularities in the tilt and orientation calculations.
- //
- // In both cases, we postpone choosing an orientation.
- //
- // However, we need to tolerate some acceleration because the angular momentum
- // of turning the device can skew the observed acceleration for a short period of time.
- private static final float NEAR_ZERO_MAGNITUDE = 1; // m/s^2
- private static final float ACCELERATION_TOLERANCE = 4; // m/s^2
- private static final float MIN_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY - ACCELERATION_TOLERANCE;
- private static final float MAX_ACCELERATION_MAGNITUDE =
- SensorManager.STANDARD_GRAVITY + ACCELERATION_TOLERANCE;
-
- // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
- // when screen is facing the sky or ground), we completely ignore orientation data.
- private static final int MAX_TILT = 75;
-
- // The tilt angle range in degrees for each orientation.
- // Beyond these tilt angles, we don't even consider transitioning into the
- // specified orientation. We place more stringent requirements on unnatural
- // orientations than natural ones to make it less likely to accidentally transition
- // into those states.
- // The first value of each pair is negative so it applies a limit when the device is
- // facing down (overhead reading in bed).
- // The second value of each pair is positive so it applies a limit when the device is
- // facing up (resting on a table).
- // The ideal tilt angle is 0 (when the device is vertical) so the limits establish
- // how close to vertical the device must be in order to change orientation.
- private static final int[][] TILT_TOLERANCE = new int[][] {
- /* ROTATION_0 */ { -25, 70 },
- /* ROTATION_90 */ { -25, 65 },
- /* ROTATION_180 */ { -25, 60 },
- /* ROTATION_270 */ { -25, 65 }
- };
-
- // The gap angle in degrees between adjacent orientation angles for hysteresis.
- // This creates a "dead zone" between the current orientation and a proposed
- // adjacent orientation. No orientation proposal is made when the orientation
- // angle is within the gap between the current orientation and the adjacent
- // orientation.
- private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 45;
-
- // Timestamp and value of the last accelerometer sample.
- private long mLastFilteredTimestampNanos;
- private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
-
- // The last proposed rotation, -1 if unknown.
- private int mProposedRotation;
-
- // Value of the current predicted rotation, -1 if unknown.
- private int mPredictedRotation;
-
- // Timestamp of when the predicted rotation most recently changed.
- private long mPredictedRotationTimestampNanos;
-
- // Timestamp when the device last appeared to be flat for sure (the flat delay elapsed).
- private long mFlatTimestampNanos;
-
- // Timestamp when the device last appeared to be swinging.
- private long mSwingTimestampNanos;
-
- // Timestamp when the device last appeared to be undergoing external acceleration.
- private long mAccelerationTimestampNanos;
-
- // History of observed tilt angles.
- private static final int TILT_HISTORY_SIZE = 40;
- private float[] mTiltHistory = new float[TILT_HISTORY_SIZE];
- private long[] mTiltHistoryTimestampNanos = new long[TILT_HISTORY_SIZE];
- private int mTiltHistoryIndex;
-
- public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
- mOrientationListener = orientationListener;
- reset();
- }
-
- public int getProposedRotation() {
- return mProposedRotation;
- }
-
- @Override
- public void onAccuracyChanged(Sensor sensor, int accuracy) {
- }
-
- @Override
- public void onSensorChanged(SensorEvent event) {
- // The vector given in the SensorEvent points straight up (towards the sky) under ideal
- // conditions (the phone is not accelerating). I'll call this up vector elsewhere.
- float x = event.values[ACCELEROMETER_DATA_X];
- float y = event.values[ACCELEROMETER_DATA_Y];
- float z = event.values[ACCELEROMETER_DATA_Z];
-
- if (LOG) {
- Slog.v(TAG, "Raw acceleration vector: "
- + "x=" + x + ", y=" + y + ", z=" + z
- + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
- }
-
- // Apply a low-pass filter to the acceleration up vector in cartesian space.
- // Reset the orientation listener state if the samples are too far apart in time
- // or when we see values of (0, 0, 0) which indicates that we polled the
- // accelerometer too soon after turning it on and we don't have any data yet.
- final long now = event.timestamp;
- final long then = mLastFilteredTimestampNanos;
- final float timeDeltaMS = (now - then) * 0.000001f;
- final boolean skipSample;
- if (now < then
- || now > then + MAX_FILTER_DELTA_TIME_NANOS
- || (x == 0 && y == 0 && z == 0)) {
- if (LOG) {
- Slog.v(TAG, "Resetting orientation listener.");
- }
- reset();
- skipSample = true;
- } else {
- final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
- x = alpha * (x - mLastFilteredX) + mLastFilteredX;
- y = alpha * (y - mLastFilteredY) + mLastFilteredY;
- z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
- if (LOG) {
- Slog.v(TAG, "Filtered acceleration vector: "
- + "x=" + x + ", y=" + y + ", z=" + z
- + ", magnitude=" + FloatMath.sqrt(x * x + y * y + z * z));
- }
- skipSample = false;
- }
- mLastFilteredTimestampNanos = now;
- mLastFilteredX = x;
- mLastFilteredY = y;
- mLastFilteredZ = z;
-
- boolean isAccelerating = false;
- boolean isFlat = false;
- boolean isSwinging = false;
- if (!skipSample) {
- // Calculate the magnitude of the acceleration vector.
- final float magnitude = FloatMath.sqrt(x * x + y * y + z * z);
- if (magnitude < NEAR_ZERO_MAGNITUDE) {
- if (LOG) {
- Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero.");
- }
- clearPredictedRotation();
- } else {
- // Determine whether the device appears to be undergoing external acceleration.
- if (isAccelerating(magnitude)) {
- isAccelerating = true;
- mAccelerationTimestampNanos = now;
- }
-
- // Calculate the tilt angle.
- // This is the angle between the up vector and the x-y plane (the plane of
- // the screen) in a range of [-90, 90] degrees.
- // -90 degrees: screen horizontal and facing the ground (overhead)
- // 0 degrees: screen vertical
- // 90 degrees: screen horizontal and facing the sky (on table)
- final int tiltAngle = (int) Math.round(
- Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
- addTiltHistoryEntry(now, tiltAngle);
-
- // Determine whether the device appears to be flat or swinging.
- if (isFlat(now)) {
- isFlat = true;
- mFlatTimestampNanos = now;
- }
- if (isSwinging(now, tiltAngle)) {
- isSwinging = true;
- mSwingTimestampNanos = now;
- }
-
- // If the tilt angle is too close to horizontal then we cannot determine
- // the orientation angle of the screen.
- if (Math.abs(tiltAngle) > MAX_TILT) {
- if (LOG) {
- Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
- + "tiltAngle=" + tiltAngle);
- }
- clearPredictedRotation();
- } else {
- // Calculate the orientation angle.
- // This is the angle between the x-y projection of the up vector onto
- // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
- int orientationAngle = (int) Math.round(
- -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
- if (orientationAngle < 0) {
- // atan2 returns [-180, 180]; normalize to [0, 360]
- orientationAngle += 360;
- }
-
- // Find the nearest rotation.
- int nearestRotation = (orientationAngle + 45) / 90;
- if (nearestRotation == 4) {
- nearestRotation = 0;
- }
-
- // Determine the predicted orientation.
- if (isTiltAngleAcceptable(nearestRotation, tiltAngle)
- && isOrientationAngleAcceptable(nearestRotation,
- orientationAngle)) {
- updatePredictedRotation(now, nearestRotation);
- if (LOG) {
- Slog.v(TAG, "Predicted: "
- + "tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle
- + ", predictedRotation=" + mPredictedRotation
- + ", predictedRotationAgeMS="
- + ((now - mPredictedRotationTimestampNanos)
- * 0.000001f));
- }
- } else {
- if (LOG) {
- Slog.v(TAG, "Ignoring sensor data, no predicted rotation: "
- + "tiltAngle=" + tiltAngle
- + ", orientationAngle=" + orientationAngle);
- }
- clearPredictedRotation();
- }
- }
- }
- }
-
- // Determine new proposed rotation.
- final int oldProposedRotation = mProposedRotation;
- if (mPredictedRotation < 0 || isPredictedRotationAcceptable(now)) {
- mProposedRotation = mPredictedRotation;
- }
-
- // Write final statistics about where we are in the orientation detection process.
- if (LOG) {
- Slog.v(TAG, "Result: currentRotation=" + mOrientationListener.mCurrentRotation
- + ", proposedRotation=" + mProposedRotation
- + ", predictedRotation=" + mPredictedRotation
- + ", timeDeltaMS=" + timeDeltaMS
- + ", isAccelerating=" + isAccelerating
- + ", isFlat=" + isFlat
- + ", isSwinging=" + isSwinging
- + ", timeUntilSettledMS=" + remainingMS(now,
- mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS)
- + ", timeUntilAccelerationDelayExpiredMS=" + remainingMS(now,
- mAccelerationTimestampNanos + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS)
- + ", timeUntilFlatDelayExpiredMS=" + remainingMS(now,
- mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS)
- + ", timeUntilSwingDelayExpiredMS=" + remainingMS(now,
- mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
- }
-
- // Tell the listener.
- if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
- if (LOG) {
- Slog.v(TAG, "Proposed rotation changed! proposedRotation=" + mProposedRotation
- + ", oldProposedRotation=" + oldProposedRotation);
- }
- mOrientationListener.onProposedRotationChanged(mProposedRotation);
- }
- }
-
- /**
- * Returns true if the tilt angle is acceptable for a given predicted rotation.
- */
- private boolean isTiltAngleAcceptable(int rotation, int tiltAngle) {
- return tiltAngle >= TILT_TOLERANCE[rotation][0]
- && tiltAngle <= TILT_TOLERANCE[rotation][1];
- }
-
- /**
- * Returns true if the orientation angle is acceptable for a given predicted rotation.
- *
- * This function takes into account the gap between adjacent orientations
- * for hysteresis.
- */
- private boolean isOrientationAngleAcceptable(int rotation, int orientationAngle) {
- // If there is no current rotation, then there is no gap.
- // The gap is used only to introduce hysteresis among advertised orientation
- // changes to avoid flapping.
- final int currentRotation = mOrientationListener.mCurrentRotation;
- if (currentRotation >= 0) {
- // If the specified rotation is the same or is counter-clockwise adjacent
- // to the current rotation, then we set a lower bound on the orientation angle.
- // For example, if currentRotation is ROTATION_0 and proposed is ROTATION_90,
- // then we want to check orientationAngle > 45 + GAP / 2.
- if (rotation == currentRotation
- || rotation == (currentRotation + 1) % 4) {
- int lowerBound = rotation * 90 - 45
- + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (rotation == 0) {
- if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
- return false;
- }
- } else {
- if (orientationAngle < lowerBound) {
- return false;
- }
- }
- }
-
- // If the specified rotation is the same or is clockwise adjacent,
- // then we set an upper bound on the orientation angle.
- // For example, if currentRotation is ROTATION_0 and rotation is ROTATION_270,
- // then we want to check orientationAngle < 315 - GAP / 2.
- if (rotation == currentRotation
- || rotation == (currentRotation + 3) % 4) {
- int upperBound = rotation * 90 + 45
- - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
- if (rotation == 0) {
- if (orientationAngle <= 45 && orientationAngle > upperBound) {
- return false;
- }
- } else {
- if (orientationAngle > upperBound) {
- return false;
- }
- }
- }
- }
- return true;
- }
-
- /**
- * Returns true if the predicted rotation is ready to be advertised as a
- * proposed rotation.
- */
- private boolean isPredictedRotationAcceptable(long now) {
- // The predicted rotation must have settled long enough.
- if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
- return false;
- }
-
- // The last flat state (time since picked up) must have been sufficiently long ago.
- if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
- return false;
- }
-
- // The last swing state (time since last movement to put down) must have been
- // sufficiently long ago.
- if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
- return false;
- }
-
- // The last acceleration state must have been sufficiently long ago.
- if (now < mAccelerationTimestampNanos
- + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
- return false;
- }
-
- // Looks good!
- return true;
- }
-
- private void reset() {
- mLastFilteredTimestampNanos = Long.MIN_VALUE;
- mProposedRotation = -1;
- mFlatTimestampNanos = Long.MIN_VALUE;
- mSwingTimestampNanos = Long.MIN_VALUE;
- mAccelerationTimestampNanos = Long.MIN_VALUE;
- clearPredictedRotation();
- clearTiltHistory();
- }
-
- private void clearPredictedRotation() {
- mPredictedRotation = -1;
- mPredictedRotationTimestampNanos = Long.MIN_VALUE;
- }
-
- private void updatePredictedRotation(long now, int rotation) {
- if (mPredictedRotation != rotation) {
- mPredictedRotation = rotation;
- mPredictedRotationTimestampNanos = now;
- }
- }
-
- private boolean isAccelerating(float magnitude) {
- return magnitude < MIN_ACCELERATION_MAGNITUDE
- || magnitude > MAX_ACCELERATION_MAGNITUDE;
- }
-
- private void clearTiltHistory() {
- mTiltHistoryTimestampNanos[0] = Long.MIN_VALUE;
- mTiltHistoryIndex = 1;
- }
-
- private void addTiltHistoryEntry(long now, float tilt) {
- mTiltHistory[mTiltHistoryIndex] = tilt;
- mTiltHistoryTimestampNanos[mTiltHistoryIndex] = now;
- mTiltHistoryIndex = (mTiltHistoryIndex + 1) % TILT_HISTORY_SIZE;
- mTiltHistoryTimestampNanos[mTiltHistoryIndex] = Long.MIN_VALUE;
- }
-
- private boolean isFlat(long now) {
- for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
- if (mTiltHistory[i] < FLAT_ANGLE) {
- break;
- }
- if (mTiltHistoryTimestampNanos[i] + FLAT_TIME_NANOS <= now) {
- // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
- return true;
- }
- }
- return false;
- }
-
- private boolean isSwinging(long now, float tilt) {
- for (int i = mTiltHistoryIndex; (i = nextTiltHistoryIndex(i)) >= 0; ) {
- if (mTiltHistoryTimestampNanos[i] + SWING_TIME_NANOS < now) {
- break;
- }
- if (mTiltHistory[i] + SWING_AWAY_ANGLE_DELTA <= tilt) {
- // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
- return true;
- }
- }
- return false;
- }
-
- private int nextTiltHistoryIndex(int index) {
- index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
- return mTiltHistoryTimestampNanos[index] != Long.MIN_VALUE ? index : -1;
- }
-
- private static float remainingMS(long now, long until) {
- return now >= until ? 0 : (until - now) * 0.000001f;
- }
- }
-}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 1500905..9603fe5 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -19,6 +19,7 @@ package android.view.accessibility;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import android.util.Pools.SynchronizedPool;
import java.util.ArrayList;
import java.util.List;
@@ -686,11 +687,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
public static final int TYPES_ALL_MASK = 0xFFFFFFFF;
private static final int MAX_POOL_SIZE = 10;
- private static final Object sPoolLock = new Object();
- private static AccessibilityEvent sPool;
- private static int sPoolSize;
- private AccessibilityEvent mNext;
- private boolean mIsInPool;
+ private static final SynchronizedPool<AccessibilityEvent> sPool =
+ new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE);
private int mEventType;
private CharSequence mPackageName;
@@ -916,17 +914,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @return An instance.
*/
public static AccessibilityEvent obtain() {
- synchronized (sPoolLock) {
- if (sPool != null) {
- AccessibilityEvent event = sPool;
- sPool = sPool.mNext;
- sPoolSize--;
- event.mNext = null;
- event.mIsInPool = false;
- return event;
- }
- return new AccessibilityEvent();
- }
+ AccessibilityEvent event = sPool.acquire();
+ return (event != null) ? event : new AccessibilityEvent();
}
/**
@@ -939,18 +928,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
*/
@Override
public void recycle() {
- if (mIsInPool) {
- throw new IllegalStateException("Event already recycled!");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize <= MAX_POOL_SIZE) {
- mNext = sPool;
- sPool = this;
- mIsInPool = true;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
/**
@@ -1137,54 +1116,176 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
* @return The string representation.
*/
public static String eventTypeToString(int eventType) {
- switch (eventType) {
- case TYPE_VIEW_CLICKED:
- return "TYPE_VIEW_CLICKED";
- case TYPE_VIEW_LONG_CLICKED:
- return "TYPE_VIEW_LONG_CLICKED";
- case TYPE_VIEW_SELECTED:
- return "TYPE_VIEW_SELECTED";
- case TYPE_VIEW_FOCUSED:
- return "TYPE_VIEW_FOCUSED";
- case TYPE_VIEW_TEXT_CHANGED:
- return "TYPE_VIEW_TEXT_CHANGED";
- case TYPE_WINDOW_STATE_CHANGED:
- return "TYPE_WINDOW_STATE_CHANGED";
- case TYPE_VIEW_HOVER_ENTER:
- return "TYPE_VIEW_HOVER_ENTER";
- case TYPE_VIEW_HOVER_EXIT:
- return "TYPE_VIEW_HOVER_EXIT";
- case TYPE_NOTIFICATION_STATE_CHANGED:
- return "TYPE_NOTIFICATION_STATE_CHANGED";
- case TYPE_TOUCH_EXPLORATION_GESTURE_START:
- return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
- case TYPE_TOUCH_EXPLORATION_GESTURE_END:
- return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
- case TYPE_WINDOW_CONTENT_CHANGED:
- return "TYPE_WINDOW_CONTENT_CHANGED";
- case TYPE_VIEW_TEXT_SELECTION_CHANGED:
- return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
- case TYPE_VIEW_SCROLLED:
- return "TYPE_VIEW_SCROLLED";
- case TYPE_ANNOUNCEMENT:
- return "TYPE_ANNOUNCEMENT";
- case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
- return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
- case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
- return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
- case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY:
- return "TYPE_CURRENT_AT_GRANULARITY_MOVEMENT_CHANGED";
- case TYPE_GESTURE_DETECTION_START:
- return "TYPE_GESTURE_DETECTION_START";
- case TYPE_GESTURE_DETECTION_END:
- return "TYPE_GESTURE_DETECTION_END";
- case TYPE_TOUCH_INTERACTION_START:
- return "TYPE_TOUCH_INTERACTION_START";
- case TYPE_TOUCH_INTERACTION_END:
- return "TYPE_TOUCH_INTERACTION_END";
- default:
- return null;
+ if (eventType == TYPES_ALL_MASK) {
+ return "TYPES_ALL_MASK";
}
+ StringBuilder builder = new StringBuilder();
+ int eventTypeCount = 0;
+ while (eventType != 0) {
+ final int eventTypeFlag = 1 << Integer.numberOfTrailingZeros(eventType);
+ eventType &= ~eventTypeFlag;
+ switch (eventTypeFlag) {
+ case TYPE_VIEW_CLICKED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_CLICKED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_LONG_CLICKED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_LONG_CLICKED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_SELECTED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_SELECTED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_FOCUSED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_FOCUSED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_TEXT_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_TEXT_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_WINDOW_STATE_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_WINDOW_STATE_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_HOVER_ENTER: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_HOVER_ENTER");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_HOVER_EXIT: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_HOVER_EXIT");
+ eventTypeCount++;
+ } break;
+ case TYPE_NOTIFICATION_STATE_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_NOTIFICATION_STATE_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_EXPLORATION_GESTURE_START: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_START");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_EXPLORATION_GESTURE_END: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_EXPLORATION_GESTURE_END");
+ eventTypeCount++;
+ } break;
+ case TYPE_WINDOW_CONTENT_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_WINDOW_CONTENT_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_TEXT_SELECTION_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_SCROLLED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_SCROLLED");
+ eventTypeCount++;
+ } break;
+ case TYPE_ANNOUNCEMENT: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_ANNOUNCEMENT");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUSED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED");
+ eventTypeCount++;
+ } break;
+ case TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_CURRENT_AT_GRANULARITY_MOVEMENT_CHANGED");
+ eventTypeCount++;
+ } break;
+ case TYPE_GESTURE_DETECTION_START: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_GESTURE_DETECTION_START");
+ eventTypeCount++;
+ } break;
+ case TYPE_GESTURE_DETECTION_END: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_GESTURE_DETECTION_END");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_INTERACTION_START: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_INTERACTION_START");
+ eventTypeCount++;
+ } break;
+ case TYPE_TOUCH_INTERACTION_END: {
+ if (eventTypeCount > 0) {
+ builder.append(", ");
+ }
+ builder.append("TYPE_TOUCH_INTERACTION_END");
+ eventTypeCount++;
+ } break;
+ }
+ }
+ if (eventTypeCount > 1) {
+ builder.insert(0, '[');
+ builder.append(']');
+ }
+ return builder.toString();
}
/**
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 20b5f17..84d7e72 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,7 +17,6 @@
package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.graphics.Rect;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -102,8 +101,6 @@ public final class AccessibilityInteractionClient
private Message mSameThreadMessage;
- private final Rect mTempBounds = new Rect();
-
// The connection cache is shared between all interrogating threads.
private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
new SparseArray<IAccessibilityServiceConnection>();
@@ -194,14 +191,14 @@ public final class AccessibilityInteractionClient
return cachedInfo;
}
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
+ final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
prefetchFlags, Thread.currentThread().getId());
// If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
if (infos != null && !infos.isEmpty()) {
return infos.get(0);
}
@@ -233,25 +230,25 @@ public final class AccessibilityInteractionClient
* where to start the search. Use
* {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
* to start from the root.
- * @param viewId The id of the view.
- * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise.
*/
- public AccessibilityNodeInfo findAccessibilityNodeInfoByViewId(int connectionId,
- int accessibilityWindowId, long accessibilityNodeId, int viewId) {
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId,
+ int accessibilityWindowId, long accessibilityNodeId, String viewId) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale =
- connection.findAccessibilityNodeInfoByViewId(accessibilityWindowId,
- accessibilityNodeId, viewId, interactionId, this,
- Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
- AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ final boolean success = connection.findAccessibilityNodeInfosByViewId(
+ accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this,
+ Thread.currentThread().getId());
+ if (success) {
+ List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
- return info;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
@@ -264,7 +261,7 @@ public final class AccessibilityInteractionClient
+ " findAccessibilityNodeInfoByViewIdInActiveWindow", re);
}
}
- return null;
+ return Collections.emptyList();
}
/**
@@ -290,15 +287,16 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.findAccessibilityNodeInfosByText(
+ final boolean success = connection.findAccessibilityNodeInfosByText(
accessibilityWindowId, accessibilityNodeId, text, interactionId, this,
Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, windowScale);
- return infos;
+ if (infos != null) {
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ return infos;
+ }
}
} else {
if (DEBUG) {
@@ -336,14 +334,13 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.findFocus(accessibilityWindowId,
+ final boolean success = connection.findFocus(accessibilityWindowId,
accessibilityNodeId, focusType, interactionId, this,
Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
return info;
}
} else {
@@ -381,14 +378,13 @@ public final class AccessibilityInteractionClient
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
- final float windowScale = connection.focusSearch(accessibilityWindowId,
+ final boolean success = connection.focusSearch(accessibilityWindowId,
accessibilityNodeId, direction, interactionId, this,
Thread.currentThread().getId());
- // If the scale is zero the call has failed.
- if (windowScale > 0) {
+ if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
return info;
}
} else {
@@ -604,36 +600,14 @@ public final class AccessibilityInteractionClient
}
/**
- * Applies compatibility scale to the info bounds if it is not equal to one.
- *
- * @param info The info whose bounds to scale.
- * @param scale The scale to apply.
- */
- private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info, float scale) {
- if (scale == 1.0f) {
- return;
- }
- Rect bounds = mTempBounds;
- info.getBoundsInParent(bounds);
- bounds.scale(scale);
- info.setBoundsInParent(bounds);
-
- info.getBoundsInScreen(bounds);
- bounds.scale(scale);
- info.setBoundsInScreen(bounds);
- }
-
- /**
* Finalize an {@link AccessibilityNodeInfo} before passing it to the client.
*
* @param info The info.
* @param connectionId The id of the connection to the system.
- * @param windowScale The source window compatibility scale.
*/
- private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, int connectionId,
- float windowScale) {
+ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
+ int connectionId) {
if (info != null) {
- applyCompatibilityScaleIfNeeded(info, windowScale);
info.setConnectionId(connectionId);
info.setSealed(true);
sAccessibilityNodeInfoCache.add(info);
@@ -645,15 +619,14 @@ public final class AccessibilityInteractionClient
*
* @param infos The {@link AccessibilityNodeInfo}s.
* @param connectionId The id of the connection to the system.
- * @param windowScale The source window compatibility scale.
*/
private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
- int connectionId, float windowScale) {
+ int connectionId) {
if (infos != null) {
final int infosCount = infos.size();
for (int i = 0; i < infosCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
}
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 1dc2487..ad87fcb 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,10 +16,12 @@
package android.view.accessibility;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Pools.SynchronizedPool;
import android.util.SparseLongArray;
import android.view.View;
@@ -77,7 +79,10 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
/** @hide */
- public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+ public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
+
+ /** @hide */
+ public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
// Actions.
@@ -126,16 +131,22 @@ public class AccessibilityNodeInfo implements Parcelable {
* at a given movement granularity. For example, move to the next character,
* word, etc.
* <p>
- * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
- * <strong>Example:</strong>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the previous character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
* info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
* </code></pre></p>
* </p>
*
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
@@ -152,17 +163,23 @@ public class AccessibilityNodeInfo implements Parcelable {
* at a given movement granularity. For example, move to the next character,
* word, etc.
* <p>
- * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
- * <strong>Example:</strong>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
+ * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
+ * <strong>Example:</strong> Move to the next character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
+ * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
+ * false);
* info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
* arguments);
* </code></pre></p>
* </p>
*
+ * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
+ * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
+ *
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
@@ -215,15 +232,53 @@ public class AccessibilityNodeInfo implements Parcelable {
public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
/**
+ * Action to copy the current selection to the clipboard.
+ */
+ public static final int ACTION_COPY = 0x00004000;
+
+ /**
+ * Action to paste the current clipboard content.
+ */
+ public static final int ACTION_PASTE = 0x00008000;
+
+ /**
+ * Action to cut the current selection and place it to the clipboard.
+ */
+ public static final int ACTION_CUT = 0x00010000;
+
+ /**
+ * Action to set the selection. Performing this action with no arguments
+ * clears the selection.
+ * <p>
+ * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SELECTION_START_INT},
+ * {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
+ * <strong>Example:</strong>
+ * <code><pre><p>
+ * Bundle arguments = new Bundle();
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
+ * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
+ * info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
+ * </code></pre></p>
+ * </p>
+ *
+ * @see #ACTION_ARGUMENT_SELECTION_START_INT
+ * @see #ACTION_ARGUMENT_SELECTION_END_INT
+ */
+ public static final int ACTION_SET_SELECTION = 0x00020000;
+
+ /**
* Argument for which movement granularity to be used when traversing the node text.
* <p>
* <strong>Type:</strong> int<br>
* <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
* {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
* </p>
+ *
+ * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
*/
public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
- "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+ "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
/**
* Argument for which HTML element to get moving to the next/previous HTML element.
@@ -232,9 +287,51 @@ public class AccessibilityNodeInfo implements Parcelable {
* <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT},
* {@link #ACTION_PREVIOUS_HTML_ELEMENT}
* </p>
+ *
+ * @see #ACTION_NEXT_HTML_ELEMENT
+ * @see #ACTION_PREVIOUS_HTML_ELEMENT
*/
public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
- "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+ "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+
+ /**
+ * Argument for whether when moving at granularity to extend the selection
+ * or to move it otherwise.
+ * <p>
+ * <strong>Type:</strong> boolean<br>
+ * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
+ * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
+ * </p>
+ *
+ * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ */
+ public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN =
+ "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+
+ /**
+ * Argument for specifying the selection start.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
+ * </p>
+ *
+ * @see #ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_START_INT =
+ "ACTION_ARGUMENT_SELECTION_START_INT";
+
+ /**
+ * Argument for specifying the selection end.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
+ * </p>
+ *
+ * @see #ACTION_SET_SELECTION
+ */
+ public static final String ACTION_ARGUMENT_SELECTION_END_INT =
+ "ACTION_ARGUMENT_SELECTION_END_INT";
/**
* The input focus.
@@ -275,29 +372,31 @@ public class AccessibilityNodeInfo implements Parcelable {
// Boolean attributes.
- private static final int PROPERTY_CHECKABLE = 0x00000001;
+ private static final int BOOLEAN_PROPERTY_CHECKABLE = 0x00000001;
+
+ private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
- private static final int PROPERTY_CHECKED = 0x00000002;
+ private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
- private static final int PROPERTY_FOCUSABLE = 0x00000004;
+ private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
- private static final int PROPERTY_FOCUSED = 0x00000008;
+ private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
- private static final int PROPERTY_SELECTED = 0x00000010;
+ private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
- private static final int PROPERTY_CLICKABLE = 0x00000020;
+ private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
- private static final int PROPERTY_LONG_CLICKABLE = 0x00000040;
+ private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
- private static final int PROPERTY_ENABLED = 0x00000080;
+ private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
- private static final int PROPERTY_PASSWORD = 0x00000100;
+ private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
- private static final int PROPERTY_SCROLLABLE = 0x00000200;
+ private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
- private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+ private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
- private static final int PROPERTY_VISIBLE_TO_USER = 0x00000800;
+ private static final int BOOLEAN_PROPERTY_EDITABLE = 0x00001000;
/**
* Bits that provide the id of a virtual descendant of a view.
@@ -354,11 +453,9 @@ public class AccessibilityNodeInfo implements Parcelable {
// Housekeeping.
private static final int MAX_POOL_SIZE = 50;
- private static final Object sPoolLock = new Object();
- private static AccessibilityNodeInfo sPool;
- private static int sPoolSize;
- private AccessibilityNodeInfo mNext;
- private boolean mIsInPool;
+ private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
+ new SynchronizedPool<AccessibilityNodeInfo>(MAX_POOL_SIZE);
+
private boolean mSealed;
// Data.
@@ -376,12 +473,16 @@ public class AccessibilityNodeInfo implements Parcelable {
private CharSequence mClassName;
private CharSequence mText;
private CharSequence mContentDescription;
+ private CharSequence mViewIdResourceName;
private final SparseLongArray mChildNodeIds = new SparseLongArray();
private int mActions;
private int mMovementGranularities;
+ private int mTextSelectionStart = UNDEFINED;
+ private int mTextSelectionEnd = UNDEFINED;
+
private int mConnectionId = UNDEFINED;
/**
@@ -487,6 +588,31 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Refreshes this info with the latest state of the view it represents.
+ * <p>
+ * <strong>Note:</strong> If this method returns false this info is obsolete
+ * since it represents a view that is no longer in the view tree and should
+ * be recycled.
+ * </p>
+ * @return Whether the refresh succeeded.
+ */
+ public boolean refresh() {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return false;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ AccessibilityNodeInfo refreshedInfo = client.findAccessibilityNodeInfoByAccessibilityId(
+ mConnectionId, mWindowId, mSourceNodeId, 0);
+ if (refreshedInfo == null) {
+ return false;
+ }
+ init(refreshedInfo);
+ refreshedInfo.recycle();
+ return true;
+ }
+
+ /**
* @return The ids of the children.
*
* @hide
@@ -705,6 +831,37 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Finds {@link AccessibilityNodeInfo}s by the fully qualified view id's resource
+ * name where a fully qualified id is of the from "package:id/id_resource_name".
+ * For example, if the target application's package is "foo.bar" and the id
+ * resource name is "baz", the fully qualified resource id is "foo.bar:id/baz".
+ *
+ * <p>
+ * <strong>Note:</strong> It is a client responsibility to recycle the
+ * received info by calling {@link AccessibilityNodeInfo#recycle()}
+ * to avoid creating of multiple instances.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the fully qualified view id if an {@link AccessibilityNodeInfo}
+ * the client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+ *
+ * @param viewId The fully qualified resource name of the view id to find.
+ * @return A list of node info.
+ */
+ public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(String viewId) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return Collections.emptyList();
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfosByViewId(mConnectionId, mWindowId, mSourceNodeId,
+ viewId);
+ }
+
+ /**
* Gets the parent.
* <p>
* <strong>Note:</strong> It is a client responsibility to recycle the
@@ -835,7 +992,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is checkable.
*/
public boolean isCheckable() {
- return getBooleanProperty(PROPERTY_CHECKABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE);
}
/**
@@ -851,7 +1008,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setCheckable(boolean checkable) {
- setBooleanProperty(PROPERTY_CHECKABLE, checkable);
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKABLE, checkable);
}
/**
@@ -860,7 +1017,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is checked.
*/
public boolean isChecked() {
- return getBooleanProperty(PROPERTY_CHECKED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_CHECKED);
}
/**
@@ -876,7 +1033,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setChecked(boolean checked) {
- setBooleanProperty(PROPERTY_CHECKED, checked);
+ setBooleanProperty(BOOLEAN_PROPERTY_CHECKED, checked);
}
/**
@@ -885,7 +1042,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is focusable.
*/
public boolean isFocusable() {
- return getBooleanProperty(PROPERTY_FOCUSABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
}
/**
@@ -901,7 +1058,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setFocusable(boolean focusable) {
- setBooleanProperty(PROPERTY_FOCUSABLE, focusable);
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
}
/**
@@ -910,7 +1067,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is focused.
*/
public boolean isFocused() {
- return getBooleanProperty(PROPERTY_FOCUSED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSED);
}
/**
@@ -926,7 +1083,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setFocused(boolean focused) {
- setBooleanProperty(PROPERTY_FOCUSED, focused);
+ setBooleanProperty(BOOLEAN_PROPERTY_FOCUSED, focused);
}
/**
@@ -935,7 +1092,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return Whether the node is visible to the user.
*/
public boolean isVisibleToUser() {
- return getBooleanProperty(PROPERTY_VISIBLE_TO_USER);
+ return getBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER);
}
/**
@@ -951,7 +1108,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setVisibleToUser(boolean visibleToUser) {
- setBooleanProperty(PROPERTY_VISIBLE_TO_USER, visibleToUser);
+ setBooleanProperty(BOOLEAN_PROPERTY_VISIBLE_TO_USER, visibleToUser);
}
/**
@@ -960,7 +1117,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is accessibility focused.
*/
public boolean isAccessibilityFocused() {
- return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED);
}
/**
@@ -976,7 +1133,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setAccessibilityFocused(boolean focused) {
- setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+ setBooleanProperty(BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED, focused);
}
/**
@@ -985,7 +1142,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is selected.
*/
public boolean isSelected() {
- return getBooleanProperty(PROPERTY_SELECTED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_SELECTED);
}
/**
@@ -1001,7 +1158,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setSelected(boolean selected) {
- setBooleanProperty(PROPERTY_SELECTED, selected);
+ setBooleanProperty(BOOLEAN_PROPERTY_SELECTED, selected);
}
/**
@@ -1010,7 +1167,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is clickable.
*/
public boolean isClickable() {
- return getBooleanProperty(PROPERTY_CLICKABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
}
/**
@@ -1026,7 +1183,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setClickable(boolean clickable) {
- setBooleanProperty(PROPERTY_CLICKABLE, clickable);
+ setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
}
/**
@@ -1035,7 +1192,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is long clickable.
*/
public boolean isLongClickable() {
- return getBooleanProperty(PROPERTY_LONG_CLICKABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
}
/**
@@ -1051,7 +1208,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setLongClickable(boolean longClickable) {
- setBooleanProperty(PROPERTY_LONG_CLICKABLE, longClickable);
+ setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
}
/**
@@ -1060,7 +1217,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is enabled.
*/
public boolean isEnabled() {
- return getBooleanProperty(PROPERTY_ENABLED);
+ return getBooleanProperty(BOOLEAN_PROPERTY_ENABLED);
}
/**
@@ -1076,7 +1233,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setEnabled(boolean enabled) {
- setBooleanProperty(PROPERTY_ENABLED, enabled);
+ setBooleanProperty(BOOLEAN_PROPERTY_ENABLED, enabled);
}
/**
@@ -1085,7 +1242,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is a password.
*/
public boolean isPassword() {
- return getBooleanProperty(PROPERTY_PASSWORD);
+ return getBooleanProperty(BOOLEAN_PROPERTY_PASSWORD);
}
/**
@@ -1101,7 +1258,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If called from an AccessibilityService.
*/
public void setPassword(boolean password) {
- setBooleanProperty(PROPERTY_PASSWORD, password);
+ setBooleanProperty(BOOLEAN_PROPERTY_PASSWORD, password);
}
/**
@@ -1110,7 +1267,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is scrollable, false otherwise.
*/
public boolean isScrollable() {
- return getBooleanProperty(PROPERTY_SCROLLABLE);
+ return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
}
/**
@@ -1127,7 +1284,32 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public void setScrollable(boolean scrollable) {
enforceNotSealed();
- setBooleanProperty(PROPERTY_SCROLLABLE, scrollable);
+ setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
+ }
+
+ /**
+ * Gets if the node is editable.
+ *
+ * @return True if the node is editable, false otherwise.
+ */
+ public boolean isEditable() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_EDITABLE);
+ }
+
+ /**
+ * Sets whether this node is editable.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param editable True if the node is editable.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setEditable(boolean editable) {
+ setBooleanProperty(BOOLEAN_PROPERTY_EDITABLE, editable);
}
/**
@@ -1349,6 +1531,75 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Sets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param viewIdResName The id resource name.
+ */
+ public void setViewIdResourceName(CharSequence viewIdResName) {
+ enforceNotSealed();
+ mViewIdResourceName = viewIdResName;
+ }
+
+ /**
+ * Gets the fully qualified resource name of the source view's id.
+ *
+ * <p>
+ * <strong>Note:</strong> The primary usage of this API is for UI test automation
+ * and in order to report the source view id of an {@link AccessibilityNodeInfo} the
+ * client has to set the {@link AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS}
+ * flag when configuring his {@link android.accessibilityservice.AccessibilityService}.
+ * </p>
+
+ * @return The id resource name.
+ */
+ public CharSequence getViewIdResourceName() {
+ return mViewIdResourceName;
+ }
+
+ /**
+ * Gets the text selection start.
+ *
+ * @return The text selection start if there is selection or -1.
+ */
+ public int getTextSelectionStart() {
+ return mTextSelectionStart;
+ }
+
+ /**
+ * Gets the text selection end.
+ *
+ * @return The text selection end if there is selection or -1.
+ */
+ public int getTextSelectionEnd() {
+ return mTextSelectionEnd;
+ }
+
+ /**
+ * Sets the text selection start and end.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param start The text selection start.
+ * @param end The text selection end.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTextSelection(int start, int end) {
+ enforceNotSealed();
+ mTextSelectionStart = start;
+ mTextSelectionEnd = end;
+ }
+
+ /**
* Gets the value of a boolean property.
*
* @param property The property.
@@ -1517,17 +1768,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return An instance.
*/
public static AccessibilityNodeInfo obtain() {
- synchronized (sPoolLock) {
- if (sPool != null) {
- AccessibilityNodeInfo info = sPool;
- sPool = sPool.mNext;
- sPoolSize--;
- info.mNext = null;
- info.mIsInPool = false;
- return info;
- }
- return new AccessibilityNodeInfo();
- }
+ AccessibilityNodeInfo info = sPool.acquire();
+ return (info != null) ? info : new AccessibilityNodeInfo();
}
/**
@@ -1552,18 +1794,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* @throws IllegalStateException If the info is already recycled.
*/
public void recycle() {
- if (mIsInPool) {
- throw new IllegalStateException("Info already recycled!");
- }
clear();
- synchronized (sPoolLock) {
- if (sPoolSize <= MAX_POOL_SIZE) {
- mNext = sPool;
- sPool = this;
- mIsInPool = true;
- sPoolSize++;
- }
- }
+ sPool.release(this);
}
/**
@@ -1609,6 +1841,10 @@ public class AccessibilityNodeInfo implements Parcelable {
parcel.writeCharSequence(mClassName);
parcel.writeCharSequence(mText);
parcel.writeCharSequence(mContentDescription);
+ parcel.writeCharSequence(mViewIdResourceName);
+
+ parcel.writeInt(mTextSelectionStart);
+ parcel.writeInt(mTextSelectionEnd);
// Since instances of this class are fetched via synchronous i.e. blocking
// calls in IPCs we always recycle as soon as the instance is marshaled.
@@ -1620,7 +1856,6 @@ public class AccessibilityNodeInfo implements Parcelable {
*
* @param other The other instance.
*/
- @SuppressWarnings("unchecked")
private void init(AccessibilityNodeInfo other) {
mSealed = other.mSealed;
mSourceNodeId = other.mSourceNodeId;
@@ -1635,6 +1870,7 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = other.mClassName;
mText = other.mText;
mContentDescription = other.mContentDescription;
+ mViewIdResourceName = other.mViewIdResourceName;
mActions= other.mActions;
mBooleanProperties = other.mBooleanProperties;
mMovementGranularities = other.mMovementGranularities;
@@ -1642,6 +1878,8 @@ public class AccessibilityNodeInfo implements Parcelable {
for (int i = 0; i < otherChildIdCount; i++) {
mChildNodeIds.put(i, other.mChildNodeIds.valueAt(i));
}
+ mTextSelectionStart = other.mTextSelectionStart;
+ mTextSelectionEnd = other.mTextSelectionEnd;
}
/**
@@ -1685,6 +1923,10 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = parcel.readCharSequence();
mText = parcel.readCharSequence();
mContentDescription = parcel.readCharSequence();
+ mViewIdResourceName = parcel.readCharSequence();
+
+ mTextSelectionStart = parcel.readInt();
+ mTextSelectionEnd = parcel.readInt();
}
/**
@@ -1707,7 +1949,10 @@ public class AccessibilityNodeInfo implements Parcelable {
mClassName = null;
mText = null;
mContentDescription = null;
+ mViewIdResourceName = null;
mActions = 0;
+ mTextSelectionStart = UNDEFINED;
+ mTextSelectionEnd = UNDEFINED;
}
/**
@@ -1746,8 +1991,16 @@ public class AccessibilityNodeInfo implements Parcelable {
return "ACTION_SCROLL_FORWARD";
case ACTION_SCROLL_BACKWARD:
return "ACTION_SCROLL_BACKWARD";
+ case ACTION_CUT:
+ return "ACTION_CUT";
+ case ACTION_COPY:
+ return "ACTION_COPY";
+ case ACTION_PASTE:
+ return "ACTION_PASTE";
+ case ACTION_SET_SELECTION:
+ return "ACTION_SET_SELECTION";
default:
- throw new IllegalArgumentException("Unknown action: " + action);
+ return"ACTION_UNKNOWN";
}
}
@@ -1851,6 +2104,7 @@ public class AccessibilityNodeInfo implements Parcelable {
builder.append("; className: ").append(mClassName);
builder.append("; text: ").append(mText);
builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; viewIdResName: ").append(mViewIdResourceName);
builder.append("; checkable: ").append(isCheckable());
builder.append("; checked: ").append(isChecked());
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index 9b39300..8d15472 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -17,6 +17,7 @@
package android.view.accessibility;
import android.os.Bundle;
+import android.view.MagnificationSpec;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
@@ -30,23 +31,23 @@ oneway interface IAccessibilityInteractionConnection {
void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
- void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interrogatingPid, long interrogatingTid, in MagnificationSpec spec);
void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
void findFocus(long accessibilityNodeId, int focusType, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
void focusSearch(long accessibilityNodeId, int direction, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
- long interrogatingTid);
+ long interrogatingTid, in MagnificationSpec spec);
void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments,
int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 08e30aa..54c2ba5 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -81,6 +81,11 @@ public final class InputMethodInfo implements Parcelable {
private boolean mIsAuxIme;
/**
+ * Cavert: mForceDefault must be false for production. This flag is only for test.
+ */
+ private final boolean mForceDefault;
+
+ /**
* Constructor.
*
* @param context The Context in which we are parsing the input method.
@@ -108,6 +113,7 @@ public final class InputMethodInfo implements Parcelable {
ServiceInfo si = service.serviceInfo;
mId = new ComponentName(si.packageName, si.name).flattenToShortString();
mIsAuxIme = true;
+ mForceDefault = false;
PackageManager pm = context.getPackageManager();
String settingsActivityComponent = null;
@@ -215,13 +221,39 @@ public final class InputMethodInfo implements Parcelable {
mIsAuxIme = source.readInt() == 1;
mService = ResolveInfo.CREATOR.createFromParcel(source);
source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR);
+ mForceDefault = false;
}
/**
- * Temporary API for creating a built-in input method.
+ * Temporary API for creating a built-in input method for test.
*/
public InputMethodInfo(String packageName, String className,
CharSequence label, String settingsActivity) {
+ this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null,
+ 0, false);
+ }
+
+ /**
+ * Temporary API for creating a built-in input method for test.
+ * @hide
+ */
+ public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
+ String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
+ boolean forceDefault) {
+ final ServiceInfo si = ri.serviceInfo;
+ mService = ri;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+ mSettingsActivityName = settingsActivity;
+ mIsDefaultResId = isDefaultResId;
+ mIsAuxIme = isAuxIme;
+ if (subtypes != null) {
+ mSubtypes.addAll(subtypes);
+ }
+ mForceDefault = forceDefault;
+ }
+
+ private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
+ CharSequence label) {
ResolveInfo ri = new ResolveInfo();
ServiceInfo si = new ServiceInfo();
ApplicationInfo ai = new ApplicationInfo();
@@ -234,11 +266,7 @@ public final class InputMethodInfo implements Parcelable {
si.exported = true;
si.nonLocalizedLabel = label;
ri.serviceInfo = si;
- mService = ri;
- mId = new ComponentName(si.packageName, si.name).flattenToShortString();
- mSettingsActivityName = settingsActivity;
- mIsDefaultResId = 0;
- mIsAuxIme = false;
+ return ri;
}
/**
@@ -340,6 +368,22 @@ public final class InputMethodInfo implements Parcelable {
return mIsDefaultResId;
}
+ /**
+ * Return whether or not this ime is a default ime or not.
+ * @hide
+ */
+ public boolean isDefault(Context context) {
+ if (mForceDefault) {
+ return true;
+ }
+ try {
+ final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
+ return res.getBoolean(getIsDefaultResourceId());
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+
public void dump(Printer pw, String prefix) {
pw.println(prefix + "mId=" + mId
+ " mSettingsActivityName=" + mSettingsActivityName);
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 008a615..8008a6b 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -318,12 +318,15 @@ class AccessibilityInjector {
/**
* Attempts to handle selection change events when accessibility is using a
* non-JavaScript method.
+ * <p>
+ * This must not be called from the main thread.
*
- * @param selectionString The selection string.
+ * @param selection The selection string.
+ * @param token The selection request token.
*/
- public void handleSelectionChangedIfNecessary(String selectionString) {
+ public void onSelectionStringChangedWebCoreThread(String selection, int token) {
if (mAccessibilityInjectorFallback != null) {
- mAccessibilityInjectorFallback.onSelectionStringChange(selectionString);
+ mAccessibilityInjectorFallback.onSelectionStringChangedWebCoreThread(selection, token);
}
}
diff --git a/core/java/android/webkit/AccessibilityInjectorFallback.java b/core/java/android/webkit/AccessibilityInjectorFallback.java
index 783b3db..6417527 100644
--- a/core/java/android/webkit/AccessibilityInjectorFallback.java
+++ b/core/java/android/webkit/AccessibilityInjectorFallback.java
@@ -27,8 +27,9 @@ import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.WebViewCore.EventHub;
+import com.android.internal.os.SomeArgs;
+
import java.util.ArrayList;
-import java.util.Stack;
/**
* This class injects accessibility into WebViews with disabled JavaScript or
@@ -48,8 +49,7 @@ import java.util.Stack;
* </p>
* The possible actions are invocations to
* {@link #setCurrentAxis(int, boolean, String)}, or
- * {@link #traverseCurrentAxis(int, boolean, String)}
- * {@link #traverseGivenAxis(int, int, boolean, String)}
+ * {@link #traverseGivenAxis(int, int, boolean, String, boolean)}
* {@link #performAxisTransition(int, int, boolean, String)}
* referred via the values of:
* {@link #ACTION_SET_CURRENT_AXIS},
@@ -74,6 +74,9 @@ class AccessibilityInjectorFallback {
private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
+ /** Timeout after which asynchronous granular movement is aborted. */
+ private static final int MODIFY_SELECTION_TIMEOUT = 500;
+
// WebView navigation axes from WebViewCore.h, plus an additional axis for
// the default behavior.
private static final int NAVIGATION_AXIS_CHARACTER = 0;
@@ -81,7 +84,8 @@ class AccessibilityInjectorFallback {
private static final int NAVIGATION_AXIS_SENTENCE = 2;
@SuppressWarnings("unused")
private static final int NAVIGATION_AXIS_HEADING = 3;
- private static final int NAVIGATION_AXIS_SIBLING = 5;
+ @SuppressWarnings("unused")
+ private static final int NAVIGATION_AXIS_SIBLING = 4;
@SuppressWarnings("unused")
private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5;
private static final int NAVIGATION_AXIS_DOCUMENT = 6;
@@ -99,8 +103,11 @@ class AccessibilityInjectorFallback {
private final WebViewClassic mWebView;
private final WebView mWebViewInternal;
- // events scheduled for sending as soon as we receive the selected text
- private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
+ // Event scheduled for sending as soon as we receive the selected text.
+ private AccessibilityEvent mScheduledEvent;
+
+ // Token required to send the scheduled event.
+ private int mScheduledToken = 0;
// the current traversal axis
private int mCurrentAxis = 2; // sentence
@@ -114,6 +121,15 @@ class AccessibilityInjectorFallback {
// keep track of last direction
private int mLastDirection;
+ // Lock used for asynchronous selection callback.
+ private final Object mCallbackLock = new Object();
+
+ // Whether the asynchronous selection callback was received.
+ private boolean mCallbackReceived;
+
+ // Whether the asynchronous selection callback succeeded.
+ private boolean mCallbackResult;
+
/**
* Creates a new injector associated with a given {@link WebViewClassic}.
*
@@ -174,8 +190,8 @@ class AccessibilityInjectorFallback {
}
mLastDirection = direction;
sendEvent = (binding.getSecondArgument(i) == 1);
- mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
- contentDescription);
+ mLastDownEventHandled = traverseGivenAxis(
+ direction, mCurrentAxis, sendEvent, contentDescription, false);
break;
case ACTION_TRAVERSE_GIVEN_AXIS:
direction = binding.getFirstArgument(i);
@@ -187,7 +203,7 @@ class AccessibilityInjectorFallback {
mLastDirection = direction;
axis = binding.getSecondArgument(i);
sendEvent = (binding.getThirdArgument(i) == 1);
- traverseGivenAxis(direction, axis, sendEvent, contentDescription);
+ traverseGivenAxis(direction, axis, sendEvent, contentDescription, false);
mLastDownEventHandled = true;
break;
case ACTION_PERFORM_AXIS_TRANSITION:
@@ -207,7 +223,7 @@ class AccessibilityInjectorFallback {
mLastDirection = binding.getFirstArgument(i);
sendEvent = (binding.getSecondArgument(i) == 1);
traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
- sendEvent, contentDescription);
+ sendEvent, contentDescription, false);
mLastDownEventHandled = false;
} else {
mLastDownEventHandled = true;
@@ -222,8 +238,7 @@ class AccessibilityInjectorFallback {
}
/**
- * Set the current navigation axis which will be used while
- * calling {@link #traverseCurrentAxis(int, boolean, String)}.
+ * Set the current navigation axis.
*
* @param axis The axis to set.
* @param sendEvent Whether to send an accessibility event to
@@ -255,20 +270,6 @@ class AccessibilityInjectorFallback {
}
}
- /**
- * Traverse the document along the current navigation axis.
- *
- * @param direction The direction of traversal.
- * @param sendEvent Whether to send an accessibility event to
- * announce the change.
- * @param contentDescription A description of the performed action.
- * @see #setCurrentAxis(int, boolean, String)
- */
- private boolean traverseCurrentAxis(int direction, boolean sendEvent,
- String contentDescription) {
- return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
- }
-
boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
@@ -276,14 +277,14 @@ class AccessibilityInjectorFallback {
final int direction = getDirectionForAction(action);
final int axis = getAxisForGranularity(arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
- return traverseGivenAxis(direction, axis, true, null);
+ return traverseGivenAxis(direction, axis, true, null, true);
}
case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT: {
final int direction = getDirectionForAction(action);
// TODO: Add support for moving by object.
final int axis = NAVIGATION_AXIS_SENTENCE;
- return traverseGivenAxis(direction, axis, true, null);
+ return traverseGivenAxis(direction, axis, true, null, true);
}
default:
return false;
@@ -293,7 +294,7 @@ class AccessibilityInjectorFallback {
/**
* Returns the {@link WebView}-defined direction for the given
* {@link AccessibilityNodeInfo}-defined action.
- *
+ *
* @param action An accessibility action identifier.
* @return A web view navigation direction.
*/
@@ -313,7 +314,7 @@ class AccessibilityInjectorFallback {
/**
* Returns the {@link WebView}-defined axis for the given
* {@link AccessibilityNodeInfo}-defined granularity.
- *
+ *
* @param granularity An accessibility granularity identifier.
* @return A web view navigation axis.
*/
@@ -345,20 +346,20 @@ class AccessibilityInjectorFallback {
* @param contentDescription A description of the performed action.
*/
private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
- String contentDescription) {
- WebViewCore webViewCore = mWebView.getWebViewCore();
+ String contentDescription, boolean sychronous) {
+ final WebViewCore webViewCore = mWebView.getWebViewCore();
if (webViewCore == null) {
return false;
}
- AccessibilityEvent event = null;
if (sendEvent) {
- event = getPartialyPopulatedAccessibilityEvent(
+ final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
- // the text will be set upon receiving the selection string
+ // The text will be set upon receiving the selection string.
event.setContentDescription(contentDescription);
+ mScheduledEvent = event;
+ mScheduledToken++;
}
- mScheduledEventStack.push(event);
// if the axis is the default let WebView handle the event which will
// result in cursor ring movement and selection of its content
@@ -366,27 +367,78 @@ class AccessibilityInjectorFallback {
return false;
}
- webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
- return true;
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = direction;
+ args.argi2 = axis;
+ args.argi3 = mScheduledToken;
+
+ // If we don't need synchronous results, just return true.
+ if (!sychronous) {
+ webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
+ return true;
+ }
+
+ final boolean callbackResult;
+
+ synchronized (mCallbackLock) {
+ mCallbackReceived = false;
+
+ // Asynchronously changes the selection in WebView, which responds by
+ // calling onSelectionStringChanged().
+ webViewCore.sendMessage(EventHub.MODIFY_SELECTION, args);
+
+ try {
+ mCallbackLock.wait(MODIFY_SELECTION_TIMEOUT);
+ } catch (InterruptedException e) {
+ // Do nothing.
+ }
+
+ callbackResult = mCallbackResult;
+ }
+
+ return (mCallbackReceived && callbackResult);
}
- /**
- * Called when the <code>selectionString</code> has changed.
- */
- public void onSelectionStringChange(String selectionString) {
+ /* package */ void onSelectionStringChangedWebCoreThread(
+ final String selection, final int token) {
+ synchronized (mCallbackLock) {
+ mCallbackReceived = true;
+ mCallbackResult = (selection != null);
+ mCallbackLock.notifyAll();
+ }
+
+ // Managing state and sending events must take place on the UI thread.
+ mWebViewInternal.post(new Runnable() {
+ @Override
+ public void run() {
+ onSelectionStringChangedMainThread(selection, token);
+ }
+ });
+ }
+
+ private void onSelectionStringChangedMainThread(String selection, int token) {
if (DEBUG) {
- Log.d(LOG_TAG, "Selection string: " + selectionString);
+ Log.d(LOG_TAG, "Selection string: " + selection);
}
- mIsLastSelectionStringNull = (selectionString == null);
- if (mScheduledEventStack.isEmpty()) {
+
+ if (token != mScheduledToken) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Selection string has incorrect token: " + token);
+ }
return;
}
- AccessibilityEvent event = mScheduledEventStack.pop();
- if ((event != null) && (selectionString != null)) {
- event.getText().add(selectionString);
+
+ mIsLastSelectionStringNull = (selection == null);
+
+ final AccessibilityEvent event = mScheduledEvent;
+ mScheduledEvent = null;
+
+ if ((event != null) && (selection != null)) {
+ event.getText().add(selection);
event.setFromIndex(0);
- event.setToIndex(selectionString.length());
+ event.setToIndex(selection.length());
sendAccessibilityEvent(event);
+ event.recycle();
}
}
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index c3a1a17..0b7e92f 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -226,8 +226,6 @@ class BrowserFrame extends Handler {
} else {
sJavaBridge.setCacheSize(4 * 1024 * 1024);
}
- // initialize CacheManager
- CacheManager.init(appContext);
// create CookieSyncManager with current Context
CookieSyncManager.createInstance(appContext);
// create PluginManager with current Context
diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java
index 52f41e6..bbd3f2b 100644
--- a/core/java/android/webkit/CacheManager.java
+++ b/core/java/android/webkit/CacheManager.java
@@ -45,14 +45,6 @@ import java.util.Map;
// CacheManager may only be used if your activity contains a WebView.
@Deprecated
public final class CacheManager {
-
- private static final String LOGTAG = "cache";
-
- static final String HEADER_KEY_IFMODIFIEDSINCE = "if-modified-since";
- static final String HEADER_KEY_IFNONEMATCH = "if-none-match";
-
- private static File mBaseDir;
-
/**
* Represents a resource stored in the HTTP cache. Instances of this class
* can be obtained by calling
@@ -239,39 +231,23 @@ public final class CacheManager {
}
/**
- * Initializes the HTTP cache. This method must be called before any
- * CacheManager methods are used. Note that this is called automatically
- * when a {@link WebView} is created.
- *
- * @param context the application context
- */
- static void init(Context context) {
- // This isn't actually where the real cache lives, but where we put files for the
- // purpose of getCacheFile().
- mBaseDir = new File(context.getCacheDir(), "webviewCacheChromiumStaging");
- if (!mBaseDir.exists()) {
- mBaseDir.mkdirs();
- }
- }
-
- /**
* Gets the base directory in which the files used to store the contents of
* cache entries are placed. See
* {@link CacheManager.CacheResult#getLocalPath CacheManager.CacheResult.getLocalPath()}.
*
* @return the base directory of the cache
- * @deprecated Access to the HTTP cache will be removed in a future release.
+ * @deprecated This method no longer has any effect and always returns null.
*/
@Deprecated
public static File getCacheFileBaseDir() {
- return mBaseDir;
+ return null;
}
/**
* Gets whether the HTTP cache is disabled.
*
* @return true if the HTTP cache is disabled
- * @deprecated Access to the HTTP cache will be removed in a future release.
+ * @deprecated This method no longer has any effect and always returns false.
*/
@Deprecated
public static boolean cacheDisabled() {
@@ -314,73 +290,11 @@ public final class CacheManager {
* @param headers a map from HTTP header name to value, to be populated
* for the returned cache entry
* @return the cache entry for the specified URL
- * @deprecated Access to the HTTP cache will be removed in a future release.
+ * @deprecated This method no longer has any effect and always returns null.
*/
@Deprecated
public static CacheResult getCacheFile(String url,
Map<String, String> headers) {
- return getCacheFile(url, 0, headers);
- }
-
- static CacheResult getCacheFile(String url, long postIdentifier,
- Map<String, String> headers) {
- CacheResult result = nativeGetCacheResult(url);
- if (result == null) {
- return null;
- }
- // A temporary local file will have been created native side and localPath set
- // appropriately.
- File src = new File(mBaseDir, result.localPath);
- try {
- // Open the file here so that even if it is deleted, the content
- // is still readable by the caller until close() is called.
- result.inStream = new FileInputStream(src);
- } catch (FileNotFoundException e) {
- Log.v(LOGTAG, "getCacheFile(): Failed to open file: " + e);
- // TODO: The files in the cache directory can be removed by the
- // system. If it is gone, what should we do?
- return null;
- }
-
- // A null value for headers is used by CACHE_MODE_CACHE_ONLY to imply
- // that we should provide the cache result even if it is expired.
- // Note that a negative expires value means a time in the far future.
- if (headers != null && result.expires >= 0
- && result.expires <= System.currentTimeMillis()) {
- if (result.lastModified == null && result.etag == null) {
- return null;
- }
- // Return HEADER_KEY_IFNONEMATCH or HEADER_KEY_IFMODIFIEDSINCE
- // for requesting validation.
- if (result.etag != null) {
- headers.put(HEADER_KEY_IFNONEMATCH, result.etag);
- }
- if (result.lastModified != null) {
- headers.put(HEADER_KEY_IFMODIFIEDSINCE, result.lastModified);
- }
- }
-
- if (DebugFlags.CACHE_MANAGER) {
- Log.v(LOGTAG, "getCacheFile for url " + url);
- }
-
- return result;
- }
-
- /**
- * Given a URL and its full headers, gets a CacheResult if a local cache
- * can be stored. Otherwise returns null. The mimetype is passed in so that
- * the function can use the mimetype that will be passed to WebCore which
- * could be different from the mimetype defined in the headers.
- * forceCache is for out-of-package callers to force creation of a
- * CacheResult, and is used to supply surrogate responses for URL
- * interception.
- *
- * @return a CacheResult for a given URL
- */
- static CacheResult createCacheFile(String url, int statusCode,
- Headers headers, String mimeType, boolean forceCache) {
- // This method is public but hidden. We break functionality.
return null;
}
@@ -424,36 +338,4 @@ public final class CacheManager {
// use, we should already have thrown an exception above.
assert false;
}
-
- /**
- * Removes all cache files.
- *
- * @return whether the removal succeeded
- */
- static boolean removeAllCacheFiles() {
- // delete cache files in a separate thread to not block UI.
- final Runnable clearCache = new Runnable() {
- public void run() {
- // delete all cache files
- try {
- String[] files = mBaseDir.list();
- // if mBaseDir doesn't exist, files can be null.
- if (files != null) {
- for (int i = 0; i < files.length; i++) {
- File f = new File(mBaseDir, files[i]);
- if (!f.delete()) {
- Log.e(LOGTAG, f.getPath() + " delete failed.");
- }
- }
- }
- } catch (SecurityException e) {
- // Ignore SecurityExceptions.
- }
- }
- };
- new Thread(clearCache).start();
- return true;
- }
-
- private static native CacheResult nativeGetCacheResult(String url);
}
diff --git a/core/java/android/webkit/EventLogTags.logtags b/core/java/android/webkit/EventLogTags.logtags
index 082a437..b0b5493 100644
--- a/core/java/android/webkit/EventLogTags.logtags
+++ b/core/java/android/webkit/EventLogTags.logtags
@@ -8,4 +8,3 @@ option java_package android.webkit;
# 70103- used by the browser app itself
70150 browser_snap_center
-70151 browser_text_size_change (oldSize|1|5), (newSize|1|5)
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index 6a627e1..c68b450 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -33,12 +33,15 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
-class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
- View.OnClickListener {
+/**
+ * @hide
+ */
+public class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
+ View.OnClickListener, WebView.FindListener {
private View mCustomView;
private EditText mEditText;
private TextView mMatches;
- private WebViewClassic mWebView;
+ private WebView mWebView;
private InputMethodManager mInput;
private Resources mResources;
private boolean mMatchesFound;
@@ -46,7 +49,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
private int mActiveMatchIndex;
private ActionMode mActionMode;
- FindActionModeCallback(Context context) {
+ public FindActionModeCallback(Context context) {
mCustomView = LayoutInflater.from(context).inflate(
com.android.internal.R.layout.webview_find, null);
mEditText = (EditText) mCustomView.findViewById(
@@ -61,7 +64,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
mResources = context.getResources();
}
- void finish() {
+ public void finish() {
mActionMode.finish();
}
@@ -69,7 +72,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
* Place text in the text field so it can be searched for. Need to press
* the find next or find previous button to find all of the matches.
*/
- void setText(String text) {
+ public void setText(String text) {
mEditText.setText(text);
Spannable span = (Spannable) mEditText.getText();
int length = span.length();
@@ -84,15 +87,23 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
}
/*
- * Set the WebView to search. Must be non null, and set before calling
- * startActionMode.
+ * Set the WebView to search. Must be non null.
*/
- void setWebView(WebViewClassic webView) {
+ public void setWebView(WebView webView) {
if (null == webView) {
throw new AssertionError("WebView supplied to "
+ "FindActionModeCallback cannot be null");
}
mWebView = webView;
+ mWebView.setFindDialogFindListener(this);
+ }
+
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting) {
+ if (isDoneCounting) {
+ updateMatchCount(activeMatchOrdinal, numberOfMatches, numberOfMatches == 0);
+ }
}
/*
@@ -121,7 +132,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
/*
* Highlight all the instances of the string from mEditText in mWebView.
*/
- void findAll() {
+ public void findAll() {
if (mWebView == null) {
throw new AssertionError(
"No WebView for FindActionModeCallback::findAll");
@@ -208,7 +219,8 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
mWebView.notifyFindDialogDismissed();
- mInput.hideSoftInputFromWindow(mWebView.getWebView().getWindowToken(), 0);
+ mWebView.setFindDialogFindListener(null);
+ mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
}
@Override
@@ -222,7 +234,7 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
throw new AssertionError(
"No WebView for FindActionModeCallback::onActionItemClicked");
}
- mInput.hideSoftInputFromWindow(mWebView.getWebView().getWindowToken(), 0);
+ mInput.hideSoftInputFromWindow(mWebView.getWindowToken(), 0);
switch(item.getItemId()) {
case com.android.internal.R.id.find_prev:
findNext(false);
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 9c0f754..bc3d035 100644
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -61,7 +61,8 @@ public class GeolocationPermissions {
};
/**
- * Gets the singleton instance of this class.
+ * Gets the singleton instance of this class. This method cannot be
+ * called before the application instantiates a {@link WebView} instance.
*
* @return the singleton {@link GeolocationPermissions} instance
*/
diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java
index 296d960..ee3b369 100644
--- a/core/java/android/webkit/HttpAuthHandler.java
+++ b/core/java/android/webkit/HttpAuthHandler.java
@@ -40,7 +40,7 @@ public class HttpAuthHandler extends Handler {
* previously been rejected by the server for the current request.
*
* @return whether the credentials are suitable for use
- * @see Webview#getHttpAuthUsernamePassword
+ * @see WebView#getHttpAuthUsernamePassword
*/
public boolean useHttpAuthUsernamePassword() {
return false;
diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java
index 3a43950..af31544 100644
--- a/core/java/android/webkit/SslErrorHandler.java
+++ b/core/java/android/webkit/SslErrorHandler.java
@@ -19,9 +19,11 @@ package android.webkit;
import android.os.Handler;
/**
- * SslErrorHandler: class responsible for handling SSL errors.
- * This class is passed as a parameter to BrowserCallback.displaySslErrorDialog
- * and is meant to receive the user's response.
+ * Represents a request for handling an SSL error. Instances of this class are
+ * created by the WebView and passed to
+ * {@link WebViewClient#onReceivedSslError}. The host application must call
+ * either {@link #proceed} or {@link #cancel} to set the WebView's response
+ * to the request.
*/
public class SslErrorHandler extends Handler {
diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java
index 096d4cda..1d44b96 100644
--- a/core/java/android/webkit/ViewStateSerializer.java
+++ b/core/java/android/webkit/ViewStateSerializer.java
@@ -31,7 +31,8 @@ class ViewStateSerializer {
private static final int WORKING_STREAM_STORAGE = 16 * 1024;
- static final int VERSION = 1;
+ // VERSION = 1 was for pictures encoded using a previous copy of libskia
+ static final int VERSION = 2;
static boolean serializeViewState(OutputStream stream, DrawData draw)
throws IOException {
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index aa68904..728bcd3 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -37,6 +37,10 @@ public abstract class WebSettings {
* <li>SINGLE_COLUMN moves all content into one column that is the width of the
* view.</li>
* <li>NARROW_COLUMNS makes all columns no wider than the screen if possible.</li>
+ * <li>TEXT_AUTOSIZING boosts font size of paragraphs based on heuristics to make
+ * the text readable when viewing a wide-viewport layout in the overview mode.
+ * It is recommended to enable zoom support {@link #setSupportZoom} when
+ * using this mode.</li>
* </ul>
*/
// XXX: These must match LayoutAlgorithm in Settings.h in WebCore.
@@ -47,7 +51,11 @@ public abstract class WebSettings {
*/
@Deprecated
SINGLE_COLUMN,
- NARROW_COLUMNS
+ NARROW_COLUMNS,
+ /**
+ * @hide
+ */
+ TEXT_AUTOSIZING
}
/**
@@ -89,6 +97,14 @@ public abstract class WebSettings {
ZoomDensity(int size) {
value = size;
}
+
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public int getValue() {
+ return value;
+ }
+
int value;
}
@@ -388,16 +404,14 @@ public abstract class WebSettings {
}
/**
- * Sets whether the WebView should save form data. The default is true,
- * unless in private browsing mode, when the value is always false.
+ * Sets whether the WebView should save form data. The default is true.
*/
public void setSaveFormData(boolean save) {
throw new MustOverrideException();
}
/**
- * Gets whether the WebView saves form data. Always false in private
- * browsing mode.
+ * Gets whether the WebView saves form data.
*
* @return whether the WebView saves form data
* @see #setSaveFormData
@@ -580,18 +594,25 @@ public abstract class WebSettings {
}
/**
- * Tells the WebView to use a wide viewport. The default is false.
+ * Sets whether the WebView should enable support for the &quot;viewport&quot;
+ * HTML meta tag or should use a wide viewport.
+ * When the value of the setting is false, the layout width is always set to the
+ * width of the WebView control in device-independent (CSS) pixels.
+ * When the value is true and the page contains the viewport meta tag, the value
+ * of the width specified in the tag is used. If the page does not contain the tag or
+ * does not provide a width, then a wide viewport will be used.
*
- * @param use whether to use a wide viewport
+ * @param use whether to enable support for the viewport meta tag
*/
public synchronized void setUseWideViewPort(boolean use) {
throw new MustOverrideException();
}
/**
- * Gets whether the WebView is using a wide viewport.
+ * Gets whether the WebView supports the &quot;viewport&quot;
+ * HTML meta tag or will use a wide viewport.
*
- * @return true if the WebView is using a wide viewport
+ * @return true if the WebView supports the viewport meta tag
* @see #setUseWideViewPort
*/
public synchronized boolean getUseWideViewPort() {
@@ -936,6 +957,9 @@ public abstract class WebSettings {
* access to content from other file scheme URLs. See
* {@link #setAllowFileAccessFromFileURLs}. To enable the most restrictive,
* and therefore secure policy, this setting should be disabled.
+ * Note that this setting affects only JavaScript access to file scheme
+ * resources. Other access to such resources, for example, from image HTML
+ * elements, is unaffected.
* <p>
* The default value is true for API level
* {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
@@ -953,6 +977,9 @@ public abstract class WebSettings {
* enable the most restrictive, and therefore secure policy, this setting
* should be disabled. Note that the value of this setting is ignored if
* the value of {@link #getAllowUniversalAccessFromFileURLs} is true.
+ * Note too, that this setting affects only JavaScript access to file scheme
+ * resources. Other access to such resources, for example, from image HTML
+ * elements, is unaffected.
* <p>
* The default value is true for API level
* {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
@@ -1052,7 +1079,7 @@ public abstract class WebSettings {
*
* @param appCachePath a String path to the directory containing
* Application Caches files.
- * @see setAppCacheEnabled
+ * @see #setAppCacheEnabled
*/
public synchronized void setAppCachePath(String appCachePath) {
throw new MustOverrideException();
@@ -1121,9 +1148,22 @@ public abstract class WebSettings {
}
/**
- * Sets whether Geolocation is enabled. The default is true. See also
- * {@link #setGeolocationDatabasePath} for how to correctly set up
- * Geolocation.
+ * Sets whether Geolocation is enabled. The default is true.
+ * <p>
+ * Please note that in order for the Geolocation API to be usable
+ * by a page in the WebView, the following requirements must be met:
+ * <ul>
+ * <li>an application must have permission to access the device location,
+ * see {@link android.Manifest.permission#ACCESS_COARSE_LOCATION},
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION};
+ * <li>an application must provide an implementation of the
+ * {@link WebChromeClient#onGeolocationPermissionsShowPrompt} callback
+ * to receive notifications that a page is requesting access to location
+ * via the JavaScript Geolocation API.
+ * </ul>
+ * <p>
+ * As an option, it is possible to store previous locations and web origin
+ * permissions in a database. See {@link #setGeolocationDatabasePath}.
*
* @param flag whether Geolocation should be enabled
*/
@@ -1295,7 +1335,7 @@ public abstract class WebSettings {
* and content is re-validated as needed. When navigating back, content is
* not revalidated, instead the content is just retrieved from the cache.
* This method allows the client to override this behavior by specifying
- * one of {@link #LOAD_DEFAULT}, {@link #LOAD_NORMAL},
+ * one of {@link #LOAD_DEFAULT},
* {@link #LOAD_CACHE_ELSE_NETWORK}, {@link #LOAD_NO_CACHE} or
* {@link #LOAD_CACHE_ONLY}. The default value is {@link #LOAD_DEFAULT}.
*
diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java
index 1bbe7bb..c10a429 100644
--- a/core/java/android/webkit/WebSettingsClassic.java
+++ b/core/java/android/webkit/WebSettingsClassic.java
@@ -647,10 +647,6 @@ public class WebSettingsClassic extends WebSettings {
@Override
public synchronized void setTextZoom(int textZoom) {
if (mTextSize != textZoom) {
- if (WebViewClassic.mLogEvent) {
- EventLog.writeEvent(EventLogTags.BROWSER_TEXT_SIZE_CHANGE,
- mTextSize, textZoom);
- }
mTextSize = textZoom;
postSync();
}
@@ -820,6 +816,10 @@ public class WebSettingsClassic extends WebSettings {
*/
@Override
public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
+ if (l == LayoutAlgorithm.TEXT_AUTOSIZING) {
+ throw new IllegalArgumentException(
+ "WebViewClassic does not support TEXT_AUTOSIZING layout mode");
+ }
// XXX: This will only be affective if libwebcore was built with
// ANDROID_LAYOUT defined.
if (mLayoutAlgorithm != l) {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6df7820..dcb664e 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -334,7 +334,9 @@ public class WebView extends AbsoluteLayout
* See {@link WebView#capturePicture} for details of the picture.
*
* @param view the WebView that owns the picture
- * @param picture the new picture
+ * @param picture the new picture. Applications targeting
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} or above
+ * will always receive a null Picture.
* @deprecated Deprecated due to internal changes.
*/
@Deprecated
@@ -615,7 +617,7 @@ public class WebView extends AbsoluteLayout
* @param realm the realm to which the credentials apply
* @param username the username
* @param password the password
- * @see getHttpAuthUsernamePassword
+ * @see #getHttpAuthUsernamePassword
* @see WebViewDatabase#hasHttpAuthUsernamePassword
* @see WebViewDatabase#clearHttpAuthUsernamePassword
*/
@@ -635,7 +637,7 @@ public class WebView extends AbsoluteLayout
* @return the credentials as a String array, if found. The first element
* is the username and the second element is the password. Null if
* no credentials are found.
- * @see setHttpAuthUsernamePassword
+ * @see #setHttpAuthUsernamePassword
* @see WebViewDatabase#hasHttpAuthUsernamePassword
* @see WebViewDatabase#clearHttpAuthUsernamePassword
*/
@@ -1329,7 +1331,8 @@ public class WebView extends AbsoluteLayout
*/
public void setFindListener(FindListener listener) {
checkThread();
- mProvider.setFindListener(listener);
+ setupFindListenerIfNeeded();
+ mFindListener.mUserFindListener = listener;
}
/**
@@ -1850,11 +1853,60 @@ public class WebView extends AbsoluteLayout
}
//-------------------------------------------------------------------------
+ // Package-private internal stuff
+ //-------------------------------------------------------------------------
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void setFindDialogFindListener(FindListener listener) {
+ checkThread();
+ setupFindListenerIfNeeded();
+ mFindListener.mFindDialogFindListener = listener;
+ }
+
+ // Only used by android.webkit.FindActionModeCallback.
+ void notifyFindDialogDismissed() {
+ checkThread();
+ mProvider.notifyFindDialogDismissed();
+ }
+
+ //-------------------------------------------------------------------------
// Private internal stuff
//-------------------------------------------------------------------------
private WebViewProvider mProvider;
+ /**
+ * In addition to the FindListener that the user may set via the WebView.setFindListener
+ * API, FindActionModeCallback will register it's own FindListener. We keep them separate
+ * via this class so that that the two FindListeners can potentially exist at once.
+ */
+ private class FindListenerDistributor implements FindListener {
+ private FindListener mFindDialogFindListener;
+ private FindListener mUserFindListener;
+
+ @Override
+ public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
+ boolean isDoneCounting) {
+ if (mFindDialogFindListener != null) {
+ mFindDialogFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+
+ if (mUserFindListener != null) {
+ mUserFindListener.onFindResultReceived(activeMatchOrdinal, numberOfMatches,
+ isDoneCounting);
+ }
+ }
+ }
+ private FindListenerDistributor mFindListener;
+
+ private void setupFindListenerIfNeeded() {
+ if (mFindListener == null) {
+ mFindListener = new FindListenerDistributor();
+ mProvider.setFindListener(mFindListener);
+ }
+ }
+
private void ensureProviderCreated() {
checkThread();
if (mProvider == null) {
@@ -1865,9 +1917,6 @@ public class WebView extends AbsoluteLayout
}
private static synchronized WebViewFactoryProvider getFactory() {
- // For now the main purpose of this function (and the factory abstration) is to keep
- // us honest and minimize usage of WebViewClassic internals when binding the proxy.
- checkThread();
return WebViewFactory.getProvider();
}
@@ -1910,9 +1959,8 @@ public class WebView extends AbsoluteLayout
@Override
public void setOverScrollMode(int mode) {
super.setOverScrollMode(mode);
- // This method may called in the constructor chain, before the WebView provider is
- // created. (Fortunately, this is the only method we override that can get called by
- // any of the base class constructors).
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
ensureProviderCreated();
mProvider.getViewDelegate().setOverScrollMode(mode);
}
@@ -2072,6 +2120,9 @@ public class WebView extends AbsoluteLayout
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
+ // This method may be called in the constructor chain, before the WebView provider is
+ // created.
+ ensureProviderCreated();
mProvider.getViewDelegate().onVisibilityChanged(changedView, visibility);
}
@@ -2137,4 +2188,10 @@ public class WebView extends AbsoluteLayout
super.setLayerType(layerType, paint);
mProvider.getViewDelegate().setLayerType(layerType, paint);
}
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ mProvider.getViewDelegate().preDispatchDraw(canvas);
+ super.dispatchDraw(canvas);
+ }
}
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 7154f95..6fefcca 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -1024,30 +1024,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int UPDATE_MATCH_COUNT = 126;
static final int CENTER_FIT_RECT = 127;
static final int SET_SCROLLBAR_MODES = 129;
- static final int SELECTION_STRING_CHANGED = 130;
- static final int HIT_TEST_RESULT = 131;
- static final int SAVE_WEBARCHIVE_FINISHED = 132;
-
- static final int SET_AUTOFILLABLE = 133;
- static final int AUTOFILL_COMPLETE = 134;
-
- static final int SCREEN_ON = 136;
- static final int UPDATE_ZOOM_DENSITY = 139;
- static final int EXIT_FULLSCREEN_VIDEO = 140;
-
- static final int COPY_TO_CLIPBOARD = 141;
- static final int INIT_EDIT_FIELD = 142;
- static final int REPLACE_TEXT = 143;
- static final int CLEAR_CARET_HANDLE = 144;
- static final int KEY_PRESS = 145;
- static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
- static final int FOCUS_NODE_CHANGED = 147;
- static final int AUTOFILL_FORM = 148;
- static final int SCROLL_EDIT_TEXT = 149;
- static final int EDIT_TEXT_SIZE_CHANGED = 150;
- static final int SHOW_CARET_HANDLE = 151;
- static final int UPDATE_CONTENT_BOUNDS = 152;
- static final int SCROLL_HANDLE_INTO_VIEW = 153;
+ static final int HIT_TEST_RESULT = 130;
+ static final int SAVE_WEBARCHIVE_FINISHED = 131;
+ static final int SET_AUTOFILLABLE = 132;
+ static final int AUTOFILL_COMPLETE = 133;
+ static final int SCREEN_ON = 134;
+ static final int UPDATE_ZOOM_DENSITY = 135;
+ static final int EXIT_FULLSCREEN_VIDEO = 136;
+ static final int COPY_TO_CLIPBOARD = 137;
+ static final int INIT_EDIT_FIELD = 138;
+ static final int REPLACE_TEXT = 139;
+ static final int CLEAR_CARET_HANDLE = 140;
+ static final int KEY_PRESS = 141;
+ static final int RELOCATE_AUTO_COMPLETE_POPUP = 142;
+ static final int FOCUS_NODE_CHANGED = 143;
+ static final int AUTOFILL_FORM = 144;
+ static final int SCROLL_EDIT_TEXT = 145;
+ static final int EDIT_TEXT_SIZE_CHANGED = 146;
+ static final int SHOW_CARET_HANDLE = 147;
+ static final int UPDATE_CONTENT_BOUNDS = 148;
+ static final int SCROLL_HANDLE_INTO_VIEW = 149;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1800,6 +1796,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
}
+ /* package */ void handleSelectionChangedWebCoreThread(String selection, int token) {
+ if (isAccessibilityInjectionEnabled()) {
+ getAccessibilityInjector().onSelectionStringChangedWebCoreThread(selection, token);
+ }
+ }
+
private boolean isAccessibilityInjectionEnabled() {
final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
if (!manager.isEnabled()) {
@@ -3702,7 +3704,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mCachedOverlappingActionModeHeight = -1;
mFindCallback = callback;
setFindIsUp(true);
- mFindCallback.setWebView(this);
+ mFindCallback.setWebView(getWebView());
if (showIme) {
mFindCallback.showSoftInput();
} else if (text != null) {
@@ -3804,7 +3806,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
/**
* Called when the find ActionMode ends.
*/
- void notifyFindDialogDismissed() {
+ @Override
+ public void notifyFindDialogDismissed() {
mFindCallback = null;
mCachedOverlappingActionModeHeight = -1;
if (mWebViewCore == null) {
@@ -7497,13 +7500,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mVerticalScrollBarMode = msg.arg2;
break;
- case SELECTION_STRING_CHANGED:
- if (isAccessibilityInjectionEnabled()) {
- getAccessibilityInjector()
- .handleSelectionChangedIfNecessary((String) msg.obj);
- }
- break;
-
case FOCUS_NODE_CHANGED:
mIsEditingText = (msg.arg1 == mFieldPointer);
if (mAutoCompletePopup != null && !mIsEditingText) {
@@ -7913,7 +7909,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (mPictureListener != null) {
// trigger picture listener for hardware layers. Software layers are
// triggered in setNewPicture
- mPictureListener.onNewPicture(getWebView(), capturePicture());
+ Picture picture = mContext.getApplicationInfo().targetSdkVersion <
+ Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
+ mPictureListener.onNewPicture(getWebView(), picture);
}
}
@@ -7998,7 +7996,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
|| mWebView.getLayerType() == View.LAYER_TYPE_SOFTWARE) {
// trigger picture listener for software layers. Hardware layers are
// triggered in pageSwapCallback
- mPictureListener.onNewPicture(getWebView(), capturePicture());
+ Picture picture = mContext.getApplicationInfo().targetSdkVersion <
+ Build.VERSION_CODES.JELLY_BEAN_MR2 ? capturePicture() : null;
+ mPictureListener.onNewPicture(getWebView(), picture);
}
}
}
@@ -8562,6 +8562,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
updateHwAccelerated();
}
+ @Override
+ public void preDispatchDraw(Canvas canvas) {
+ // no-op for WebViewClassic.
+ }
+
private void updateHwAccelerated() {
if (mNativeClass == 0) {
return;
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 08a046a..e8974c6 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -31,6 +31,7 @@ public class WebViewClient {
* proper handler for the url. If WebViewClient is provided, return true
* means the host application handles the url, while return false means the
* current WebView handles the url.
+ * This method is not called for requests using the POST "method".
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
@@ -82,9 +83,9 @@ public class WebViewClient {
* Notify the host application of a resource request and allow the
* application to return the data. If the return value is null, the WebView
* will continue to load the resource as usual. Otherwise, the return
- * response and data will be used. NOTE: This method is called by the
- * network thread so clients should exercise caution when accessing private
- * data.
+ * response and data will be used. NOTE: This method is called on a thread
+ * other than the UI thread so clients should exercise caution
+ * when accessing private data or the view system.
*
* @param view The {@link android.webkit.WebView} that is requesting the
* resource.
@@ -213,7 +214,7 @@ public class WebViewClient {
* @param handler the HttpAuthHandler used to set the WebView's response
* @param host the host requiring authentication
* @param realm the realm for which authentication is required
- * @see Webview#getHttpAuthUsernamePassword
+ * @see WebView#getHttpAuthUsernamePassword
*/
public void onReceivedHttpAuthRequest(WebView view,
HttpAuthHandler handler, String host, String realm) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index c35b768..4a09636 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -41,6 +41,8 @@ import android.view.View;
import android.webkit.WebViewClassic.FocusNodeHref;
import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
+import com.android.internal.os.SomeArgs;
+
import junit.framework.Assert;
import java.io.OutputStream;
@@ -1545,12 +1547,14 @@ public final class WebViewCore {
case MODIFY_SELECTION:
mTextSelectionChangeReason
= TextSelectionData.REASON_ACCESSIBILITY_INJECTOR;
- String modifiedSelectionString =
- nativeModifySelection(mNativeClass, msg.arg1,
- msg.arg2);
- mWebViewClassic.mPrivateHandler.obtainMessage(
- WebViewClassic.SELECTION_STRING_CHANGED,
- modifiedSelectionString).sendToTarget();
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final String modifiedSelectionString = nativeModifySelection(
+ mNativeClass, args.argi1, args.argi2);
+ // If accessibility is on, the main thread may be
+ // waiting for a response. Send on webcore thread.
+ mWebViewClassic.handleSelectionChangedWebCoreThread(
+ modifiedSelectionString, args.argi3);
+ args.recycle();
mTextSelectionChangeReason
= TextSelectionData.REASON_UNKNOWN;
break;
@@ -2001,9 +2005,6 @@ public final class WebViewCore {
private void clearCache(boolean includeDiskFiles) {
mBrowserFrame.clearCache();
- if (includeDiskFiles) {
- CacheManager.removeAllCacheFiles();
- }
}
private void loadUrl(String url, Map<String, String> extraHeaders) {
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index 5597259..e08052a 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -50,7 +50,7 @@ public class WebViewDatabase {
*
* @return true if there are any saved username/password pairs
* @see WebView#savePassword
- * @see clearUsernamePassword
+ * @see #clearUsernamePassword
*/
public boolean hasUsernamePassword() {
throw new MustOverrideException();
@@ -61,7 +61,7 @@ public class WebViewDatabase {
* Note that these are unrelated to HTTP authentication credentials.
*
* @see WebView#savePassword
- * @see hasUsernamePassword
+ * @see #hasUsernamePassword
*/
public void clearUsernamePassword() {
throw new MustOverrideException();
@@ -71,9 +71,9 @@ public class WebViewDatabase {
* Gets whether there are any saved credentials for HTTP authentication.
*
* @return whether there are any saved credentials
- * @see Webview#getHttpAuthUsernamePassword
- * @see Webview#setHttpAuthUsernamePassword
- * @see clearHttpAuthUsernamePassword
+ * @see WebView#getHttpAuthUsernamePassword
+ * @see WebView#setHttpAuthUsernamePassword
+ * @see #clearHttpAuthUsernamePassword
*/
public boolean hasHttpAuthUsernamePassword() {
throw new MustOverrideException();
@@ -82,9 +82,9 @@ public class WebViewDatabase {
/**
* Clears any saved credentials for HTTP authentication.
*
- * @see Webview#getHttpAuthUsernamePassword
- * @see Webview#setHttpAuthUsernamePassword
- * @see hasHttpAuthUsernamePassword
+ * @see WebView#getHttpAuthUsernamePassword
+ * @see WebView#setHttpAuthUsernamePassword
+ * @see #hasHttpAuthUsernamePassword
*/
public void clearHttpAuthUsernamePassword() {
throw new MustOverrideException();
@@ -94,7 +94,7 @@ public class WebViewDatabase {
* Gets whether there is any saved data for web forms.
*
* @return whether there is any saved data for web forms
- * @see clearFormData
+ * @see #clearFormData
*/
public boolean hasFormData() {
throw new MustOverrideException();
@@ -103,7 +103,7 @@ public class WebViewDatabase {
/**
* Clears any saved data for web forms.
*
- * @see hasFormData
+ * @see #hasFormData
*/
public void clearFormData() {
throw new MustOverrideException();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index b833a01..18df0b1 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -31,7 +31,7 @@ class WebViewFactory {
// TODO: When the Chromium powered WebView is ready, it should be the default factory class.
private static final String DEFAULT_WEBVIEW_FACTORY = "android.webkit.WebViewClassic$Factory";
private static final String CHROMIUM_WEBVIEW_FACTORY =
- "com.android.webviewchromium.WebViewChromiumFactoryProvider";
+ "com.android.webview.chromium.WebViewChromiumFactoryProvider";
private static final String CHROMIUM_WEBVIEW_JAR = "/system/framework/webviewchromium.jar";
private static final String LOGTAG = "WebViewFactory";
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index c9f9fbd..fa17ab9 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -240,7 +240,7 @@ public interface WebViewProvider {
public View findHierarchyView(String className, int hashCode);
//-------------------------------------------------------------------------
- // Provider glue methods
+ // Provider internal methods
//-------------------------------------------------------------------------
/**
@@ -255,6 +255,12 @@ public interface WebViewProvider {
*/
/* package */ ScrollDelegate getScrollDelegate();
+ /**
+ * Only used by FindActionModeCallback to inform providers that the find dialog has
+ * been dismissed.
+ */
+ public void notifyFindDialogDismissed();
+
//-------------------------------------------------------------------------
// View / ViewGroup delegation methods
//-------------------------------------------------------------------------
@@ -341,6 +347,8 @@ public interface WebViewProvider {
public void setBackgroundColor(int color);
public void setLayerType(int layerType, Paint paint);
+
+ public void preDispatchDraw(Canvas canvas);
}
interface ScrollDelegate {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 57bf0d3..396fd68 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -1374,9 +1374,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (isEnabled()) {
if (getFirstVisiblePosition() > 0) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.setScrollable(true);
}
if (getLastVisiblePosition() < getCount() - 1) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.setScrollable(true);
}
}
}
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 3b5e75b..7674837 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -305,7 +305,7 @@ public abstract class AbsSeekBar extends ProgressBar {
}
// Canvas will be translated, so 0,0 is where we start drawing
- final int left = isLayoutRtl() ? available - thumbPos : thumbPos;
+ final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
thumb.setBounds(left, topBound, left + thumbWidth, bottomBound);
}
@@ -426,7 +426,7 @@ public abstract class AbsSeekBar extends ProgressBar {
int x = (int)event.getX();
float scale;
float progress = 0;
- if (isLayoutRtl()) {
+ if (isLayoutRtl() && mMirrorForRtl) {
if (x > width - mPaddingRight) {
scale = 0.0f;
} else if (x < mPaddingLeft) {
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index f279f8e..a379157 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -375,7 +375,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
/**
* Constructor called from {@link #CREATOR}
*/
- private SavedState(Parcel in) {
+ SavedState(Parcel in) {
super(in);
selectedId = in.readLong();
position = in.readInt();
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 44f04db..97926a7 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -97,11 +97,11 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* Constructor
*
* @param context The current context.
- * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+ * @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
*/
- public ArrayAdapter(Context context, int textViewResourceId) {
- init(context, textViewResourceId, 0, new ArrayList<T>());
+ public ArrayAdapter(Context context, int resource) {
+ init(context, resource, 0, new ArrayList<T>());
}
/**
@@ -120,12 +120,12 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* Constructor
*
* @param context The current context.
- * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+ * @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
- init(context, textViewResourceId, 0, Arrays.asList(objects));
+ public ArrayAdapter(Context context, int resource, T[] objects) {
+ init(context, resource, 0, Arrays.asList(objects));
}
/**
@@ -145,12 +145,12 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* Constructor
*
* @param context The current context.
- * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
+ * @param resource The resource ID for a layout file containing a TextView to use when
* instantiating views.
* @param objects The objects to represent in the ListView.
*/
- public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
- init(context, textViewResourceId, 0, objects);
+ public ArrayAdapter(Context context, int resource, List<T> objects) {
+ init(context, resource, 0, objects);
}
/**
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index f1804f8..41ab5f2 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.util.ValueModel;
/**
@@ -55,7 +56,9 @@ import android.view.accessibility.AccessibilityNodeInfo;
* {@link android.R.styleable#View View Attributes}
* </p>
*/
-public class CheckBox extends CompoundButton {
+public class CheckBox extends CompoundButton implements ValueEditor<Boolean> {
+ private ValueModel<Boolean> mValueModel = ValueModel.EMPTY;
+
public CheckBox(Context context) {
this(context, null);
}
@@ -79,4 +82,22 @@ public class CheckBox extends CompoundButton {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(CheckBox.class.getName());
}
+
+ @Override
+ public ValueModel<Boolean> getValueModel() {
+ return mValueModel;
+ }
+
+ @Override
+ public void setValueModel(ValueModel<Boolean> valueModel) {
+ mValueModel = valueModel;
+ setChecked(mValueModel.get());
+ }
+
+ @Override
+ public boolean performClick() {
+ boolean handled = super.performClick();
+ mValueModel.set(isChecked());
+ return handled;
+ }
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index 57e51c2..ec81214 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -17,6 +17,7 @@
package android.widget;
import android.content.Context;
+import android.graphics.Rect;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
@@ -24,6 +25,7 @@ import android.text.TextUtils;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.MovementMethod;
import android.util.AttributeSet;
+import android.util.ValueModel;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -47,7 +49,9 @@ import android.view.accessibility.AccessibilityNodeInfo;
* {@link android.R.styleable#TextView TextView Attributes},
* {@link android.R.styleable#View View Attributes}
*/
-public class EditText extends TextView {
+public class EditText extends TextView implements ValueEditor<CharSequence> {
+ private ValueModel<CharSequence> mValueModel = ValueModel.EMPTY;
+
public EditText(Context context) {
this(context, null);
}
@@ -128,4 +132,21 @@ public class EditText extends TextView {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(EditText.class.getName());
}
+
+ @Override
+ public ValueModel<CharSequence> getValueModel() {
+ return mValueModel;
+ }
+
+ @Override
+ public void setValueModel(ValueModel<CharSequence> valueModel) {
+ mValueModel = valueModel;
+ setText(mValueModel.get());
+ }
+
+ @Override
+ void sendAfterTextChanged(Editable text) {
+ super.sendAfterTextChanged(text);
+ mValueModel.set(text);
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8892316..dc305a5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -20,6 +20,8 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.EditableInputConnection;
import android.R;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.Context;
@@ -124,7 +126,6 @@ public class Editor {
InputMethodState mInputMethodState;
DisplayList[] mTextDisplayLists;
- int mLastLayoutHeight;
boolean mFrozenWithFocus;
boolean mSelectionMoved;
@@ -1289,20 +1290,11 @@ public class Editor {
mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)];
}
- // If the height of the layout changes (usually when inserting or deleting a line,
- // but could be changes within a span), invalidate everything. We could optimize
- // more aggressively (for example, adding offsets to blocks) but it would be more
- // complex and we would only get the benefit in some cases.
- int layoutHeight = layout.getHeight();
- if (mLastLayoutHeight != layoutHeight) {
- invalidateTextDisplayList();
- mLastLayoutHeight = layoutHeight;
- }
-
DynamicLayout dynamicLayout = (DynamicLayout) layout;
int[] blockEndLines = dynamicLayout.getBlockEndLines();
int[] blockIndices = dynamicLayout.getBlockIndices();
final int numberOfBlocks = dynamicLayout.getNumberOfBlocks();
+ final int indexFirstChangedBlock = dynamicLayout.getIndexFirstChangedBlock();
int endOfPreviousBlock = -1;
int searchStartIndex = 0;
@@ -1324,10 +1316,11 @@ public class Editor {
blockDisplayList = mTextDisplayLists[blockIndex] =
mTextView.getHardwareRenderer().createDisplayList("Text " + blockIndex);
} else {
- if (blockIsInvalid) blockDisplayList.invalidate();
+ if (blockIsInvalid) blockDisplayList.clear();
}
- if (!blockDisplayList.isValid()) {
+ final boolean blockDisplayListIsInvalid = !blockDisplayList.isValid();
+ if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) {
final int blockBeginLine = endOfPreviousBlock + 1;
final int top = layout.getLineTop(blockBeginLine);
final int bottom = layout.getLineBottom(blockEndLine);
@@ -1344,24 +1337,27 @@ public class Editor {
right = (int) (max + 0.5f);
}
- final HardwareCanvas hardwareCanvas = blockDisplayList.start();
- try {
- // Tighten the bounds of the viewport to the actual text size
- hardwareCanvas.setViewport(right - left, bottom - top);
- // The dirty rect should always be null for a display list
- hardwareCanvas.onPreDraw(null);
- // drawText is always relative to TextView's origin, this translation brings
- // this range of text back to the top left corner of the viewport
- hardwareCanvas.translate(-left, -top);
- layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine);
- // No need to untranslate, previous context is popped after drawDisplayList
- } finally {
- hardwareCanvas.onPostDraw();
- blockDisplayList.end();
- blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
- // Same as drawDisplayList below, handled by our TextView's parent
- blockDisplayList.setClipChildren(false);
+ // Rebuild display list if it is invalid
+ if (blockDisplayListIsInvalid) {
+ final HardwareCanvas hardwareCanvas = blockDisplayList.start(
+ right - left, bottom - top);
+ try {
+ // drawText is always relative to TextView's origin, this translation
+ // brings this range of text back to the top left corner of the viewport
+ hardwareCanvas.translate(-left, -top);
+ layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine);
+ // No need to untranslate, previous context is popped after
+ // drawDisplayList
+ } finally {
+ blockDisplayList.end();
+ // Same as drawDisplayList below, handled by our TextView's parent
+ blockDisplayList.setClipChildren(false);
+ }
}
+
+ // Valid disply list whose index is >= indexFirstChangedBlock
+ // only needs to update its drawing location.
+ blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
}
((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, null,
@@ -1369,6 +1365,8 @@ public class Editor {
endOfPreviousBlock = blockEndLine;
}
+
+ dynamicLayout.setIndexFirstChangedBlock(numberOfBlocks);
} else {
// Boring layout is used for empty and hint text
layout.drawText(canvas, firstLine, lastLine);
@@ -1431,7 +1429,7 @@ public class Editor {
while (i < numberOfBlocks) {
final int blockIndex = blockIndices[i];
if (blockIndex != DynamicLayout.INVALID_BLOCK_INDEX) {
- mTextDisplayLists[blockIndex].invalidate();
+ mTextDisplayLists[blockIndex].clear();
}
if (blockEndLines[i] >= lastLine) break;
i++;
@@ -1442,7 +1440,7 @@ public class Editor {
void invalidateTextDisplayList() {
if (mTextDisplayLists != null) {
for (int i = 0; i < mTextDisplayLists.length; i++) {
- if (mTextDisplayLists[i] != null) mTextDisplayLists[i].invalidate();
+ if (mTextDisplayLists[i] != null) mTextDisplayLists[i].clear();
}
}
}
@@ -1468,20 +1466,24 @@ public class Editor {
middle = (top + bottom) >> 1;
}
- updateCursorPosition(0, top, middle, getPrimaryHorizontal(layout, hintLayout, offset));
+ boolean clamped = layout.shouldClampCursor(line);
+ updateCursorPosition(0, top, middle,
+ getPrimaryHorizontal(layout, hintLayout, offset, clamped));
if (mCursorCount == 2) {
- updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset));
+ updateCursorPosition(1, middle, bottom,
+ layout.getSecondaryHorizontal(offset, clamped));
}
}
- private float getPrimaryHorizontal(Layout layout, Layout hintLayout, int offset) {
+ private float getPrimaryHorizontal(Layout layout, Layout hintLayout, int offset,
+ boolean clamped) {
if (TextUtils.isEmpty(layout.getText()) &&
hintLayout != null &&
!TextUtils.isEmpty(hintLayout.getText())) {
- return hintLayout.getPrimaryHorizontal(offset);
+ return hintLayout.getPrimaryHorizontal(offset, clamped);
} else {
- return layout.getPrimaryHorizontal(offset);
+ return layout.getPrimaryHorizontal(offset, clamped);
}
}
@@ -1890,10 +1892,23 @@ public class Editor {
// Make sure there is only at most one EasyEditSpan in the text
if (mPopupWindow.mEasyEditSpan != null) {
- text.removeSpan(mPopupWindow.mEasyEditSpan);
+ mPopupWindow.mEasyEditSpan.setDeleteEnabled(false);
}
mPopupWindow.setEasyEditSpan((EasyEditSpan) span);
+ mPopupWindow.setOnDeleteListener(new EasyEditDeleteListener() {
+ @Override
+ public void onDeleteClick(EasyEditSpan span) {
+ Editable editable = (Editable) mTextView.getText();
+ int start = editable.getSpanStart(span);
+ int end = editable.getSpanEnd(span);
+ if (start >= 0 && end >= 0) {
+ sendNotification(EasyEditSpan.TEXT_DELETED, span);
+ mTextView.deleteText_internal(start, end);
+ }
+ editable.removeSpan(span);
+ }
+ });
if (mTextView.getWindowVisibility() != View.VISIBLE) {
// The window is not visible yet, ignore the text change.
@@ -1927,8 +1942,10 @@ public class Editor {
@Override
public void onSpanChanged(Spannable text, Object span, int previousStart, int previousEnd,
int newStart, int newEnd) {
- if (mPopupWindow != null && span == mPopupWindow.mEasyEditSpan) {
- text.removeSpan(mPopupWindow.mEasyEditSpan);
+ if (mPopupWindow != null && span instanceof EasyEditSpan) {
+ EasyEditSpan easyEditSpan = (EasyEditSpan) span;
+ sendNotification(EasyEditSpan.TEXT_MODIFIED, easyEditSpan);
+ text.removeSpan(easyEditSpan);
}
}
@@ -1938,6 +1955,31 @@ public class Editor {
mTextView.removeCallbacks(mHidePopup);
}
}
+
+ private void sendNotification(int textChangedType, EasyEditSpan span) {
+ try {
+ PendingIntent pendingIntent = span.getPendingIntent();
+ if (pendingIntent != null) {
+ Intent intent = new Intent();
+ intent.putExtra(EasyEditSpan.EXTRA_TEXT_CHANGED_TYPE, textChangedType);
+ pendingIntent.send(mTextView.getContext(), 0, intent);
+ }
+ } catch (CanceledException e) {
+ // This should not happen, as we should try to send the intent only once.
+ Log.w(TAG, "PendingIntent for notification cannot be sent", e);
+ }
+ }
+ }
+
+ /**
+ * Listens for the delete event triggered by {@link EasyEditPopupWindow}.
+ */
+ private interface EasyEditDeleteListener {
+
+ /**
+ * Clicks the delete pop-up.
+ */
+ void onDeleteClick(EasyEditSpan span);
}
/**
@@ -1950,6 +1992,7 @@ public class Editor {
com.android.internal.R.layout.text_edit_action_popup_text;
private TextView mDeleteTextView;
private EasyEditSpan mEasyEditSpan;
+ private EasyEditDeleteListener mOnDeleteListener;
@Override
protected void createPopupWindow() {
@@ -1984,16 +2027,26 @@ public class Editor {
mEasyEditSpan = easyEditSpan;
}
+ private void setOnDeleteListener(EasyEditDeleteListener listener) {
+ mOnDeleteListener = listener;
+ }
+
@Override
public void onClick(View view) {
- if (view == mDeleteTextView) {
- Editable editable = (Editable) mTextView.getText();
- int start = editable.getSpanStart(mEasyEditSpan);
- int end = editable.getSpanEnd(mEasyEditSpan);
- if (start >= 0 && end >= 0) {
- mTextView.deleteText_internal(start, end);
- }
+ if (view == mDeleteTextView
+ && mEasyEditSpan != null && mEasyEditSpan.isDeleteEnabled()
+ && mOnDeleteListener != null) {
+ mOnDeleteListener.onDeleteClick(mEasyEditSpan);
+ }
+ }
+
+ @Override
+ public void hide() {
+ if (mEasyEditSpan != null) {
+ mEasyEditSpan.setDeleteEnabled(false);
}
+ mOnDeleteListener = null;
+ super.hide();
}
@Override
@@ -2647,15 +2700,10 @@ public class Editor {
suggestionStart, suggestionEnd).toString();
mTextView.replaceText_internal(spanStart, spanEnd, suggestion);
- // Notify source IME of the suggestion pick. Do this before swaping texts.
- if (!TextUtils.isEmpty(
- suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText,
- suggestionInfo.suggestionIndex);
- }
- }
+ // Notify source IME of the suggestion pick. Do this before
+ // swaping texts.
+ suggestionInfo.suggestionSpan.notifySelection(
+ mTextView.getContext(), originalText, suggestionInfo.suggestionIndex);
// Swap text content between actual text and Suggestion span
String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions();
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index e0c5bbd..c4ef11c 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -891,7 +891,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
lp = (Gallery.LayoutParams) generateDefaultLayoutParams();
}
- addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp);
+ addViewInLayout(child, fromLeft != mIsRtl ? -1 : 0, lp, true);
child.setSelected(offset == 0);
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 772d748..85ed8db 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -605,7 +605,7 @@ public class GridLayout extends ViewGroup {
}
private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
- return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, horizontal, leading);
+ return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
}
private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
@@ -824,13 +824,11 @@ public class GridLayout extends ViewGroup {
// Draw grid
private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
- int dx = getPaddingLeft();
- int dy = getPaddingTop();
if (isLayoutRtl()) {
int width = getWidth();
- graphics.drawLine(width - dx - x1, dy + y1, width - dx - x2, dy + y2, paint);
+ graphics.drawLine(width - x1, y1, width - x2, y2, paint);
} else {
- graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint);
+ graphics.drawLine(x1, y1, x2, y2, paint);
}
}
@@ -838,18 +836,17 @@ public class GridLayout extends ViewGroup {
* @hide
*/
@Override
- protected void onDebugDrawMargins(Canvas canvas) {
+ protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
// Apply defaults, so as to remove UNDEFINED values
LayoutParams lp = new LayoutParams();
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
- Insets insets = getLayoutMode() == OPTICAL_BOUNDS ? c.getOpticalInsets() : Insets.NONE;
lp.setMargins(
- getMargin1(c, true, true) - insets.left,
- getMargin1(c, false, true) - insets.top,
- getMargin1(c, true, false) - insets.right,
- getMargin1(c, false, false) - insets.bottom);
- lp.onDebugDraw(c, canvas);
+ getMargin1(c, true, true),
+ getMargin1(c, false, true),
+ getMargin1(c, true, false),
+ getMargin1(c, false, false));
+ lp.onDebugDraw(c, canvas, paint);
}
}
@@ -858,26 +855,30 @@ public class GridLayout extends ViewGroup {
*/
@Override
protected void onDebugDraw(Canvas canvas) {
- int height = getHeight() - getPaddingTop() - getPaddingBottom();
- int width = getWidth() - getPaddingLeft() - getPaddingRight();
-
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.argb(50, 255, 255, 255));
+ Insets insets = getOpticalInsets();
+
+ int top = getPaddingTop() + insets.top;
+ int left = getPaddingLeft() + insets.left;
+ int right = getWidth() - getPaddingRight() - insets.right;
+ int bottom = getHeight() - getPaddingBottom() - insets.bottom;
+
int[] xs = horizontalAxis.locations;
if (xs != null) {
for (int i = 0, length = xs.length; i < length; i++) {
- int x = xs[i];
- drawLine(canvas, x, 0, x, height - 1, paint);
+ int x = left + xs[i];
+ drawLine(canvas, x, top, x, bottom, paint);
}
}
int[] ys = verticalAxis.locations;
if (ys != null) {
for (int i = 0, length = ys.length; i < length; i++) {
- int y = ys[i];
- drawLine(canvas, 0, y, width - 1, y, paint);
+ int y = top + ys[i];
+ drawLine(canvas, left, y, right, y, paint);
}
}
@@ -1013,12 +1014,7 @@ public class GridLayout extends ViewGroup {
}
private int getMeasurement(View c, boolean horizontal) {
- int result = horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
- if (getLayoutMode() == OPTICAL_BOUNDS) {
- Insets insets = c.getOpticalInsets();
- return result - (horizontal ? insets.left + insets.right : insets.top + insets.bottom);
- }
- return result;
+ return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight();
}
final int getMeasurementIncludingMargin(View c, boolean horizontal) {
@@ -1124,14 +1120,6 @@ public class GridLayout extends ViewGroup {
targetWidth - width - paddingRight - rightMargin - dx;
int cy = paddingTop + y1 + gravityOffsetY + alignmentOffsetY + topMargin;
- boolean useLayoutBounds = getLayoutMode() == OPTICAL_BOUNDS;
- if (useLayoutBounds) {
- Insets insets = c.getOpticalInsets();
- cx -= insets.left;
- cy -= insets.top;
- width += (insets.left + insets.right);
- height += (insets.top + insets.bottom);
- }
if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) {
c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
}
@@ -2418,6 +2406,8 @@ public class GridLayout extends ViewGroup {
* <li> {@code spec.span = [start, start + size]} </li>
* <li> {@code spec.alignment = alignment} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start
* @param size the size
@@ -2433,9 +2423,13 @@ public class GridLayout extends ViewGroup {
* <li> {@code spec.span = [start, start + 1]} </li>
* <li> {@code spec.alignment = alignment} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start index
* @param alignment the alignment
+ *
+ * @see #spec(int, int, Alignment)
*/
public static Spec spec(int start, Alignment alignment) {
return spec(start, 1, alignment);
@@ -2446,9 +2440,13 @@ public class GridLayout extends ViewGroup {
* <ul>
* <li> {@code spec.span = [start, start + size]} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start
* @param size the size
+ *
+ * @see #spec(int, Alignment)
*/
public static Spec spec(int start, int size) {
return spec(start, size, UNDEFINED_ALIGNMENT);
@@ -2459,8 +2457,12 @@ public class GridLayout extends ViewGroup {
* <ul>
* <li> {@code spec.span = [start, start + 1]} </li>
* </ul>
+ * <p>
+ * To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start index
+ *
+ * @see #spec(int, int)
*/
public static Spec spec(int start) {
return spec(start, 1);
@@ -2654,14 +2656,7 @@ public class GridLayout extends ViewGroup {
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
int baseline = view.getBaseline();
- if (baseline == -1) {
- return UNDEFINED;
- } else {
- if (mode == OPTICAL_BOUNDS) {
- return baseline - view.getOpticalInsets().top;
- }
- return baseline;
- }
+ return baseline == -1 ? UNDEFINED : baseline;
}
@Override
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 26c801f..1bbf4eb 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -30,6 +30,7 @@ import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -40,6 +41,9 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
+import java.io.IOException;
+import java.io.InputStream;
+
/**
* Displays an arbitrary image, such as an icon. The ImageView class
* can load images from various sources (such as resources or content
@@ -90,6 +94,9 @@ public class ImageView extends View {
private int mBaseline = -1;
private boolean mBaselineAlignBottom = false;
+ // AdjustViewBounds behavior will be in compatibility mode for older apps.
+ private boolean mAdjustViewBoundsCompat = false;
+
private static final ScaleType[] sScaleTypeArray = {
ScaleType.MATRIX,
ScaleType.FIT_XY,
@@ -164,6 +171,8 @@ public class ImageView extends View {
private void initImageView() {
mMatrix = new Matrix();
mScaleType = ScaleType.FIT_CENTER;
+ mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <=
+ Build.VERSION_CODES.JELLY_BEAN_MR1;
}
@Override
@@ -225,8 +234,15 @@ public class ImageView extends View {
/**
* Set this to true if you want the ImageView to adjust its bounds
* to preserve the aspect ratio of its drawable.
+ *
+ * <p><strong>Note:</strong> If the application targets API level 17 or lower,
+ * adjustViewBounds will allow the drawable to shrink the view bounds, but not grow
+ * to fill available measured space in all cases. This is for compatibility with
+ * legacy {@link android.view.View.MeasureSpec MeasureSpec} and
+ * {@link android.widget.RelativeLayout RelativeLayout} behavior.</p>
+ *
* @param adjustViewBounds Whether to adjust the bounds of this view
- * to presrve the original aspect ratio of the drawable
+ * to preserve the original aspect ratio of the drawable.
*
* @see #getAdjustViewBounds()
*
@@ -546,13 +562,14 @@ public class ImageView extends View {
/** Return the view's optional matrix. This is applied to the
view's drawable when it is drawn. If there is not matrix,
- this method will return null.
- Do not change this matrix in place. If you want a different matrix
- applied to the drawable, be sure to call setImageMatrix().
+ this method will return an identity matrix.
+ Do not change this matrix in place but make a copy.
+ If you want a different matrix applied to the drawable,
+ be sure to call setImageMatrix().
*/
public Matrix getImageMatrix() {
if (mDrawMatrix == null) {
- return Matrix.IDENTITY_MATRIX;
+ return new Matrix(Matrix.IDENTITY_MATRIX);
}
return mDrawMatrix;
}
@@ -635,20 +652,27 @@ public class ImageView extends View {
}
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
|| ContentResolver.SCHEME_FILE.equals(scheme)) {
+ InputStream stream = null;
try {
- d = Drawable.createFromStream(
- mContext.getContentResolver().openInputStream(mUri),
- null);
+ stream = mContext.getContentResolver().openInputStream(mUri);
+ d = Drawable.createFromStream(stream, null);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ Log.w("ImageView", "Unable to close content: " + mUri, e);
+ }
+ }
}
- } else {
+ } else {
d = Drawable.createFromPath(mUri.toString());
}
if (d == null) {
- System.out.println("resolveUri failed on bad bitmap uri: "
- + mUri);
+ System.out.println("resolveUri failed on bad bitmap uri: " + mUri);
// Don't try again.
mUri = null;
}
@@ -792,6 +816,12 @@ public class ImageView extends View {
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
+
+ // Allow the width to outgrow its original estimate if height is fixed.
+ if (!resizeHeight && !mAdjustViewBoundsCompat) {
+ widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
+ }
+
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
@@ -802,6 +832,13 @@ public class ImageView extends View {
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
+
+ // Allow the height to outgrow its original estimate if width is fixed.
+ if (!resizeWidth && !mAdjustViewBoundsCompat) {
+ heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
+ heightMeasureSpec);
+ }
+
if (newHeight <= heightSize) {
heightSize = newHeight;
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 36dd13c..bc57c36 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -1431,9 +1431,9 @@ public class LinearLayout extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
- layoutVertical();
+ layoutVertical(l, t, r, b);
} else {
- layoutHorizontal();
+ layoutHorizontal(l, t, r, b);
}
}
@@ -1444,15 +1444,19 @@ public class LinearLayout extends ViewGroup {
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
*/
- void layoutVertical() {
+ void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
- final int width = mRight - mLeft;
+ final int width = right - left;
int childRight = width - mPaddingRight;
// Space available for child
@@ -1466,12 +1470,12 @@ public class LinearLayout extends ViewGroup {
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
- childTop = mPaddingTop + mBottom - mTop - mTotalLength;
+ childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
- childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;
+ childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
@@ -1534,8 +1538,12 @@ public class LinearLayout extends ViewGroup {
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
+ * @param left
+ * @param top
+ * @param right
+ * @param bottom
*/
- void layoutHorizontal() {
+ void layoutHorizontal(int left, int top, int right, int bottom) {
final boolean isLayoutRtl = isLayoutRtl();
final int paddingTop = mPaddingTop;
@@ -1543,7 +1551,7 @@ public class LinearLayout extends ViewGroup {
int childLeft;
// Where bottom of child should go
- final int height = mBottom - mTop;
+ final int height = bottom - top;
int childBottom = height - mPaddingBottom;
// Space available for child
@@ -1563,12 +1571,12 @@ public class LinearLayout extends ViewGroup {
switch (Gravity.getAbsoluteGravity(majorGravity, layoutDirection)) {
case Gravity.RIGHT:
// mTotalLength contains the padding already
- childLeft = mPaddingLeft + mRight - mLeft - mTotalLength;
+ childLeft = mPaddingLeft + right - left - mTotalLength;
break;
case Gravity.CENTER_HORIZONTAL:
// mTotalLength contains the padding already
- childLeft = mPaddingLeft + (mRight - mLeft - mTotalLength) / 2;
+ childLeft = mPaddingLeft + (right - left - mTotalLength) / 2;
break;
case Gravity.LEFT:
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 4436fbb..69e3177 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -2429,9 +2429,7 @@ public class ListView extends AbsListView {
View selectedView = getSelectedView();
int selectedPos = mSelectedPosition;
- int nextSelectedPosition = (direction == View.FOCUS_DOWN) ?
- lookForSelectablePosition(selectedPos + 1, true) :
- lookForSelectablePosition(selectedPos - 1, false);
+ int nextSelectedPosition = lookForSelectablePositionOnScreen(direction);
int amountToScroll = amountToScroll(direction, nextSelectedPosition);
// if we are moving focus, we may OVERRIDE the default behavior
@@ -2643,18 +2641,14 @@ public class ListView extends AbsListView {
final int listBottom = getHeight() - mListPadding.bottom;
final int listTop = mListPadding.top;
- int numChildren = getChildCount();
+ final int numChildren = getChildCount();
if (direction == View.FOCUS_DOWN) {
int indexToMakeVisible = numChildren - 1;
if (nextSelectedPosition != INVALID_POSITION) {
indexToMakeVisible = nextSelectedPosition - mFirstPosition;
}
- while (numChildren <= indexToMakeVisible) {
- // Child to view is not attached yet.
- addViewBelow(getChildAt(numChildren - 1), mFirstPosition + numChildren - 1);
- numChildren++;
- }
+
final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
final View viewToMakeVisible = getChildAt(indexToMakeVisible);
@@ -2688,12 +2682,6 @@ public class ListView extends AbsListView {
if (nextSelectedPosition != INVALID_POSITION) {
indexToMakeVisible = nextSelectedPosition - mFirstPosition;
}
- while (indexToMakeVisible < 0) {
- // Child to view is not attached yet.
- addViewAbove(getChildAt(0), mFirstPosition);
- mFirstPosition--;
- indexToMakeVisible = nextSelectedPosition - mFirstPosition;
- }
final int positionToMakeVisible = mFirstPosition + indexToMakeVisible;
final View viewToMakeVisible = getChildAt(indexToMakeVisible);
int goalTop = listTop;
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index f76ab2b..ee1bf18 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -149,7 +149,7 @@ public class MediaController extends FrameLayout {
private void initFloatingWindowLayout() {
mDecorLayoutParams = new WindowManager.LayoutParams();
WindowManager.LayoutParams p = mDecorLayoutParams;
- p.gravity = Gravity.TOP;
+ p.gravity = Gravity.TOP | Gravity.LEFT;
p.height = LayoutParams.WRAP_CONTENT;
p.x = 0;
p.format = PixelFormat.TRANSLUCENT;
@@ -167,9 +167,15 @@ public class MediaController extends FrameLayout {
int [] anchorPos = new int[2];
mAnchor.getLocationOnScreen(anchorPos);
+ // we need to know the size of the controller so we can properly position it
+ // within its space
+ mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST));
+
WindowManager.LayoutParams p = mDecorLayoutParams;
p.width = mAnchor.getWidth();
- p.y = anchorPos[1] + mAnchor.getHeight();
+ p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2;
+ p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight();
}
// This is called whenever mAnchor's layout bound changes
@@ -204,6 +210,8 @@ public class MediaController extends FrameLayout {
/**
* Set the view that acts as the anchor for the control view.
* This can for example be a VideoView, or your Activity's main view.
+ * When VideoView calls this method, it will use the VideoView's parent
+ * as the anchor.
* @param view The view to which to anchor the controller when it is visible.
*/
public void setAnchorView(View view) {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index ea50e2e..d816200 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -38,10 +38,7 @@ import android.graphics.drawable.shapes.Shape;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SynchronizedPool;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -226,6 +223,8 @@ public class ProgressBar extends View {
private boolean mAttached;
private boolean mRefreshIsPosted;
+ boolean mMirrorForRtl = false;
+
private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
private AccessibilityEventSender mAccessibilityEventSender;
@@ -305,6 +304,8 @@ public class ProgressBar extends View {
setIndeterminate(mOnlyIndeterminate || a.getBoolean(
R.styleable.ProgressBar_indeterminate, mIndeterminate));
+ mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl);
+
a.recycle();
}
@@ -604,33 +605,20 @@ public class ProgressBar extends View {
}
}
- private static class RefreshData implements Poolable<RefreshData> {
+ private static class RefreshData {
+ private static final int POOL_MAX = 24;
+ private static final SynchronizedPool<RefreshData> sPool =
+ new SynchronizedPool<RefreshData>(POOL_MAX);
+
public int id;
public int progress;
public boolean fromUser;
-
- private RefreshData mNext;
- private boolean mIsPooled;
-
- private static final int POOL_MAX = 24;
- private static final Pool<RefreshData> sPool = Pools.synchronizedPool(
- Pools.finitePool(new PoolableManager<RefreshData>() {
- @Override
- public RefreshData newInstance() {
- return new RefreshData();
- }
-
- @Override
- public void onAcquired(RefreshData element) {
- }
-
- @Override
- public void onReleased(RefreshData element) {
- }
- }, POOL_MAX));
public static RefreshData obtain(int id, int progress, boolean fromUser) {
RefreshData rd = sPool.acquire();
+ if (rd == null) {
+ rd = new RefreshData();
+ }
rd.id = id;
rd.progress = progress;
rd.fromUser = fromUser;
@@ -640,28 +628,8 @@ public class ProgressBar extends View {
public void recycle() {
sPool.release(this);
}
-
- @Override
- public void setNextPoolable(RefreshData element) {
- mNext = element;
- }
-
- @Override
- public RefreshData getNextPoolable() {
- return mNext;
- }
-
- @Override
- public boolean isPooled() {
- return mIsPooled;
- }
-
- @Override
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
}
-
+
private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
boolean callBackToApp) {
float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
@@ -1040,7 +1008,7 @@ public class ProgressBar extends View {
}
}
}
- if (isLayoutRtl()) {
+ if (isLayoutRtl() && mMirrorForRtl) {
int tempLeft = left;
left = w - right;
right = w - tempLeft;
@@ -1062,7 +1030,7 @@ public class ProgressBar extends View {
// Translate canvas so a indeterminate circular progress bar with padding
// rotates properly in its animation
canvas.save();
- if(isLayoutRtl()) {
+ if(isLayoutRtl() && mMirrorForRtl) {
canvas.translate(getWidth() - mPaddingRight, mPaddingTop);
canvas.scale(-1.0f, 1.0f);
} else {
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 786afe2..368f6ad 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -27,6 +27,7 @@ import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Intents;
@@ -50,6 +51,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
private Drawable mOverlay;
private QueryHandler mQueryHandler;
private Drawable mDefaultAvatar;
+ private Bundle mExtras = null;
protected String[] mExcludeMimes = null;
@@ -58,6 +60,8 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
static final private int TOKEN_EMAIL_LOOKUP_AND_TRIGGER = 2;
static final private int TOKEN_PHONE_LOOKUP_AND_TRIGGER = 3;
+ static final private String EXTRA_URI_CONTENT = "uri_content";
+
static final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
RawContacts.CONTACT_ID,
Contacts.LOOKUP_KEY,
@@ -175,7 +179,26 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
* until this view is clicked.
*/
public void assignContactFromEmail(String emailAddress, boolean lazyLookup) {
+ assignContactFromEmail(emailAddress, lazyLookup, null);
+ }
+
+ /**
+ * Assign a contact based on an email address. This should only be used when
+ * the contact's URI is not available, as an extra query will have to be
+ * performed to lookup the URI based on the email.
+
+ @param emailAddress The email address of the contact.
+ @param lazyLookup If this is true, the lookup query will not be performed
+ until this view is clicked.
+ @param extras A bundle of extras to populate the contact edit page with if the contact
+ is not found and the user chooses to add the email address to an existing contact or
+ create a new contact. Uses the same string constants as those found in
+ {@link android.provider.ContactsContract.Intents.Insert}
+ */
+
+ public void assignContactFromEmail(String emailAddress, boolean lazyLookup, Bundle extras) {
mContactEmail = emailAddress;
+ mExtras = extras;
if (!lazyLookup) {
mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP, null,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
@@ -186,6 +209,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
}
+
/**
* Assign a contact based on a phone number. This should only be used when
* the contact's URI is not available, as an extra query will have to be
@@ -196,7 +220,25 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
* until this view is clicked.
*/
public void assignContactFromPhone(String phoneNumber, boolean lazyLookup) {
+ assignContactFromPhone(phoneNumber, lazyLookup, new Bundle());
+ }
+
+ /**
+ * Assign a contact based on a phone number. This should only be used when
+ * the contact's URI is not available, as an extra query will have to be
+ * performed to lookup the URI based on the phone number.
+ *
+ * @param phoneNumber The phone number of the contact.
+ * @param lazyLookup If this is true, the lookup query will not be performed
+ * until this view is clicked.
+ * @param extras A bundle of extras to populate the contact edit page with if the contact
+ * is not found and the user chooses to add the phone number to an existing contact or
+ * create a new contact. Uses the same string constants as those found in
+ * {@link android.provider.ContactsContract.Intents.Insert}
+ */
+ public void assignContactFromPhone(String phoneNumber, boolean lazyLookup, Bundle extras) {
mContactPhone = phoneNumber;
+ mExtras = extras;
if (!lazyLookup) {
mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP, null,
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
@@ -213,15 +255,21 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
@Override
public void onClick(View v) {
+ // If contact has been assigned, mExtras should no longer be null, but do a null check
+ // anyway just in case assignContactFromPhone or Email was called with a null bundle or
+ // wasn't assigned previously.
+ final Bundle extras = (mExtras == null) ? new Bundle() : mExtras;
if (mContactUri != null) {
QuickContact.showQuickContact(getContext(), QuickContactBadge.this, mContactUri,
QuickContact.MODE_LARGE, mExcludeMimes);
} else if (mContactEmail != null) {
- mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, mContactEmail,
+ extras.putString(EXTRA_URI_CONTENT, mContactEmail);
+ mQueryHandler.startQuery(TOKEN_EMAIL_LOOKUP_AND_TRIGGER, extras,
Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mContactEmail)),
EMAIL_LOOKUP_PROJECTION, null, null, null);
} else if (mContactPhone != null) {
- mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, mContactPhone,
+ extras.putString(EXTRA_URI_CONTENT, mContactPhone);
+ mQueryHandler.startQuery(TOKEN_PHONE_LOOKUP_AND_TRIGGER, extras,
Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, mContactPhone),
PHONE_LOOKUP_PROJECTION, null, null, null);
} else {
@@ -262,12 +310,12 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
Uri lookupUri = null;
Uri createUri = null;
boolean trigger = false;
-
+ Bundle extras = (cookie != null) ? (Bundle) cookie : new Bundle();
try {
switch(token) {
case TOKEN_PHONE_LOOKUP_AND_TRIGGER:
trigger = true;
- createUri = Uri.fromParts("tel", (String)cookie, null);
+ createUri = Uri.fromParts("tel", extras.getString(EXTRA_URI_CONTENT), null);
//$FALL-THROUGH$
case TOKEN_PHONE_LOOKUP: {
@@ -281,7 +329,8 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
case TOKEN_EMAIL_LOOKUP_AND_TRIGGER:
trigger = true;
- createUri = Uri.fromParts("mailto", (String)cookie, null);
+ createUri = Uri.fromParts("mailto",
+ extras.getString(EXTRA_URI_CONTENT), null);
//$FALL-THROUGH$
case TOKEN_EMAIL_LOOKUP: {
@@ -309,6 +358,10 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
} else if (createUri != null) {
// Prompt user to add this person to contacts
final Intent intent = new Intent(Intents.SHOW_OR_CREATE_CONTACT, createUri);
+ if (extras != null) {
+ extras.remove(EXTRA_URI_CONTENT);
+ intent.putExtras(extras);
+ }
getContext().startActivity(intent);
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 27fda24..deec41c 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -29,16 +29,15 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.os.Build;
import android.util.AttributeSet;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
+import android.util.Pools.SimplePool;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.RemoteViews.RemoteView;
@@ -56,6 +55,21 @@ import static android.util.Log.d;
* {@link #ALIGN_PARENT_BOTTOM}.
* </p>
*
+ * <p><strong>Note:</strong> In platform version 17 and lower, RelativeLayout was affected by
+ * a measurement bug that could cause child views to be measured with incorrect
+ * {@link android.view.View.MeasureSpec MeasureSpec} values. (See
+ * {@link android.view.View.MeasureSpec#makeMeasureSpec(int, int) MeasureSpec.makeMeasureSpec}
+ * for more details.) This was triggered when a RelativeLayout container was placed in
+ * a scrolling container, such as a ScrollView or HorizontalScrollView. If a custom view
+ * not equipped to properly measure with the MeasureSpec mode
+ * {@link android.view.View.MeasureSpec#UNSPECIFIED UNSPECIFIED} was placed in a RelativeLayout,
+ * this would silently work anyway as RelativeLayout would pass a very large
+ * {@link android.view.View.MeasureSpec#AT_MOST AT_MOST} MeasureSpec instead.</p>
+ *
+ * <p>This behavior has been preserved for apps that set <code>android:targetSdkVersion="17"</code>
+ * or older in their manifest's <code>uses-sdk</code> tag for compatibility. Apps targeting SDK
+ * version 18 or newer will receive the correct behavior</p>
+ *
* <p>See the <a href="{@docRoot}guide/topics/ui/layout/relative.html">Relative
* Layout</a> guide.</p>
*
@@ -202,18 +216,34 @@ public class RelativeLayout extends ViewGroup {
private View[] mSortedVerticalChildren = new View[0];
private final DependencyGraph mGraph = new DependencyGraph();
+ // Compatibility hack. Old versions of the platform had problems
+ // with MeasureSpec value overflow and RelativeLayout was one source of them.
+ // Some apps came to rely on them. :(
+ private boolean mAllowBrokenMeasureSpecs = false;
+
+ private int mDisplayWidth;
+
public RelativeLayout(Context context) {
super(context);
+ mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <=
+ Build.VERSION_CODES.JELLY_BEAN_MR1;
+ getDisplayWidth();
}
public RelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initFromAttributes(context, attrs);
+ mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <=
+ Build.VERSION_CODES.JELLY_BEAN_MR1;
+ getDisplayWidth();
}
public RelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initFromAttributes(context, attrs);
+ mAllowBrokenMeasureSpecs = context.getApplicationInfo().targetSdkVersion <=
+ Build.VERSION_CODES.JELLY_BEAN_MR1;
+ getDisplayWidth();
}
private void initFromAttributes(Context context, AttributeSet attrs) {
@@ -223,6 +253,11 @@ public class RelativeLayout extends ViewGroup {
a.recycle();
}
+ private void getDisplayWidth() {
+ WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ mDisplayWidth = wm.getDefaultDisplay().getWidth();
+ }
+
@Override
public boolean shouldDelayChildPressedState() {
return false;
@@ -414,51 +449,28 @@ public class RelativeLayout extends ViewGroup {
final boolean isWrapContentWidth = widthMode != MeasureSpec.EXACTLY;
final boolean isWrapContentHeight = heightMode != MeasureSpec.EXACTLY;
+ // We need to know our size for doing the correct computation of children positioning in RTL
+ // mode but there is no practical way to get it instead of running the code below.
+ // So, instead of running the code twice, we just set the width to the "display width"
+ // before the computation and then, as a last pass, we will update their real position with
+ // an offset equals to "displayWidth - width".
+ final int layoutDirection = getLayoutDirection();
+ if (isLayoutRtl() && myWidth == -1) {
+ myWidth = mDisplayWidth;
+ }
+
View[] views = mSortedHorizontalChildren;
int count = views.length;
- // We need to know our size for doing the correct computation of positioning in RTL mode
- if (isLayoutRtl() && (myWidth == -1 || isWrapContentWidth)) {
- int w = getPaddingStart() + getPaddingEnd();
- final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- for (int i = 0; i < count; i++) {
- View child = views[i];
- if (child.getVisibility() != GONE) {
- LayoutParams params = (LayoutParams) child.getLayoutParams();
- // Would be similar to a call to measureChildHorizontal(child, params, -1, myHeight)
- // but we cannot change for now the behavior of measureChildHorizontal() for
- // taking care or a "-1" for "mywidth" so use here our own version of that code.
- int childHeightMeasureSpec;
- if (params.width == LayoutParams.MATCH_PARENT) {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
- } else {
- childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
- }
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
-
- w += child.getMeasuredWidth();
- w += params.leftMargin + params.rightMargin;
- }
- }
- if (myWidth == -1) {
- // Easy case: "myWidth" was undefined before so use the width we have just computed
- myWidth = w;
- } else {
- // "myWidth" was defined before, so take the min of it and the computed width if it
- // is a non null one
- if (w > 0) {
- myWidth = Math.min(myWidth, w);
- }
- }
- }
-
for (int i = 0; i < count; i++) {
View child = views[i];
if (child.getVisibility() != GONE) {
LayoutParams params = (LayoutParams) child.getLayoutParams();
+ int[] rules = params.getRules(layoutDirection);
- applyHorizontalSizeRules(params, myWidth);
+ applyHorizontalSizeRules(params, myWidth, rules);
measureChildHorizontal(child, params, myWidth, myHeight);
+
if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
offsetHorizontalAxis = true;
}
@@ -480,7 +492,11 @@ public class RelativeLayout extends ViewGroup {
}
if (isWrapContentWidth) {
- width = Math.max(width, params.mRight);
+ if (isLayoutRtl()) {
+ width = Math.max(width, myWidth - params.mLeft);
+ } else {
+ width = Math.max(width, params.mRight);
+ }
}
if (isWrapContentHeight) {
@@ -519,8 +535,6 @@ public class RelativeLayout extends ViewGroup {
}
}
- final int layoutDirection = getLayoutDirection();
-
if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
@@ -610,6 +624,19 @@ public class RelativeLayout extends ViewGroup {
}
}
+ if (isLayoutRtl()) {
+ final int offsetWidth = myWidth - width;
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() != GONE) {
+ LayoutParams params = (LayoutParams) child.getLayoutParams();
+ params.mLeft -= offsetWidth;
+ params.mRight -= offsetWidth;
+ }
+ }
+
+ }
+
setMeasuredDimension(width, height);
}
@@ -673,7 +700,17 @@ public class RelativeLayout extends ViewGroup {
mPaddingLeft, mPaddingRight,
myWidth);
int childHeightMeasureSpec;
- if (params.width == LayoutParams.MATCH_PARENT) {
+ if (myHeight < 0 && !mAllowBrokenMeasureSpecs) {
+ if (params.height >= 0) {
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ params.height, MeasureSpec.EXACTLY);
+ } else {
+ // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement
+ // is code for, "we got an unspecified mode in the RelativeLayout's measurespec."
+ // Carry it forward.
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+ } else if (params.width == LayoutParams.MATCH_PARENT) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(myHeight, MeasureSpec.AT_MOST);
@@ -700,6 +737,16 @@ public class RelativeLayout extends ViewGroup {
private int getChildMeasureSpec(int childStart, int childEnd,
int childSize, int startMargin, int endMargin, int startPadding,
int endPadding, int mySize) {
+ if (mySize < 0 && !mAllowBrokenMeasureSpecs) {
+ if (childSize >= 0) {
+ return MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.EXACTLY);
+ }
+ // Negative values in a mySize/myWidth/myWidth value in RelativeLayout measurement
+ // is code for, "we got an unspecified mode in the RelativeLayout's measurespec."
+ // Carry it forward.
+ return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ }
+
int childSpecMode = 0;
int childSpecSize = 0;
@@ -826,9 +873,7 @@ public class RelativeLayout extends ViewGroup {
return rules[ALIGN_PARENT_BOTTOM] != 0;
}
- private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth) {
- final int layoutDirection = getLayoutDirection();
- int[] rules = childParams.getRules(layoutDirection);
+ private void applyHorizontalSizeRules(LayoutParams childParams, int myWidth, int[] rules) {
RelativeLayout.LayoutParams anchorParams;
// -1 indicated a "soft requirement" in that direction. For example:
@@ -991,7 +1036,7 @@ public class RelativeLayout extends ViewGroup {
return -1;
}
- private void centerHorizontal(View child, LayoutParams params, int myWidth) {
+ private static void centerHorizontal(View child, LayoutParams params, int myWidth) {
int childWidth = child.getMeasuredWidth();
int left = (myWidth - childWidth) / 2;
@@ -999,7 +1044,7 @@ public class RelativeLayout extends ViewGroup {
params.mRight = left + childWidth;
}
- private void centerVertical(View child, LayoutParams params, int myHeight) {
+ private static void centerVertical(View child, LayoutParams params, int myHeight) {
int childHeight = child.getMeasuredHeight();
int top = (myHeight - childHeight) / 2;
@@ -1193,6 +1238,7 @@ public class RelativeLayout extends ViewGroup {
com.android.internal.R.styleable.RelativeLayout_Layout);
final int[] rules = mRules;
+ //noinspection MismatchedReadAndWriteOfArray
final int[] initialRules = mInitialRules;
final int N = a.getIndexCount();
@@ -1271,9 +1317,7 @@ public class RelativeLayout extends ViewGroup {
}
}
- for (int n = LEFT_OF; n < VERB_COUNT; n++) {
- initialRules[n] = rules[n];
- }
+ System.arraycopy(rules, LEFT_OF, initialRules, LEFT_OF, VERB_COUNT);
a.recycle();
}
@@ -1364,9 +1408,7 @@ public class RelativeLayout extends ViewGroup {
private void resolveRules(int layoutDirection) {
final boolean isLayoutRtl = (layoutDirection == View.LAYOUT_DIRECTION_RTL);
// Reset to initial state
- for (int n = LEFT_OF; n < VERB_COUNT; n++) {
- mRules[n] = mInitialRules[n];
- }
+ System.arraycopy(mInitialRules, LEFT_OF, mRules, LEFT_OF, VERB_COUNT);
// Apply rules depending on direction
if (mRules[ALIGN_START] != 0) {
mRules[isLayoutRtl ? ALIGN_RIGHT : ALIGN_LEFT] = mRules[ALIGN_START];
@@ -1655,7 +1697,7 @@ public class RelativeLayout extends ViewGroup {
*
* A node with no dependent is considered a root of the graph.
*/
- static class Node implements Poolable<Node> {
+ static class Node {
/**
* The view representing this node in the layout.
*/
@@ -1678,43 +1720,14 @@ public class RelativeLayout extends ViewGroup {
// 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;
- private boolean mIsPooled;
-
- public void setNextPoolable(Node element) {
- mNext = element;
- }
-
- public Node getNextPoolable() {
- return mNext;
- }
-
- public boolean isPooled() {
- return mIsPooled;
- }
-
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
+ private static final SimplePool<Node> sPool = new SimplePool<Node>(POOL_LIMIT);
static Node acquire(View view) {
- final Node node = sPool.acquire();
+ Node node = sPool.acquire();
+ if (node == null) {
+ node = new Node();
+ }
node.view = view;
-
return node;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 8d1be53..79fc51e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -53,7 +53,6 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
-
/**
* A class that describes a view hierarchy that can be displayed in
* another process. The hierarchy is inflated from a layout resource
@@ -340,7 +339,7 @@ public class RemoteViews implements Parcelable, Filter {
if (target == null) return;
if (!mIsWidgetCollectionChild) {
- Log.e("RemoteViews", "The method setOnClickFillInIntent is available " +
+ Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " +
"only from RemoteViewsFactory (ie. on collection items).");
return;
}
@@ -359,13 +358,13 @@ public class RemoteViews implements Parcelable, Filter {
if (parent instanceof AppWidgetHostView || parent == null) {
// Somehow they've managed to get this far without having
// and AdapterView as a parent.
- Log.e("RemoteViews", "Collection item doesn't have AdapterView parent");
+ Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent");
return;
}
// Insure that a template pending intent has been set on an ancestor
if (!(parent.getTag() instanceof PendingIntent)) {
- Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" +
+ Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" +
" calling setPendingIntentTemplate on parent.");
return;
}
@@ -472,7 +471,7 @@ public class RemoteViews implements Parcelable, Filter {
av.setOnItemClickListener(listener);
av.setTag(pendingIntentTemplate);
} else {
- Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" +
+ Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" +
"an AdapterView (id: " + viewId + ")");
return;
}
@@ -487,6 +486,88 @@ public class RemoteViews implements Parcelable, Filter {
public final static int TAG = 8;
}
+ private class SetRemoteViewsAdapterList extends Action {
+ public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
+ this.viewId = id;
+ this.list = list;
+ this.viewTypeCount = viewTypeCount;
+ }
+
+ public SetRemoteViewsAdapterList(Parcel parcel) {
+ viewId = parcel.readInt();
+ viewTypeCount = parcel.readInt();
+ int count = parcel.readInt();
+ list = new ArrayList<RemoteViews>();
+
+ for (int i = 0; i < count; i++) {
+ RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
+ list.add(rv);
+ }
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ dest.writeInt(viewTypeCount);
+
+ if (list == null || list.size() == 0) {
+ dest.writeInt(0);
+ } else {
+ int count = list.size();
+ dest.writeInt(count);
+ for (int i = 0; i < count; i++) {
+ RemoteViews rv = list.get(i);
+ rv.writeToParcel(dest, flags);
+ }
+ }
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final View target = root.findViewById(viewId);
+ if (target == null) return;
+
+ // Ensure that we are applying to an AppWidget root
+ if (!(rootParent instanceof AppWidgetHostView)) {
+ Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
+ "AppWidgets (root id: " + viewId + ")");
+ return;
+ }
+ // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
+ if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
+ Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
+ "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
+ return;
+ }
+
+ if (target instanceof AbsListView) {
+ AbsListView v = (AbsListView) target;
+ Adapter a = v.getAdapter();
+ if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
+ ((RemoteViewsListAdapter) a).setViewsList(list);
+ } else {
+ v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
+ }
+ } else if (target instanceof AdapterViewAnimator) {
+ AdapterViewAnimator v = (AdapterViewAnimator) target;
+ Adapter a = v.getAdapter();
+ if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
+ ((RemoteViewsListAdapter) a).setViewsList(list);
+ } else {
+ v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
+ }
+ }
+ }
+
+ public String getActionName() {
+ return "SetRemoteViewsAdapterList";
+ }
+
+ int viewTypeCount;
+ ArrayList<RemoteViews> list;
+ public final static int TAG = 15;
+ }
+
private class SetRemoteViewsAdapterIntent extends Action {
public SetRemoteViewsAdapterIntent(int id, Intent intent) {
this.viewId = id;
@@ -511,13 +592,13 @@ public class RemoteViews implements Parcelable, Filter {
// Ensure that we are applying to an AppWidget root
if (!(rootParent instanceof AppWidgetHostView)) {
- Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " +
+ Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
"AppWidgets (root id: " + viewId + ")");
return;
}
// Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
- Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " +
+ Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
"an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
return;
}
@@ -585,7 +666,7 @@ public class RemoteViews implements Parcelable, Filter {
// If the view is an AdapterView, setting a PendingIntent on click doesn't make much
// sense, do they mean to set a PendingIntent template for the AdapterView's children?
if (mIsWidgetCollectionChild) {
- Log.w("RemoteViews", "Cannot setOnClickPendingIntent for collection item " +
+ Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
"(id: " + viewId + ")");
ApplicationInfo appInfo = root.getContext().getApplicationInfo();
@@ -772,7 +853,7 @@ public class RemoteViews implements Parcelable, Filter {
try {
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+ Log.d(LOG_TAG, "view: " + klass.getName() + " calling method: "
+ this.methodName + "()");
}
method.invoke(view);
@@ -945,7 +1026,7 @@ public class RemoteViews implements Parcelable, Filter {
this.type = in.readInt();
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
+ Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
+ " methodName=" + this.methodName + " type=" + this.type);
}
@@ -1012,7 +1093,7 @@ public class RemoteViews implements Parcelable, Filter {
out.writeInt(this.type);
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
+ Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
+ " methodName=" + this.methodName + " type=" + this.type);
}
@@ -1139,7 +1220,7 @@ public class RemoteViews implements Parcelable, Filter {
try {
//noinspection ConstantIfStatement
if (false) {
- Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
+ Log.d(LOG_TAG, "view: " + klass.getName() + " calling method: "
+ this.methodName + "(" + param.getName() + ") with "
+ (this.value == null ? "null" : this.value.getClass().getName()));
}
@@ -1562,6 +1643,9 @@ public class RemoteViews implements Parcelable, Filter {
case BitmapReflectionAction.TAG:
mActions.add(new BitmapReflectionAction(parcel));
break;
+ case SetRemoteViewsAdapterList.TAG:
+ mActions.add(new SetRemoteViewsAdapterList(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
@@ -1982,7 +2066,7 @@ public class RemoteViews implements Parcelable, Filter {
*
* @param appWidgetId The id of the app widget which contains the specified view. (This
* parameter is ignored in this deprecated method)
- * @param viewId The id of the {@link AbsListView}
+ * @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
* @deprecated This method has been deprecated. See
@@ -1997,7 +2081,7 @@ public class RemoteViews implements Parcelable, Filter {
* Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
* Can only be used for App Widgets.
*
- * @param viewId The id of the {@link AbsListView}
+ * @param viewId The id of the {@link AdapterView}
* @param intent The intent of the service which will be
* providing data to the RemoteViewsAdapter
*/
@@ -2006,6 +2090,29 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
+ * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
+ * This is a simpler but less flexible approach to populating collection widgets. Its use is
+ * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
+ * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
+ * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
+ * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
+ *
+ * This API is supported in the compatibility library for previous API levels, see
+ * RemoteViewsCompat.
+ *
+ * @param viewId The id of the {@link AdapterView}
+ * @param list The list of RemoteViews which will populate the view specified by viewId.
+ * @param viewTypeCount The maximum number of unique layout id's used to construct the list of
+ * RemoteViews. This count cannot change during the life-cycle of a given widget, so this
+ * parameter should account for the maximum possible number of types that may appear in the
+ * See {@link Adapter#getViewTypeCount()}.
+ */
+ public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
+ addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
+ }
+
+ /**
* Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
*
* @param viewId The id of the view to change
diff --git a/core/java/android/widget/RemoteViewsListAdapter.java b/core/java/android/widget/RemoteViewsListAdapter.java
new file mode 100644
index 0000000..e490458
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsListAdapter.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class RemoteViewsListAdapter extends BaseAdapter {
+
+ private Context mContext;
+ private ArrayList<RemoteViews> mRemoteViewsList;
+ private ArrayList<Integer> mViewTypes = new ArrayList<Integer>();
+ private int mViewTypeCount;
+
+ public RemoteViewsListAdapter(Context context, ArrayList<RemoteViews> remoteViews,
+ int viewTypeCount) {
+ mContext = context;
+ mRemoteViewsList = remoteViews;
+ mViewTypeCount = viewTypeCount;
+ init();
+ }
+
+ public void setViewsList(ArrayList<RemoteViews> remoteViews) {
+ mRemoteViewsList = remoteViews;
+ init();
+ notifyDataSetChanged();
+ }
+
+ private void init() {
+ if (mRemoteViewsList == null) return;
+
+ mViewTypes.clear();
+ for (RemoteViews rv: mRemoteViewsList) {
+ if (!mViewTypes.contains(rv.getLayoutId())) {
+ mViewTypes.add(rv.getLayoutId());
+ }
+ }
+
+ if (mViewTypes.size() > mViewTypeCount || mViewTypeCount < 1) {
+ throw new RuntimeException("Invalid view type count -- view type count must be >= 1" +
+ "and must be as large as the total number of distinct view types");
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (mRemoteViewsList != null) {
+ return mRemoteViewsList.size();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (position < getCount()) {
+ RemoteViews rv = mRemoteViewsList.get(position);
+ rv.setIsWidgetCollectionChild(true);
+ View v;
+ if (convertView != null && rv != null &&
+ convertView.getId() == rv.getLayoutId()) {
+ v = convertView;
+ rv.reapply(mContext, v);
+ } else {
+ v = rv.apply(mContext, parent);
+ }
+ return v;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position < getCount()) {
+ int layoutId = mRemoteViewsList.get(position).getLayoutId();
+ return mViewTypes.indexOf(layoutId);
+ } else {
+ return 0;
+ }
+ }
+
+ public int getViewTypeCount() {
+ return mViewTypeCount;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+}
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index cd8638d..0281602 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -1247,6 +1247,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
*/
@Override
public void onActionViewCollapsed() {
+ setQuery("", false);
clearFocus();
updateViewsVisibility(true);
mQueryTextView.setImeOptions(mCollapsedImeOptions);
diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java
index 2737f94..a6486a8 100644
--- a/core/java/android/widget/SeekBar.java
+++ b/core/java/android/widget/SeekBar.java
@@ -18,6 +18,7 @@ package android.widget;
import android.content.Context;
import android.util.AttributeSet;
+import android.util.ValueModel;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -33,7 +34,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
*
* @attr ref android.R.styleable#SeekBar_thumb
*/
-public class SeekBar extends AbsSeekBar {
+public class SeekBar extends AbsSeekBar implements ValueEditor<Integer> {
/**
* A callback that notifies clients when the progress level has been
@@ -69,8 +70,9 @@ public class SeekBar extends AbsSeekBar {
void onStopTrackingTouch(SeekBar seekBar);
}
+ private ValueModel<Integer> mValueModel = ValueModel.EMPTY;
private OnSeekBarChangeListener mOnSeekBarChangeListener;
-
+
public SeekBar(Context context) {
this(context, null);
}
@@ -89,9 +91,23 @@ public class SeekBar extends AbsSeekBar {
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser);
+ if (fromUser) {
+ mValueModel.set(getProgress());
+ }
}
}
+ @Override
+ public ValueModel<Integer> getValueModel() {
+ return mValueModel;
+ }
+
+ @Override
+ public void setValueModel(ValueModel<Integer> valueModel) {
+ mValueModel = valueModel;
+ setProgress(mValueModel.get());
+ }
+
/**
* Sets a listener to receive notifications of changes to the SeekBar's progress level. Also
* provides notifications of when the user starts and stops a touch gesture within the SeekBar.
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 74ea038..9e7f97e 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -109,7 +109,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
mIds = new int[size];
mSpellCheckSpans = new SpellCheckSpan[size];
- setLocale(mTextView.getTextServicesLocale());
+ setLocale(mTextView.getSpellCheckerLocale());
mCookie = hashCode();
}
@@ -120,7 +120,8 @@ public class SpellChecker implements SpellCheckerSessionListener {
mTextServicesManager = (TextServicesManager) mTextView.getContext().
getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
if (!mTextServicesManager.isSpellCheckerEnabled()
- || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) {
+ || mCurrentLocale == null
+ || mTextServicesManager.getCurrentSpellCheckerSubtype(true) == null) {
mSpellCheckerSession = null;
} else {
mSpellCheckerSession = mTextServicesManager.newSpellCheckerSession(
@@ -146,8 +147,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
resetSession();
- // Change SpellParsers' wordIterator locale
- mWordIterator = new WordIterator(locale);
+ if (locale != null) {
+ // Change SpellParsers' wordIterator locale
+ mWordIterator = new WordIterator(locale);
+ }
// This class is the listener for locale change: warn other locale-aware objects
mTextView.onLocaleChanged();
@@ -222,9 +225,9 @@ public class SpellChecker implements SpellCheckerSessionListener {
if (DBG) {
Log.d(TAG, "Start spell-checking: " + start + ", " + end);
}
- final Locale locale = mTextView.getTextServicesLocale();
+ final Locale locale = mTextView.getSpellCheckerLocale();
final boolean isSessionActive = isSessionActive();
- if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
+ if (locale == null || mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
setLocale(locale);
// Re-check the entire text
start = 0;
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 925864c..fa64fd3 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -25,6 +25,8 @@ import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
@@ -697,6 +699,69 @@ public class Spinner extends AbsSpinner implements OnClickListener {
return width;
}
+ @Override
+ public Parcelable onSaveInstanceState() {
+ final SavedState ss = new SavedState(super.onSaveInstanceState());
+ ss.showDropdown = mPopup != null && mPopup.isShowing();
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ if (ss.showDropdown) {
+ ViewTreeObserver vto = getViewTreeObserver();
+ if (vto != null) {
+ final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!mPopup.isShowing()) {
+ mPopup.show();
+ }
+ final ViewTreeObserver vto = getViewTreeObserver();
+ if (vto != null) {
+ vto.removeOnGlobalLayoutListener(this);
+ }
+ }
+ };
+ vto.addOnGlobalLayoutListener(listener);
+ }
+ }
+ }
+
+ static class SavedState extends AbsSpinner.SavedState {
+ boolean showDropdown;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ private SavedState(Parcel in) {
+ super(in);
+ showDropdown = in.readByte() != 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeByte((byte) (showDropdown ? 1 : 0));
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
/**
* <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
* into a ListAdapter.</p>
@@ -941,8 +1006,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
mHintText = hintText;
}
- @Override
- public void show() {
+ void computeContentWidth() {
final Drawable background = getBackground();
int hOffset = 0;
if (background != null) {
@@ -955,6 +1019,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
final int spinnerPaddingLeft = Spinner.this.getPaddingLeft();
final int spinnerPaddingRight = Spinner.this.getPaddingRight();
final int spinnerWidth = Spinner.this.getWidth();
+
if (mDropDownWidth == WRAP_CONTENT) {
int contentWidth = measureContentWidth(
(SpinnerAdapter) mAdapter, getBackground());
@@ -977,11 +1042,25 @@ public class Spinner extends AbsSpinner implements OnClickListener {
hOffset += spinnerPaddingLeft;
}
setHorizontalOffset(hOffset);
+ }
+
+ @Override
+ public void show() {
+ final boolean wasShowing = isShowing();
+
+ computeContentWidth();
+
setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
super.show();
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
setSelection(Spinner.this.getSelectedItemPosition());
+ if (wasShowing) {
+ // Skip setting up the layout/dismiss listener below. If we were previously
+ // showing it will still stick around.
+ return;
+ }
+
// Make sure we hide if our anchor goes away.
// TODO: This might be appropriate to push all the way down to PopupWindow,
// but it may have other side effects to investigate first. (Text editing handles, etc.)
@@ -992,6 +1071,12 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void onGlobalLayout() {
if (!Spinner.this.isVisibleToUser()) {
dismiss();
+ } else {
+ computeContentWidth();
+
+ // Use super.show here to update; we don't want to move the selected
+ // position or adjust other things that would be reset otherwise.
+ DropdownPopup.super.show();
}
}
};
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index 399b4fa..f4b2ce0 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -445,7 +445,7 @@ public class TableLayout extends LinearLayout {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// enforce vertical layout
- layoutVertical();
+ layoutVertical(l, t, r, b);
}
/**
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index 35927e0..fe3631a 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -120,7 +120,7 @@ public class TableRow extends LinearLayout {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// enforce horizontal layout
- layoutHorizontal();
+ layoutHorizontal(l, t, r, b);
}
/**
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 290d9b5..5bf21c0 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -386,6 +386,16 @@ public class TextClock extends TextView {
}
/**
+ * Returns the current format string. Always valid after constructor has
+ * finished, and will never be {@code null}.
+ *
+ * @hide
+ */
+ public CharSequence getFormat() {
+ return mFormat;
+ }
+
+ /**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
* depending on whether the user has selected 24-hour format.
*
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 22bfadb..a1ced6e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -26,6 +26,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
@@ -510,7 +511,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private InputFilter[] mFilters = NO_FILTERS;
- private volatile Locale mCurrentTextServicesLocaleCache;
+ private volatile Locale mCurrentSpellCheckerLocaleCache;
private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock();
// It is possible to have a selection even when mEditor is null (programmatically set, like when
@@ -606,6 +607,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int typefaceIndex = -1;
int styleIndex = -1;
boolean allCaps = false;
+ int shadowcolor = 0;
+ float dx = 0, dy = 0, r = 0;
final Resources.Theme theme = context.getTheme();
@@ -666,6 +669,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextAppearance_textAllCaps:
allCaps = appearance.getBoolean(attr, false);
break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowColor:
+ shadowcolor = a.getInt(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowDx:
+ dx = a.getFloat(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowDy:
+ dy = a.getFloat(attr, 0);
+ break;
+
+ case com.android.internal.R.styleable.TextAppearance_shadowRadius:
+ r = a.getFloat(attr, 0);
+ break;
}
}
@@ -689,8 +708,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int maxlength = -1;
CharSequence text = "";
CharSequence hint = null;
- int shadowcolor = 0;
- float dx = 0, dy = 0, r = 0;
boolean password = false;
int inputType = EditorInfo.TYPE_NULL;
@@ -2330,6 +2347,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
+ final int shadowcolor = appearance.getInt(
+ com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
+ if (shadowcolor != 0) {
+ final float dx = appearance.getFloat(
+ com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
+ final float dy = appearance.getFloat(
+ com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
+ final float r = appearance.getFloat(
+ com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
+
+ setShadowLayer(r, dx, dy, shadowcolor);
+ }
+
if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
false)) {
setTransformationMethod(new AllCapsTransformationMethod(getContext()));
@@ -4349,6 +4379,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/////////////////////////////////////////////////////////////////////////
+ private int getBoxHeight(Layout l) {
+ Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
+ int padding = (l == mHintLayout) ?
+ getCompoundPaddingTop() + getCompoundPaddingBottom() :
+ getExtendedPaddingTop() + getExtendedPaddingBottom();
+ return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
+ }
+
int getVerticalOffset(boolean forceNormal) {
int voffset = 0;
final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
@@ -4359,15 +4397,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (gravity != Gravity.TOP) {
- int boxht;
-
- if (l == mHintLayout) {
- boxht = getMeasuredHeight() - getCompoundPaddingTop() -
- getCompoundPaddingBottom();
- } else {
- boxht = getMeasuredHeight() - getExtendedPaddingTop() -
- getExtendedPaddingBottom();
- }
+ int boxht = getBoxHeight(l);
int textht = l.getHeight();
if (textht < boxht) {
@@ -4390,15 +4420,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (gravity != Gravity.BOTTOM) {
- int boxht;
-
- if (l == mHintLayout) {
- boxht = getMeasuredHeight() - getCompoundPaddingTop() -
- getCompoundPaddingBottom();
- } else {
- boxht = getMeasuredHeight() - getExtendedPaddingTop() -
- getExtendedPaddingBottom();
- }
+ int boxht = getBoxHeight(l);
int textht = l.getHeight();
if (textht < boxht) {
@@ -5144,6 +5166,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
voffset = getVerticalOffset(true);
}
+ if (isLayoutModeOptical(mParent)) {
+ voffset -= getOpticalInsets().top;
+ }
+
return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
}
@@ -6462,7 +6488,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mDeferScroll = -1;
bringPointIntoView(Math.min(curs, mText.length()));
}
- if (changed && mEditor != null) mEditor.invalidateTextDisplayList();
}
private boolean isShowingHint() {
@@ -6556,15 +6581,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int line = layout.getLineForOffset(offset);
- // FIXME: Is it okay to truncate this, or should we round?
- final int x = (int)layout.getPrimaryHorizontal(offset);
- final int top = layout.getLineTop(line);
- final int bottom = layout.getLineTop(line + 1);
-
- int left = (int) FloatMath.floor(layout.getLineLeft(line));
- int right = (int) FloatMath.ceil(layout.getLineRight(line));
- int ht = layout.getHeight();
-
int grav;
switch (layout.getParagraphAlignment(line)) {
@@ -6586,8 +6602,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
}
+ // We only want to clamp the cursor to fit within the layout width
+ // in left-to-right modes, because in a right to left alignment,
+ // we want to scroll to keep the line-right on the screen, as other
+ // lines are likely to have text flush with the right margin, which
+ // we want to keep visible.
+ // A better long-term solution would probably be to measure both
+ // the full line and a blank-trimmed version, and, for example, use
+ // the latter measurement for centering and right alignment, but for
+ // the time being we only implement the cursor clamping in left to
+ // right where it is most likely to be annoying.
+ final boolean clamped = grav > 0;
+ // FIXME: Is it okay to truncate this, or should we round?
+ final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
+ final int top = layout.getLineTop(line);
+ final int bottom = layout.getLineTop(line + 1);
+
+ int left = (int) FloatMath.floor(layout.getLineLeft(line));
+ int right = (int) FloatMath.ceil(layout.getLineRight(line));
+ int ht = layout.getHeight();
+
int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
+ if (!mHorizontallyScrolling && right - left > hspace && right > x) {
+ // If cursor has been clamped, make sure we don't scroll.
+ right = Math.max(x, left + hspace);
+ }
int hslack = (bottom - top) / 2;
int vslack = hslack;
@@ -7154,6 +7194,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
protected void onSelectionChanged(int selStart, int selEnd) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+ notifyAccessibilityStateChanged();
}
/**
@@ -7730,7 +7771,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Returns the TextView_textColor attribute from the
- * Resources.StyledAttributes, if set, or the TextAppearance_textColor
+ * TypedArray, if set, or the TextAppearance_textColor
* from the TextView_textAppearance attribute, if TextView_textColor
* was not set directly.
*/
@@ -7828,27 +7869,46 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
(isTextSelectable() && mText instanceof Spannable && isEnabled());
}
+ private Locale getTextServicesLocale(boolean allowNullLocale) {
+ // Start fetching the text services locale asynchronously.
+ updateTextServicesLocaleAsync();
+ // If !allowNullLocale and there is no cached text services locale, just return the default
+ // locale.
+ return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
+ : mCurrentSpellCheckerLocaleCache;
+ }
+
/**
* This is a temporary method. Future versions may support multi-locale text.
* Caveat: This method may not return the latest text services locale, but this should be
* acceptable and it's more important to make this method asynchronous.
*
- * @return The locale that should be used for a word iterator and a spell checker
+ * @return The locale that should be used for a word iterator
* in this TextView, based on the current spell checker settings,
* the current IME's locale, or the system default locale.
+ * Please note that a word iterator in this TextView is different from another word iterator
+ * used by SpellChecker.java of TextView. This method should be used for the former.
* @hide
*/
// TODO: Support multi-locale
// TODO: Update the text services locale immediately after the keyboard locale is switched
// by catching intent of keyboard switch event
public Locale getTextServicesLocale() {
- if (mCurrentTextServicesLocaleCache == null) {
- // If there is no cached text services locale, just return the default locale.
- mCurrentTextServicesLocaleCache = Locale.getDefault();
- }
- // Start fetching the text services locale asynchronously.
- updateTextServicesLocaleAsync();
- return mCurrentTextServicesLocaleCache;
+ return getTextServicesLocale(false /* allowNullLocale */);
+ }
+
+ /**
+ * This is a temporary method. Future versions may support multi-locale text.
+ * Caveat: This method may not return the latest spell checker locale, but this should be
+ * acceptable and it's more important to make this method asynchronous.
+ *
+ * @return The locale that should be used for a spell checker in this TextView,
+ * based on the current spell checker settings, the current IME's locale, or the system default
+ * locale.
+ * @hide
+ */
+ public Locale getSpellCheckerLocale() {
+ return getTextServicesLocale(true /* allowNullLocale */);
}
private void updateTextServicesLocaleAsync() {
@@ -7867,14 +7927,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void updateTextServicesLocaleLocked() {
- Locale locale = Locale.getDefault();
final TextServicesManager textServicesManager = (TextServicesManager)
mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
+ final Locale locale;
if (subtype != null) {
locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
+ } else {
+ locale = null;
}
- mCurrentTextServicesLocaleCache = locale;
+ mCurrentSpellCheckerLocaleCache = locale;
}
void onLocaleChanged() {
@@ -7944,6 +8006,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
info.setText(getTextForAccessibility());
}
+ if (mBufferType == BufferType.EDITABLE) {
+ info.setEditable(true);
+ }
+
if (TextUtils.isEmpty(getContentDescription()) && !TextUtils.isEmpty(mText)) {
info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
@@ -7953,6 +8019,82 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
+ if (isFocused()) {
+ if (canSelectText()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
+ }
+ if (canCopy()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_COPY);
+ }
+ if (canPaste()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
+ }
+ if (canCut()) {
+ info.addAction(AccessibilityNodeInfo.ACTION_CUT);
+ }
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_COPY: {
+ if (isFocused() && canCopy()) {
+ if (onTextContextMenuItem(ID_COPY)) {
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ } return false;
+ case AccessibilityNodeInfo.ACTION_PASTE: {
+ if (isFocused() && canPaste()) {
+ if (onTextContextMenuItem(ID_PASTE)) {
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ } return false;
+ case AccessibilityNodeInfo.ACTION_CUT: {
+ if (isFocused() && canCut()) {
+ if (onTextContextMenuItem(ID_CUT)) {
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ } return false;
+ case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
+ if (isFocused() && canSelectText()) {
+ CharSequence text = getIterableTextForAccessibility();
+ if (text == null) {
+ return false;
+ }
+ final int start = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
+ final int end = (arguments != null) ? arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
+ if ((getSelectionStart() != start || getSelectionEnd() != end)) {
+ // No arguments clears the selection.
+ if (start == end && end == -1) {
+ Selection.removeSelection((Spannable) text);
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ if (start >= 0 && start <= end && end <= text.length()) {
+ Selection.setSelection((Spannable) text, start, end);
+ // Make sure selection mode is engaged.
+ if (mEditor != null) {
+ mEditor.startSelectionActionMode();
+ }
+ notifyAccessibilityStateChanged();
+ return true;
+ }
+ }
+ }
+ } return false;
+ default: {
+ return super.performAccessibilityAction(action, arguments);
+ }
+ }
}
@Override
@@ -8522,32 +8664,51 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
@Override
- public int getAccessibilityCursorPosition() {
+ public int getAccessibilitySelectionStart() {
+ if (TextUtils.isEmpty(getContentDescription())) {
+ final int selectionStart = getSelectionStart();
+ if (selectionStart >= 0) {
+ return selectionStart;
+ }
+ }
+ return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isAccessibilitySelectionExtendable() {
+ return true;
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int getAccessibilitySelectionEnd() {
if (TextUtils.isEmpty(getContentDescription())) {
final int selectionEnd = getSelectionEnd();
if (selectionEnd >= 0) {
return selectionEnd;
}
}
- return super.getAccessibilityCursorPosition();
+ return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
}
/**
* @hide
*/
@Override
- public void setAccessibilityCursorPosition(int index) {
- if (getAccessibilityCursorPosition() == index) {
+ public void setAccessibilitySelection(int start, int end) {
+ if (getAccessibilitySelectionStart() == start
+ && getAccessibilitySelectionEnd() == end) {
return;
}
- if (TextUtils.isEmpty(getContentDescription())) {
- if (index >= 0 && index <= mText.length()) {
- Selection.setSelection((Spannable) mText, index);
- } else {
- Selection.removeSelection((Spannable) mText);
- }
+ CharSequence text = getIterableTextForAccessibility();
+ if (start >= 0 && start <= end && end <= text.length()) {
+ Selection.setSelection((Spannable) text, start, end);
} else {
- super.setAccessibilityCursorPosition(index);
+ Selection.removeSelection((Spannable) text);
}
}
@@ -8669,11 +8830,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public void drawTextRun(Canvas c, int start, int end,
- int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
+ int contextStart, int contextEnd, float x, float y, Paint p) {
int count = end - start;
int contextCount = contextEnd - contextStart;
c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
- contextCount, x, y, flags, p);
+ contextCount, x, y, p);
}
public float measureText(int start, int end, Paint p) {
@@ -8685,30 +8846,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public float getTextRunAdvances(int start, int end, int contextStart,
- int contextEnd, int flags, float[] advances, int advancesIndex,
+ int contextEnd, float[] advances, int advancesIndex,
Paint p) {
int count = end - start;
int contextCount = contextEnd - contextStart;
return p.getTextRunAdvances(mChars, start + mStart, count,
- contextStart + mStart, contextCount, flags, advances,
+ contextStart + mStart, contextCount, advances,
advancesIndex);
}
- public float getTextRunAdvances(int start, int end, int contextStart,
- int contextEnd, int flags, float[] advances, int advancesIndex,
- Paint p, int reserved) {
- int count = end - start;
- int contextCount = contextEnd - contextStart;
- return p.getTextRunAdvances(mChars, start + mStart, count,
- contextStart + mStart, contextCount, flags, advances,
- advancesIndex, reserved);
- }
-
- public int getTextRunCursor(int contextStart, int contextEnd, int flags,
+ public int getTextRunCursor(int contextStart, int contextEnd,
int offset, int cursorOpt, Paint p) {
int contextCount = contextEnd - contextStart;
return p.getTextRunCursor(mChars, contextStart + mStart,
- contextCount, flags, offset + mStart, cursorOpt);
+ contextCount, offset + mStart, cursorOpt);
}
}
diff --git a/core/java/android/widget/ValueEditor.java b/core/java/android/widget/ValueEditor.java
new file mode 100755
index 0000000..2b91abf
--- /dev/null
+++ b/core/java/android/widget/ValueEditor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.util.ValueModel;
+
+/**
+ * An interface for editors of simple values. Classes implementing this interface are normally
+ * UI controls (subclasses of {@link android.view.View View}) that can provide a suitable
+ * user interface to display and edit values of the specified type. This interface is
+ * intended to describe editors for simple types, like {@code boolean}, {@code int} or
+ * {@code String}, where the values themselves are immutable.
+ * <p>
+ * For example, {@link android.widget.CheckBox CheckBox} implements
+ * this interface for the Boolean type as it is capable of providing an appropriate
+ * mechanism for displaying and changing the value of a Boolean property.
+ *
+ * @param <T> the value type that this editor supports
+ */
+public interface ValueEditor<T> {
+ /**
+ * Return the last value model that was set. If no value model has been set, the editor
+ * should return the value {@link android.util.ValueModel#EMPTY}.
+ *
+ * @return the value model
+ */
+ public ValueModel<T> getValueModel();
+
+ /**
+ * Sets the value model for this editor. When the value model is set, the editor should
+ * retrieve the value from the value model, using {@link android.util.ValueModel#get()},
+ * and set its internal state accordingly. Likewise, when the editor's internal state changes
+ * it should update the value model by calling {@link android.util.ValueModel#set(T)}
+ * with the appropriate value.
+ *
+ * @param valueModel the new value model for this editor.
+ */
+ public void setValueModel(ValueModel<T> valueModel);
+}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 329b0df..16b6a76 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -35,6 +35,7 @@ import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
+import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.MediaController.MediaPlayerControl;
@@ -107,23 +108,65 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- //Log.i("@@@@", "onMeasure");
+ //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+ // + MeasureSpec.toString(heightMeasureSpec) + ")");
+
int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
if (mVideoWidth > 0 && mVideoHeight > 0) {
- if ( mVideoWidth * height > width * mVideoHeight ) {
- //Log.i("@@@", "image too tall, correcting");
+
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
+ // the size is fixed
+ width = widthSpecSize;
+ height = heightSpecSize;
+
+ // for compatibility, we adjust size based on aspect ratio
+ if ( mVideoWidth * height < width * mVideoHeight ) {
+ //Log.i("@@@", "image too wide, correcting");
+ width = height * mVideoWidth / mVideoHeight;
+ } else if ( mVideoWidth * height > width * mVideoHeight ) {
+ //Log.i("@@@", "image too tall, correcting");
+ height = width * mVideoHeight / mVideoWidth;
+ }
+ } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+ // only the width is fixed, adjust the height to match aspect ratio if possible
+ width = widthSpecSize;
height = width * mVideoHeight / mVideoWidth;
- } else if ( mVideoWidth * height < width * mVideoHeight ) {
- //Log.i("@@@", "image too wide, correcting");
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ height = heightSpecSize;
+ }
+ } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+ // only the height is fixed, adjust the width to match aspect ratio if possible
+ height = heightSpecSize;
width = height * mVideoWidth / mVideoHeight;
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // couldn't match aspect ratio within the constraints
+ width = widthSpecSize;
+ }
} else {
- //Log.i("@@@", "aspect ratio is correct: " +
- //width+"/"+height+"="+
- //mVideoWidth+"/"+mVideoHeight);
+ // neither the width nor the height are fixed, try to use actual video size
+ width = mVideoWidth;
+ height = mVideoHeight;
+ if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) {
+ // too tall, decrease both width and height
+ height = heightSpecSize;
+ width = height * mVideoWidth / mVideoHeight;
+ }
+ if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) {
+ // too wide, decrease both width and height
+ width = widthSpecSize;
+ height = width * mVideoHeight / mVideoWidth;
+ }
}
+ } else {
+ // no size yet, just adopt the given spec sizes
}
- //Log.i("@@@@@@@@@@", "setting size: " + width + 'x' + height);
setMeasuredDimension(width, height);
}
@@ -140,33 +183,8 @@ public class VideoView extends SurfaceView implements MediaPlayerControl {
}
public int resolveAdjustedSize(int desiredSize, int measureSpec) {
- int result = desiredSize;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
-
- switch (specMode) {
- case MeasureSpec.UNSPECIFIED:
- /* Parent says we can be as big as we want. Just don't be larger
- * than max size imposed on ourselves.
- */
- result = desiredSize;
- break;
-
- case MeasureSpec.AT_MOST:
- /* Parent says we can be as big as we want, up to specSize.
- * Don't be larger than specSize, and don't be larger than
- * the max size imposed on ourselves.
- */
- result = Math.min(desiredSize, specSize);
- break;
-
- case MeasureSpec.EXACTLY:
- // No choice. Do what we are told.
- result = specSize;
- break;
- }
- return result;
-}
+ return getDefaultSize(desiredSize, measureSpec);
+ }
private void initVideoView() {
mVideoWidth = 0;
diff --git a/core/java/android/util/Poolable.java b/core/java/com/android/internal/app/IAppOpsCallback.aidl
index 87e0529..afbc609 100644
--- a/core/java/android/util/Poolable.java
+++ b/core/java/com/android/internal/app/IAppOpsCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,10 @@
* limitations under the License.
*/
-package android.util;
+package com.android.internal.app;
-/**
- * @hide
- */
-public interface Poolable<T> {
- void setNextPoolable(T element);
- T getNextPoolable();
- boolean isPooled();
- void setPooled(boolean isPooled);
+// This interface is also used by native code, so must
+// be kept in sync with frameworks/native/include/binder/IAppOpsCallback.h
+oneway interface IAppOpsCallback {
+ void opChanged(int op, String packageName);
}
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
new file mode 100644
index 0000000..a9da863
--- /dev/null
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AppOpsManager;
+import com.android.internal.app.IAppOpsCallback;
+
+interface IAppOpsService {
+ // These first methods are also called by native code, so must
+ // be kept in sync with frameworks/native/include/binder/IAppOpsService.h
+ int checkOperation(int code, int uid, String packageName);
+ int noteOperation(int code, int uid, String packageName);
+ int startOperation(int code, int uid, String packageName);
+ void finishOperation(int code, int uid, String packageName);
+ void startWatchingMode(int op, String packageName, IAppOpsCallback callback);
+ void stopWatchingMode(IAppOpsCallback callback);
+
+ // Remaining methods are only used in Java.
+ List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
+ List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
+ void setMode(int code, int uid, String packageName, int mode);
+}
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 1a76461f..823e19f 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -37,6 +37,8 @@ interface IBatteryStats {
void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type);
void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type);
+ void noteVibratorOn(int uid, long durationMillis);
+ void noteVibratorOff(int uid);
void noteStartGps(int uid);
void noteStopGps(int uid);
void noteScreenOn();
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
index 78b4466..6d51d38 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetHost.aidl
@@ -22,9 +22,9 @@ import android.widget.RemoteViews;
/** {@hide} */
oneway interface IAppWidgetHost {
- void updateAppWidget(int appWidgetId, in RemoteViews views);
- void providerChanged(int appWidgetId, in AppWidgetProviderInfo info);
- void providersChanged();
- void viewDataChanged(int appWidgetId, int viewId);
+ void updateAppWidget(int appWidgetId, in RemoteViews views, int userId);
+ void providerChanged(int appWidgetId, in AppWidgetProviderInfo info, int userId);
+ void providersChanged(int userId);
+ void viewDataChanged(int appWidgetId, int viewId, int userId);
}
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index e685e63..7ddd5d2 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -26,42 +26,39 @@ import android.widget.RemoteViews;
/** {@hide} */
interface IAppWidgetService {
-
+
//
// for AppWidgetHost
//
int[] startListening(IAppWidgetHost host, String packageName, int hostId,
- out List<RemoteViews> updatedViews);
- int[] startListeningAsUser(IAppWidgetHost host, String packageName, int hostId,
out List<RemoteViews> updatedViews, int userId);
- void stopListening(int hostId);
- void stopListeningAsUser(int hostId, int userId);
- int allocateAppWidgetId(String packageName, int hostId);
- void deleteAppWidgetId(int appWidgetId);
- void deleteHost(int hostId);
- void deleteAllHosts();
- RemoteViews getAppWidgetViews(int appWidgetId);
- int[] getAppWidgetIdsForHost(int hostId);
+ void stopListening(int hostId, int userId);
+ int allocateAppWidgetId(String packageName, int hostId, int userId);
+ void deleteAppWidgetId(int appWidgetId, int userId);
+ void deleteHost(int hostId, int userId);
+ void deleteAllHosts(int userId);
+ RemoteViews getAppWidgetViews(int appWidgetId, int userId);
+ int[] getAppWidgetIdsForHost(int hostId, int userId);
//
// for AppWidgetManager
//
- void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
- void updateAppWidgetOptions(int appWidgetId, in Bundle extras);
- Bundle getAppWidgetOptions(int appWidgetId);
- void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views);
- void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views);
- void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId);
- List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter);
- AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId);
- boolean hasBindAppWidgetPermission(in String packageName);
- void setBindAppWidgetPermission(in String packageName, in boolean permission);
- void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options);
- boolean bindAppWidgetIdIfAllowed(
- in String packageName, int appWidgetId, in ComponentName provider, in Bundle options);
+ void updateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId);
+ void updateAppWidgetOptions(int appWidgetId, in Bundle extras, int userId);
+ Bundle getAppWidgetOptions(int appWidgetId, int userId);
+ void partiallyUpdateAppWidgetIds(in int[] appWidgetIds, in RemoteViews views, int userId);
+ void updateAppWidgetProvider(in ComponentName provider, in RemoteViews views, int userId);
+ void notifyAppWidgetViewDataChanged(in int[] appWidgetIds, int viewId, int userId);
+ List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter, int userId);
+ AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId, int userId);
+ boolean hasBindAppWidgetPermission(in String packageName, int userId);
+ void setBindAppWidgetPermission(in String packageName, in boolean permission, int userId);
+ void bindAppWidgetId(int appWidgetId, in ComponentName provider, in Bundle options, int userId);
+ boolean bindAppWidgetIdIfAllowed(in String packageName, int appWidgetId,
+ in ComponentName provider, in Bundle options, int userId);
void bindRemoteViewsService(int appWidgetId, in Intent intent, in IBinder connection, int userId);
void unbindRemoteViewsService(int appWidgetId, in Intent intent, int userId);
- int[] getAppWidgetIds(in ComponentName provider);
+ int[] getAppWidgetIds(in ComponentName provider, int userId);
}
diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java
index 20ecace..424c19b 100644
--- a/core/java/com/android/internal/content/PackageMonitor.java
+++ b/core/java/com/android/internal/content/PackageMonitor.java
@@ -153,8 +153,33 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
public void onPackageUpdateFinished(String packageName, int uid) {
}
-
- public void onPackageChanged(String packageName, int uid, String[] components) {
+
+ /**
+ * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED
+ * Intent.ACTION_PACKAGE_CHANGED} being received, informing you of
+ * changes to the enabled/disabled state of components in a package
+ * and/or of the overall package.
+ *
+ * @param packageName The name of the package that is changing.
+ * @param uid The user ID the package runs under.
+ * @param components Any components in the package that are changing. If
+ * the overall package is changing, this will contain an entry of the
+ * package name itself.
+ * @return Return true to indicate you care about this change, which will
+ * result in {@link #onSomePackagesChanged()} being called later. If you
+ * return false, no further callbacks will happen about this change. The
+ * default implementation returns true if this is a change to the entire
+ * package.
+ */
+ public boolean onPackageChanged(String packageName, int uid, String[] components) {
+ if (components != null) {
+ for (String name : components) {
+ if (packageName.equals(name)) {
+ return true;
+ }
+ }
+ }
+ return false;
}
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
@@ -189,7 +214,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
*/
public void onPackageAppeared(String packageName, int reason) {
}
-
+
+ /**
+ * Called when an existing package is updated or its disabled state changes.
+ */
public void onPackageModified(String packageName) {
}
@@ -328,9 +356,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver {
if (pkg != null) {
mModifiedPackages = mTempArray;
mTempArray[0] = pkg;
- onPackageChanged(pkg, uid, components);
- // XXX Don't want this to always cause mSomePackagesChanged,
- // since it can happen a fair amount.
+ mChangeType = PACKAGE_PERMANENT_CHANGE;
+ if (onPackageChanged(pkg, uid, components)) {
+ mSomePackagesChanged = true;
+ }
onPackageModified(pkg);
}
} else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
new file mode 100644
index 0000000..655d148
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -0,0 +1,839 @@
+/*
+ * Copyright (C) 2013 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.inputmethod;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * InputMethodManagerUtils contains some static methods that provides IME informations.
+ * This methods are supposed to be used in both the framework and the Settings application.
+ */
+public class InputMethodUtils {
+ public static final boolean DEBUG = false;
+ public static final int NOT_A_SUBTYPE_ID = -1;
+ public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+ public static final String SUBTYPE_MODE_VOICE = "voice";
+ private static final String TAG = "InputMethodUtils";
+ private static final Locale ENGLISH_LOCALE = new Locale("en");
+ private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
+ private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
+ "EnabledWhenDefaultIsNotAsciiCapable";
+ private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
+
+ private InputMethodUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean isSystemIme(InputMethodInfo inputMethod) {
+ return (inputMethod.getServiceInfo().applicationInfo.flags
+ & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
+ if (!isSystemIme(imi)) {
+ return false;
+ }
+ return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
+ }
+
+ private static boolean isSystemAuxilialyImeThatHashAutomaticSubtype(InputMethodInfo imi) {
+ if (!isSystemIme(imi)) {
+ return false;
+ }
+ if (!imi.isAuxiliaryIme()) {
+ return false;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype s = imi.getSubtypeAt(i);
+ if (s.overridesImplicitlyEnabledSubtype()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+ Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) {
+ final ArrayList<InputMethodInfo> retval = new ArrayList<InputMethodInfo>();
+ boolean auxilialyImeAdded = false;
+ for (int i = 0; i < imis.size(); ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (isDefaultEnabledIme(isSystemReady, imi, context)) {
+ retval.add(imi);
+ if (imi.isAuxiliaryIme()) {
+ auxilialyImeAdded = true;
+ }
+ }
+ }
+ if (auxilialyImeAdded) {
+ return retval;
+ }
+ for (int i = 0; i < imis.size(); ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (isSystemAuxilialyImeThatHashAutomaticSubtype(imi)) {
+ retval.add(imi);
+ }
+ }
+ return retval;
+ }
+
+ // TODO: Rename isSystemDefaultImeThatHasCurrentLanguageSubtype
+ public static boolean isValidSystemDefaultIme(
+ boolean isSystemReady, InputMethodInfo imi, Context context) {
+ if (!isSystemReady) {
+ return false;
+ }
+ if (!isSystemIme(imi)) {
+ return false;
+ }
+ if (imi.getIsDefaultResourceId() != 0) {
+ try {
+ if (imi.isDefault(context) && containsSubtypeOf(
+ imi, context.getResources().getConfiguration().locale.getLanguage(),
+ null /* mode */)) {
+ return true;
+ }
+ } catch (Resources.NotFoundException ex) {
+ }
+ }
+ if (imi.getSubtypeCount() == 0) {
+ Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
+ }
+ return false;
+ }
+
+ public static boolean isDefaultEnabledIme(
+ boolean isSystemReady, InputMethodInfo imi, Context context) {
+ return isValidSystemDefaultIme(isSystemReady, imi, context)
+ || isSystemImeThatHasEnglishKeyboardSubtype(imi);
+ }
+
+ private static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
+ final int N = imi.getSubtypeCount();
+ for (int i = 0; i < N; ++i) {
+ if (!imi.getSubtypeAt(i).getLocale().startsWith(language)) {
+ continue;
+ }
+ if(!TextUtils.isEmpty(mode) && !imi.getSubtypeAt(i).getMode().equalsIgnoreCase(mode)) {
+ continue;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ subtypes.add(imi.getSubtypeAt(i));
+ }
+ return subtypes;
+ }
+
+ public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
+ InputMethodInfo imi, String mode) {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
+ subtypes.add(subtype);
+ }
+ }
+ return subtypes;
+ }
+
+ public static InputMethodInfo getMostApplicableDefaultIME(
+ List<InputMethodInfo> enabledImes) {
+ if (enabledImes != null && enabledImes.size() > 0) {
+ // We'd prefer to fall back on a system IME, since that is safer.
+ int i = enabledImes.size();
+ int firstFoundSystemIme = -1;
+ while (i > 0) {
+ i--;
+ final InputMethodInfo imi = enabledImes.get(i);
+ if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
+ && !imi.isAuxiliaryIme()) {
+ return imi;
+ }
+ if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)
+ && !imi.isAuxiliaryIme()) {
+ firstFoundSystemIme = i;
+ }
+ }
+ return enabledImes.get(Math.max(firstFoundSystemIme, 0));
+ }
+ return null;
+ }
+
+ public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
+ return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
+ }
+
+ public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+ if (imi != null) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = imi.getSubtypeAt(i);
+ if (subtypeHashCode == ims.hashCode()) {
+ return i;
+ }
+ }
+ }
+ return NOT_A_SUBTYPE_ID;
+ }
+
+ private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+ Resources res, InputMethodInfo imi) {
+ final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
+ final String systemLocale = res.getConfiguration().locale.toString();
+ if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
+ final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
+ new HashMap<String, InputMethodSubtype>();
+ final int N = subtypes.size();
+ for (int i = 0; i < N; ++i) {
+ // scan overriding implicitly enabled subtypes.
+ InputMethodSubtype subtype = subtypes.get(i);
+ if (subtype.overridesImplicitlyEnabledSubtype()) {
+ final String mode = subtype.getMode();
+ if (!applicableModeAndSubtypesMap.containsKey(mode)) {
+ applicableModeAndSubtypesMap.put(mode, subtype);
+ }
+ }
+ }
+ if (applicableModeAndSubtypesMap.size() > 0) {
+ return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
+ }
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype subtype = subtypes.get(i);
+ final String locale = subtype.getLocale();
+ final String mode = subtype.getMode();
+ // When system locale starts with subtype's locale, that subtype will be applicable
+ // for system locale
+ // For instance, it's clearly applicable for cases like system locale = en_US and
+ // subtype = en, but it is not necessarily considered applicable for cases like system
+ // locale = en and subtype = en_US.
+ // We just call systemLocale.startsWith(locale) in this function because there is no
+ // need to find applicable subtypes aggressively unlike
+ // findLastResortApplicableSubtypeLocked.
+ if (systemLocale.startsWith(locale)) {
+ final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
+ // If more applicable subtypes are contained, skip.
+ if (applicableSubtype != null) {
+ if (systemLocale.equals(applicableSubtype.getLocale())) continue;
+ if (!systemLocale.equals(locale)) continue;
+ }
+ applicableModeAndSubtypesMap.put(mode, subtype);
+ }
+ }
+ final InputMethodSubtype keyboardSubtype
+ = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
+ final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
+ applicableModeAndSubtypesMap.values());
+ if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype subtype = subtypes.get(i);
+ final String mode = subtype.getMode();
+ if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
+ TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
+ applicableSubtypes.add(subtype);
+ }
+ }
+ }
+ if (keyboardSubtype == null) {
+ InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
+ res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
+ if (lastResortKeyboardSubtype != null) {
+ applicableSubtypes.add(lastResortKeyboardSubtype);
+ }
+ }
+ return applicableSubtypes;
+ }
+
+ private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
+ Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes,
+ boolean allowsImplicitlySelectedSubtypes) {
+ if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
+ enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ context.getResources(), imi);
+ }
+ return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
+ }
+
+ /**
+ * If there are no selected subtypes, tries finding the most applicable one according to the
+ * given locale.
+ * @param subtypes this function will search the most applicable subtype in subtypes
+ * @param mode subtypes will be filtered by mode
+ * @param locale subtypes will be filtered by locale
+ * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
+ * it will return the first subtype matched with mode
+ * @return the most applicable subtypeId
+ */
+ public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+ Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
+ boolean canIgnoreLocaleAsLastResort) {
+ if (subtypes == null || subtypes.size() == 0) {
+ return null;
+ }
+ if (TextUtils.isEmpty(locale)) {
+ locale = res.getConfiguration().locale.toString();
+ }
+ final String language = locale.substring(0, 2);
+ boolean partialMatchFound = false;
+ InputMethodSubtype applicableSubtype = null;
+ InputMethodSubtype firstMatchedModeSubtype = null;
+ final int N = subtypes.size();
+ for (int i = 0; i < N; ++i) {
+ InputMethodSubtype subtype = subtypes.get(i);
+ final String subtypeLocale = subtype.getLocale();
+ // An applicable subtype should match "mode". If mode is null, mode will be ignored,
+ // and all subtypes with all modes can be candidates.
+ if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
+ if (firstMatchedModeSubtype == null) {
+ firstMatchedModeSubtype = subtype;
+ }
+ if (locale.equals(subtypeLocale)) {
+ // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
+ applicableSubtype = subtype;
+ break;
+ } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
+ // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
+ applicableSubtype = subtype;
+ partialMatchFound = true;
+ }
+ }
+ }
+
+ if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
+ return firstMatchedModeSubtype;
+ }
+
+ // The first subtype applicable to the system locale will be defined as the most applicable
+ // subtype.
+ if (DEBUG) {
+ if (applicableSubtype != null) {
+ Slog.d(TAG, "Applicable InputMethodSubtype was found: "
+ + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
+ }
+ }
+ return applicableSubtype;
+ }
+
+ public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
+ if (subtype == null) return true;
+ return !subtype.isAuxiliary();
+ }
+
+ /**
+ * Utility class for putting and getting settings for InputMethod
+ * TODO: Move all putters and getters of settings to this class.
+ */
+ public static class InputMethodSettings {
+ // The string for enabled input method is saved as follows:
+ // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ private final Resources mRes;
+ private final ContentResolver mResolver;
+ private final HashMap<String, InputMethodInfo> mMethodMap;
+ private final ArrayList<InputMethodInfo> mMethodList;
+
+ private String mEnabledInputMethodsStrCache;
+ private int mCurrentUserId;
+
+ private static void buildEnabledInputMethodsSettingString(
+ StringBuilder builder, Pair<String, ArrayList<String>> pair) {
+ String id = pair.first;
+ ArrayList<String> subtypes = pair.second;
+ builder.append(id);
+ // Inputmethod and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ for (String subtypeId: subtypes) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
+ }
+ }
+
+ public InputMethodSettings(
+ Resources res, ContentResolver resolver,
+ HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
+ int userId) {
+ setCurrentUserId(userId);
+ mRes = res;
+ mResolver = resolver;
+ mMethodMap = methodMap;
+ mMethodList = methodList;
+ }
+
+ public void setCurrentUserId(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
+ + userId + ", new ime = " + getSelectedInputMethod());
+ }
+ // IMMS settings are kept per user, so keep track of current user
+ mCurrentUserId = userId;
+ }
+
+ public List<InputMethodInfo> getEnabledInputMethodListLocked() {
+ return createEnabledInputMethodListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ public List<Pair<InputMethodInfo, ArrayList<String>>>
+ getEnabledInputMethodAndSubtypeHashCodeListLocked() {
+ return createEnabledInputMethodAndSubtypeHashCodeListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked());
+ }
+
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
+ List<InputMethodSubtype> enabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi);
+ if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
+ enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ context.getResources(), imi);
+ }
+ return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
+ }
+
+ private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ InputMethodInfo imi) {
+ List<Pair<String, ArrayList<String>>> imsList =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ ArrayList<InputMethodSubtype> enabledSubtypes =
+ new ArrayList<InputMethodSubtype>();
+ if (imi != null) {
+ for (Pair<String, ArrayList<String>> imsPair : imsList) {
+ InputMethodInfo info = mMethodMap.get(imsPair.first);
+ if (info != null && info.getId().equals(imi.getId())) {
+ final int subtypeCount = info.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = info.getSubtypeAt(i);
+ for (String s: imsPair.second) {
+ if (String.valueOf(ims.hashCode()).equals(s)) {
+ enabledSubtypes.add(ims);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return enabledSubtypes;
+ }
+
+ // At the initial boot, the settings for input methods are not set,
+ // so we need to enable IME in that case.
+ public void enableAllIMEsIfThereIsNoEnabledIME() {
+ if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
+ StringBuilder sb = new StringBuilder();
+ final int N = mMethodList.size();
+ for (int i = 0; i < N; i++) {
+ InputMethodInfo imi = mMethodList.get(i);
+ Slog.i(TAG, "Adding: " + imi.getId());
+ if (i > 0) sb.append(':');
+ sb.append(imi.getId());
+ }
+ putEnabledInputMethodsStr(sb.toString());
+ }
+ }
+
+ public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ ArrayList<Pair<String, ArrayList<String>>> imsList
+ = new ArrayList<Pair<String, ArrayList<String>>>();
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ mInputMethodSplitter.setString(enabledInputMethodsStr);
+ while (mInputMethodSplitter.hasNext()) {
+ String nextImsStr = mInputMethodSplitter.next();
+ mSubtypeSplitter.setString(nextImsStr);
+ if (mSubtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<String>();
+ // The first element is ime id.
+ String imeId = mSubtypeSplitter.next();
+ while (mSubtypeSplitter.hasNext()) {
+ subtypeHashes.add(mSubtypeSplitter.next());
+ }
+ imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
+ }
+
+ public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
+ if (reloadInputMethodStr) {
+ getEnabledInputMethodsStr();
+ }
+ if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
+ // Add in the newly enabled input method.
+ putEnabledInputMethodsStr(id);
+ } else {
+ putEnabledInputMethodsStr(
+ mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
+ }
+ }
+
+ /**
+ * Build and put a string of EnabledInputMethods with removing specified Id.
+ * @return the specified id was removed or not.
+ */
+ public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+ boolean isRemoved = false;
+ boolean needsAppendSeparator = false;
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ String curId = ims.first;
+ if (curId.equals(id)) {
+ // We are disabling this input method, and it is
+ // currently enabled. Skip it to remove from the
+ // new list.
+ isRemoved = true;
+ } else {
+ if (needsAppendSeparator) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ needsAppendSeparator = true;
+ }
+ buildEnabledInputMethodsSettingString(builder, ims);
+ }
+ }
+ if (isRemoved) {
+ // Update the setting with the new list of input methods.
+ putEnabledInputMethodsStr(builder.toString());
+ }
+ return isRemoved;
+ }
+
+ private List<InputMethodInfo> createEnabledInputMethodListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
+ for (Pair<String, ArrayList<String>> ims: imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(info);
+ }
+ }
+ return res;
+ }
+
+ private List<Pair<InputMethodInfo, ArrayList<String>>>
+ createEnabledInputMethodAndSubtypeHashCodeListLocked(
+ List<Pair<String, ArrayList<String>>> imsList) {
+ final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
+ = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
+ for (Pair<String, ArrayList<String>> ims : imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null) {
+ res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
+ }
+ }
+ return res;
+ }
+
+ private void putEnabledInputMethodsStr(String str) {
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
+ mEnabledInputMethodsStrCache = str;
+ if (DEBUG) {
+ Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+ }
+ }
+
+ public String getEnabledInputMethodsStr() {
+ mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
+ if (DEBUG) {
+ Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
+ + ", " + mCurrentUserId);
+ }
+ return mEnabledInputMethodsStrCache;
+ }
+
+ private void saveSubtypeHistory(
+ List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
+ StringBuilder builder = new StringBuilder();
+ boolean isImeAdded = false;
+ if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
+ builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
+ newSubtypeId);
+ isImeAdded = true;
+ }
+ for (Pair<String, String> ime: savedImes) {
+ String imeId = ime.first;
+ String subtypeId = ime.second;
+ if (TextUtils.isEmpty(subtypeId)) {
+ subtypeId = NOT_A_SUBTYPE_ID_STR;
+ }
+ if (isImeAdded) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ } else {
+ isImeAdded = true;
+ }
+ builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
+ subtypeId);
+ }
+ // Remove the last INPUT_METHOD_SEPARATER
+ putSubtypeHistoryStr(builder.toString());
+ }
+
+ private void addSubtypeToHistory(String imeId, String subtypeId) {
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> ime: subtypeHistory) {
+ if (ime.first.equals(imeId)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
+ + ime.second);
+ }
+ // We should break here
+ subtypeHistory.remove(ime);
+ break;
+ }
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
+ }
+ saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
+ }
+
+ private void putSubtypeHistoryStr(String str) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSubtypeHistoryStr: " + str);
+ }
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
+ }
+
+ public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+ // Gets the first one from the history
+ return getLastSubtypeForInputMethodLockedInternal(null);
+ }
+
+ public String getLastSubtypeForInputMethodLocked(String imeId) {
+ Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
+ if (ime != null) {
+ return ime.second;
+ } else {
+ return null;
+ }
+ }
+
+ private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
+ List<Pair<String, ArrayList<String>>> enabledImes =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> imeAndSubtype : subtypeHistory) {
+ final String imeInTheHistory = imeAndSubtype.first;
+ // If imeId is empty, returns the first IME and subtype in the history
+ if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
+ final String subtypeInTheHistory = imeAndSubtype.second;
+ final String subtypeHashCode =
+ getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
+ enabledImes, imeInTheHistory, subtypeInTheHistory);
+ if (!TextUtils.isEmpty(subtypeHashCode)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
+ }
+ return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "No enabled IME found in the history");
+ }
+ return null;
+ }
+
+ private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
+ ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+ for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
+ if (enabledIme.first.equals(imeId)) {
+ final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (explicitlyEnabledSubtypes.size() == 0) {
+ // If there are no explicitly enabled subtypes, applicable subtypes are
+ // enabled implicitly.
+ // If IME is enabled and no subtypes are enabled, applicable subtypes
+ // are enabled implicitly, so needs to treat them to be enabled.
+ if (imi != null && imi.getSubtypeCount() > 0) {
+ List<InputMethodSubtype> implicitlySelectedSubtypes =
+ getImplicitlyApplicableSubtypesLocked(mRes, imi);
+ if (implicitlySelectedSubtypes != null) {
+ final int N = implicitlySelectedSubtypes.size();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
+ if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+ return subtypeHashCode;
+ }
+ }
+ }
+ }
+ } else {
+ for (String s: explicitlyEnabledSubtypes) {
+ if (s.equals(subtypeHashCode)) {
+ // If both imeId and subtypeId are enabled, return subtypeId.
+ try {
+ final int hashCode = Integer.valueOf(subtypeHashCode);
+ // Check whether the subtype id is valid or not
+ if (isValidSubtypeId(imi, hashCode)) {
+ return s;
+ } else {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ } catch (NumberFormatException e) {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ }
+ }
+ // If imeId was enabled but subtypeId was disabled.
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ // If both imeId and subtypeId are disabled, return null
+ return null;
+ }
+
+ private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
+ ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
+ final String subtypeHistoryStr = getSubtypeHistoryStr();
+ if (TextUtils.isEmpty(subtypeHistoryStr)) {
+ return imsList;
+ }
+ mInputMethodSplitter.setString(subtypeHistoryStr);
+ while (mInputMethodSplitter.hasNext()) {
+ String nextImsStr = mInputMethodSplitter.next();
+ mSubtypeSplitter.setString(nextImsStr);
+ if (mSubtypeSplitter.hasNext()) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ // The first element is ime id.
+ String imeId = mSubtypeSplitter.next();
+ while (mSubtypeSplitter.hasNext()) {
+ subtypeId = mSubtypeSplitter.next();
+ break;
+ }
+ imsList.add(new Pair<String, String>(imeId, subtypeId));
+ }
+ }
+ return imsList;
+ }
+
+ private String getSubtypeHistoryStr() {
+ if (DEBUG) {
+ Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
+ }
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
+ }
+
+ public void putSelectedInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ Settings.Secure.putStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
+ }
+
+ public void putSelectedSubtype(int subtypeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+ + mCurrentUserId);
+ }
+ Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ subtypeId, mCurrentUserId);
+ }
+
+ public String getDisabledSystemInputMethods() {
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
+ }
+
+ public String getSelectedInputMethod() {
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
+ + ", " + mCurrentUserId);
+ }
+ return Settings.Secure.getStringForUser(
+ mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
+ }
+
+ public boolean isSubtypeSelected() {
+ return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+ }
+
+ private int getSelectedInputMethodSubtypeHashCode() {
+ try {
+ return Settings.Secure.getIntForUser(
+ mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
+ } catch (SettingNotFoundException e) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ }
+
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ public int getSelectedInputMethodSubtypeId(String selectedImiId) {
+ final InputMethodInfo imi = mMethodMap.get(selectedImiId);
+ if (imi == null) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+ return getSubtypeIdFromHashCode(imi, subtypeHashCode);
+ }
+
+ public void saveCurrentInputMethodAndSubtypeToHistory(
+ String curMethodId, InputMethodSubtype currentSubtype) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ if (currentSubtype != null) {
+ subtypeId = String.valueOf(currentSubtype.hashCode());
+ }
+ if (canAddToLastInputMethod(currentSubtype)) {
+ addSubtypeToHistory(curMethodId, subtypeId);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index c517a68..8282d23 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -31,6 +31,7 @@ import com.android.internal.util.ProcFileReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
+import java.net.ProtocolException;
import libcore.io.IoUtils;
@@ -41,7 +42,8 @@ import libcore.io.IoUtils;
public class NetworkStatsFactory {
private static final String TAG = "NetworkStatsFactory";
- // TODO: consider moving parsing to native code
+ private static final boolean USE_NATIVE_PARSING = true;
+ private static final boolean SANITY_CHECK_NATIVE = false;
/** Path to {@code /proc/net/xt_qtaguid/iface_stat_all}. */
private final File mStatsXtIfaceAll;
@@ -69,7 +71,7 @@ public class NetworkStatsFactory {
*
* @throws IllegalStateException when problem parsing stats.
*/
- public NetworkStats readNetworkStatsSummaryDev() throws IllegalStateException {
+ public NetworkStats readNetworkStatsSummaryDev() throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
@@ -105,11 +107,9 @@ public class NetworkStatsFactory {
reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
- } catch (IOException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} finally {
IoUtils.closeQuietly(reader);
StrictMode.setThreadPolicy(savedPolicy);
@@ -124,7 +124,7 @@ public class NetworkStatsFactory {
*
* @throws IllegalStateException when problem parsing stats.
*/
- public NetworkStats readNetworkStatsSummaryXt() throws IllegalStateException {
+ public NetworkStats readNetworkStatsSummaryXt() throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
// return null when kernel doesn't support
@@ -154,11 +154,9 @@ public class NetworkStatsFactory {
reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
- } catch (IOException e) {
- throw new IllegalStateException("problem parsing stats: " + e);
+ throw new ProtocolException("problem parsing stats", e);
} finally {
IoUtils.closeQuietly(reader);
StrictMode.setThreadPolicy(savedPolicy);
@@ -166,17 +164,33 @@ public class NetworkStatsFactory {
return stats;
}
- public NetworkStats readNetworkStatsDetail() {
+ public NetworkStats readNetworkStatsDetail() throws IOException {
return readNetworkStatsDetail(UID_ALL);
}
+ public NetworkStats readNetworkStatsDetail(int limitUid) throws IOException {
+ if (USE_NATIVE_PARSING) {
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ if (nativeReadNetworkStatsDetail(stats, mStatsXtUid.getAbsolutePath(), limitUid) != 0) {
+ throw new IOException("Failed to parse network stats");
+ }
+ if (SANITY_CHECK_NATIVE) {
+ final NetworkStats javaStats = javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ assertEquals(javaStats, stats);
+ }
+ return stats;
+ } else {
+ return javaReadNetworkStatsDetail(mStatsXtUid, limitUid);
+ }
+ }
+
/**
- * Parse and return {@link NetworkStats} with UID-level details. Values
- * monotonically increase since device boot.
- *
- * @throws IllegalStateException when problem parsing stats.
+ * Parse and return {@link NetworkStats} with UID-level details. Values are
+ * expected to monotonically increase since device boot.
*/
- public NetworkStats readNetworkStatsDetail(int limitUid) throws IllegalStateException {
+ @VisibleForTesting
+ public static NetworkStats javaReadNetworkStatsDetail(File detailPath, int limitUid)
+ throws IOException {
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskReads();
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
@@ -188,13 +202,13 @@ public class NetworkStatsFactory {
ProcFileReader reader = null;
try {
// open and consume header line
- reader = new ProcFileReader(new FileInputStream(mStatsXtUid));
+ reader = new ProcFileReader(new FileInputStream(detailPath));
reader.finishLine();
while (reader.hasMoreData()) {
idx = reader.nextInt();
if (idx != lastIdx + 1) {
- throw new IllegalStateException(
+ throw new ProtocolException(
"inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
}
lastIdx = idx;
@@ -215,11 +229,9 @@ public class NetworkStatsFactory {
reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing idx " + idx, e);
+ throw new ProtocolException("problem parsing idx " + idx, e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing idx " + idx, e);
- } catch (IOException e) {
- throw new IllegalStateException("problem parsing idx " + idx, e);
+ throw new ProtocolException("problem parsing idx " + idx, e);
} finally {
IoUtils.closeQuietly(reader);
StrictMode.setThreadPolicy(savedPolicy);
@@ -227,4 +239,30 @@ public class NetworkStatsFactory {
return stats;
}
+
+ public void assertEquals(NetworkStats expected, NetworkStats actual) {
+ if (expected.size() != actual.size()) {
+ throw new AssertionError(
+ "Expected size " + expected.size() + ", actual size " + actual.size());
+ }
+
+ NetworkStats.Entry expectedRow = null;
+ NetworkStats.Entry actualRow = null;
+ for (int i = 0; i < expected.size(); i++) {
+ expectedRow = expected.getValues(i, expectedRow);
+ actualRow = actual.getValues(i, actualRow);
+ if (!expectedRow.equals(actualRow)) {
+ throw new AssertionError(
+ "Expected row " + i + ": " + expectedRow + ", actual row " + actualRow);
+ }
+ }
+ }
+
+ /**
+ * Parse statistics from file into given {@link NetworkStats} object. Values
+ * are expected to monotonically increase since device boot.
+ */
+ @VisibleForTesting
+ public static native int nativeReadNetworkStatsDetail(
+ NetworkStats stats, String path, int limitUid);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 94e7a06..04b9884 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16,14 +16,11 @@
package com.android.internal.os;
-import static android.net.NetworkStats.IFACE_ALL;
-import static android.net.NetworkStats.UID_ALL;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
-import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
import android.os.BatteryManager;
@@ -49,7 +46,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
-import com.android.internal.R;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.util.JournaledFile;
import com.google.android.collect.Sets;
@@ -87,7 +83,7 @@ public final class BatteryStatsImpl extends BatteryStats {
private static final int MAGIC = 0xBA757475; // 'BATSTATS'
// Current on-disk Parcel version
- private static final int VERSION = 62 + (USE_OLD_HISTORY ? 1000 : 0);
+ private static final int VERSION = 64 + (USE_OLD_HISTORY ? 1000 : 0);
// Maximum number of items we will record in the history.
private static final int MAX_HISTORY_ITEMS = 2000;
@@ -356,8 +352,8 @@ public final class BatteryStatsImpl extends BatteryStats {
}
public static interface Unpluggable {
- void unplug(long batteryUptime, long batteryRealtime);
- void plug(long batteryUptime, long batteryRealtime);
+ void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
+ void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime);
}
/**
@@ -392,12 +388,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeInt(mUnpluggedCount);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedCount = mPluggedCount;
mCount.set(mPluggedCount);
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mPluggedCount = mCount.get();
}
@@ -587,7 +583,7 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mUnpluggedTime);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
if (DEBUG && mType < 0) {
Log.v(TAG, "unplug #" + mType + ": realtime=" + batteryRealtime
+ " old mUnpluggedTime=" + mUnpluggedTime
@@ -602,7 +598,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
if (DEBUG && mType < 0) {
Log.v(TAG, "plug #" + mType + ": realtime=" + batteryRealtime
+ " old mTotalTime=" + mTotalTime);
@@ -731,7 +727,7 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mTrackingReportedValues;
/*
- * A sequnce counter, incremented once for each update of the stats.
+ * A sequence counter, incremented once for each update of the stats.
*/
int mUpdateVersion;
@@ -786,8 +782,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mCurrentReportedTotalTime = totalTime;
}
- public void unplug(long batteryUptime, long batteryRealtime) {
- super.unplug(batteryUptime, batteryRealtime);
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
if (mTrackingReportedValues) {
mUnpluggedReportedTotalTime = mCurrentReportedTotalTime;
mUnpluggedReportedCount = mCurrentReportedCount;
@@ -795,8 +791,8 @@ public final class BatteryStatsImpl extends BatteryStats {
mInDischarge = true;
}
- public void plug(long batteryUptime, long batteryRealtime) {
- super.plug(batteryUptime, batteryRealtime);
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
mInDischarge = false;
}
@@ -849,6 +845,141 @@ public final class BatteryStatsImpl extends BatteryStats {
}
/**
+ * A timer that increments in batches. It does not run for durations, but just jumps
+ * for a pre-determined amount.
+ */
+ public static final class BatchTimer extends Timer {
+ final Uid mUid;
+
+ /**
+ * The last time at which we updated the timer. This is in elapsed realtime microseconds.
+ */
+ long mLastAddedTime;
+
+ /**
+ * The last duration that we added to the timer. This is in microseconds.
+ */
+ long mLastAddedDuration;
+
+ /**
+ * Whether we are currently in a discharge cycle.
+ */
+ boolean mInDischarge;
+
+ BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
+ boolean inDischarge, Parcel in) {
+ super(type, unpluggables, in);
+ mUid = uid;
+ mLastAddedTime = in.readLong();
+ mLastAddedDuration = in.readLong();
+ mInDischarge = inDischarge;
+ }
+
+ BatchTimer(Uid uid, int type, ArrayList<Unpluggable> unpluggables,
+ boolean inDischarge) {
+ super(type, unpluggables);
+ mUid = uid;
+ mInDischarge = inDischarge;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, long batteryRealtime) {
+ super.writeToParcel(out, batteryRealtime);
+ out.writeLong(mLastAddedTime);
+ out.writeLong(mLastAddedDuration);
+ }
+
+ @Override
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ recomputeLastDuration(SystemClock.elapsedRealtime() * 1000, false);
+ mInDischarge = false;
+ super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
+ }
+
+ @Override
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
+ recomputeLastDuration(elapsedRealtime, false);
+ mInDischarge = true;
+ // If we are still within the last added duration, then re-added whatever remains.
+ if (mLastAddedTime == elapsedRealtime) {
+ mTotalTime += mLastAddedDuration;
+ }
+ super.unplug(elapsedRealtime, batteryUptime, batteryRealtime);
+ }
+
+ @Override
+ public void logState(Printer pw, String prefix) {
+ super.logState(pw, prefix);
+ pw.println(prefix + "mLastAddedTime=" + mLastAddedTime
+ + " mLastAddedDuration=" + mLastAddedDuration);
+ }
+
+ private long computeOverage(long curTime) {
+ if (mLastAddedTime > 0) {
+ return mLastTime + mLastAddedDuration - curTime;
+ }
+ return 0;
+ }
+
+ private void recomputeLastDuration(long curTime, boolean abort) {
+ final long overage = computeOverage(curTime);
+ if (overage > 0) {
+ // Aborting before the duration ran out -- roll back the remaining
+ // duration. Only do this if currently discharging; otherwise we didn't
+ // actually add the time.
+ if (mInDischarge) {
+ mTotalTime -= overage;
+ }
+ if (abort) {
+ mLastAddedTime = 0;
+ } else {
+ mLastAddedTime = curTime;
+ mLastAddedDuration -= overage;
+ }
+ }
+ }
+
+ public void addDuration(BatteryStatsImpl stats, long durationMillis) {
+ final long now = SystemClock.elapsedRealtime() * 1000;
+ recomputeLastDuration(now, true);
+ mLastAddedTime = now;
+ mLastAddedDuration = durationMillis * 1000;
+ if (mInDischarge) {
+ mTotalTime += mLastAddedDuration;
+ mCount++;
+ }
+ }
+
+ public void abortLastDuration(BatteryStatsImpl stats) {
+ final long now = SystemClock.elapsedRealtime() * 1000;
+ recomputeLastDuration(now, true);
+ }
+
+ @Override
+ protected int computeCurrentCountLocked() {
+ return mCount;
+ }
+
+ @Override
+ protected long computeRunTimeLocked(long curBatteryRealtime) {
+ final long overage = computeOverage(SystemClock.elapsedRealtime() * 1000);
+ if (overage > 0) {
+ return mTotalTime = overage;
+ }
+ return mTotalTime;
+ }
+
+ @Override
+ boolean reset(BatteryStatsImpl stats, boolean detachIfReset) {
+ final long now = SystemClock.elapsedRealtime() * 1000;
+ recomputeLastDuration(now, true);
+ boolean stillActive = mLastAddedTime == now;
+ super.reset(stats, !stillActive && detachIfReset);
+ return !stillActive;
+ }
+ }
+
+ /**
* State for keeping track of timing information.
*/
public static final class StopwatchTimer extends Timer {
@@ -902,12 +1033,12 @@ public final class BatteryStatsImpl extends BatteryStats {
out.writeLong(mUpdateTime);
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
if (mNesting > 0) {
if (DEBUG && mType < 0) {
Log.v(TAG, "old mUpdateTime=" + mUpdateTime);
}
- super.plug(batteryUptime, batteryRealtime);
+ super.plug(elapsedRealtime, batteryUptime, batteryRealtime);
mUpdateTime = batteryRealtime;
if (DEBUG && mType < 0) {
Log.v(TAG, "new mUpdateTime=" + mUpdateTime);
@@ -1443,7 +1574,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mHistoryOverflow = false;
}
- public void doUnplugLocked(long batteryUptime, long batteryRealtime) {
+ public void doUnplugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
NetworkStats.Entry entry = null;
// Track UID data usage
@@ -1462,7 +1593,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).unplug(batteryUptime, batteryRealtime);
+ mUnpluggables.get(i).unplug(elapsedRealtime, batteryUptime, batteryRealtime);
}
// Track both mobile and total overall data
@@ -1483,7 +1614,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mBluetoothPingCount = 0;
}
- public void doPlugLocked(long batteryUptime, long batteryRealtime) {
+ public void doPlugLocked(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
NetworkStats.Entry entry = null;
for (int iu = mUidStats.size() - 1; iu >= 0; iu--) {
@@ -1498,7 +1629,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
for (int i = mUnpluggables.size() - 1; i >= 0; i--) {
- mUnpluggables.get(i).plug(batteryUptime, batteryRealtime);
+ mUnpluggables.get(i).plug(elapsedRealtime, batteryUptime, batteryRealtime);
}
// Track both mobile and total overall data
@@ -2109,6 +2240,14 @@ public final class BatteryStatsImpl extends BatteryStats {
getUidStatsLocked(uid).noteVideoTurnedOffLocked();
}
+ public void noteVibratorOnLocked(int uid, long durationMillis) {
+ getUidStatsLocked(uid).noteVibratorOnLocked(durationMillis);
+ }
+
+ public void noteVibratorOffLocked(int uid) {
+ getUidStatsLocked(uid).noteVibratorOffLocked();
+ }
+
public void noteWifiRunningLocked(WorkSource ws) {
if (!mGlobalWifiRunning) {
mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG;
@@ -2402,6 +2541,8 @@ public final class BatteryStatsImpl extends BatteryStats {
boolean mVideoTurnedOn;
StopwatchTimer mVideoTurnedOnTimer;
+ BatchTimer mVibratorOnTimer;
+
Counter[] mUserActivityCounters;
/**
@@ -2439,10 +2580,6 @@ public final class BatteryStatsImpl extends BatteryStats {
mWifiScanTimers, mUnpluggables);
mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED,
mWifiMulticastTimers, mUnpluggables);
- mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables);
- mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables);
}
@Override
@@ -2587,15 +2724,19 @@ public final class BatteryStatsImpl extends BatteryStats {
}
}
+ public StopwatchTimer createAudioTurnedOnTimerLocked() {
+ if (mAudioTurnedOnTimer == null) {
+ mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
+ null, mUnpluggables);
+ }
+ return mAudioTurnedOnTimer;
+ }
+
@Override
public void noteAudioTurnedOnLocked() {
if (!mAudioTurnedOn) {
mAudioTurnedOn = true;
- if (mAudioTurnedOnTimer == null) {
- mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON,
- null, mUnpluggables);
- }
- mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+ createAudioTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
}
}
@@ -2603,19 +2744,25 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteAudioTurnedOffLocked() {
if (mAudioTurnedOn) {
mAudioTurnedOn = false;
- mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ if (mAudioTurnedOnTimer != null) {
+ mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+ }
+
+ public StopwatchTimer createVideoTurnedOnTimerLocked() {
+ if (mVideoTurnedOnTimer == null) {
+ mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
+ null, mUnpluggables);
}
+ return mVideoTurnedOnTimer;
}
@Override
public void noteVideoTurnedOnLocked() {
if (!mVideoTurnedOn) {
mVideoTurnedOn = true;
- if (mVideoTurnedOnTimer == null) {
- mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON,
- null, mUnpluggables);
- }
- mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this);
+ createVideoTurnedOnTimerLocked().startRunningLocked(BatteryStatsImpl.this);
}
}
@@ -2623,7 +2770,27 @@ public final class BatteryStatsImpl extends BatteryStats {
public void noteVideoTurnedOffLocked() {
if (mVideoTurnedOn) {
mVideoTurnedOn = false;
- mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ if (mVideoTurnedOnTimer != null) {
+ mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this);
+ }
+ }
+ }
+
+ public BatchTimer createVibratorOnTimerLocked() {
+ if (mVibratorOnTimer == null) {
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
+ mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal);
+ }
+ return mVibratorOnTimer;
+ }
+
+ public void noteVibratorOnLocked(long durationMillis) {
+ createVibratorOnTimerLocked().addDuration(BatteryStatsImpl.this, durationMillis);
+ }
+
+ public void noteVibratorOffLocked() {
+ if (mVibratorOnTimer != null) {
+ mVibratorOnTimer.abortLastDuration(BatteryStatsImpl.this);
}
}
@@ -2677,6 +2844,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
@Override
+ public Timer getVibratorOnTimer() {
+ return mVibratorOnTimer;
+ }
+
+ @Override
public void noteUserActivityLocked(int type) {
if (mUserActivityCounters == null) {
initUserActivityLocked();
@@ -2747,6 +2919,14 @@ public final class BatteryStatsImpl extends BatteryStats {
active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false);
active |= mVideoTurnedOn;
}
+ if (mVibratorOnTimer != null) {
+ if (mVibratorOnTimer.reset(BatteryStatsImpl.this, false)) {
+ mVibratorOnTimer.detach();
+ mVibratorOnTimer = null;
+ } else {
+ active = true;
+ }
+ }
mLoadedTcpBytesReceived = mLoadedTcpBytesSent = 0;
mCurrentTcpBytesReceived = mCurrentTcpBytesSent = 0;
@@ -2832,9 +3012,11 @@ public final class BatteryStatsImpl extends BatteryStats {
}
if (mAudioTurnedOnTimer != null) {
mAudioTurnedOnTimer.detach();
+ mAudioTurnedOnTimer = null;
}
if (mVideoTurnedOnTimer != null) {
mVideoTurnedOnTimer.detach();
+ mVideoTurnedOnTimer = null;
}
if (mUserActivityCounters != null) {
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
@@ -2917,6 +3099,12 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ if (mVibratorOnTimer != null) {
+ out.writeInt(1);
+ mVibratorOnTimer.writeToParcel(out, batteryRealtime);
+ } else {
+ out.writeInt(0);
+ }
if (mUserActivityCounters != null) {
out.writeInt(1);
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
@@ -3016,6 +3204,12 @@ public final class BatteryStatsImpl extends BatteryStats {
mVideoTurnedOnTimer = null;
}
if (in.readInt() != 0) {
+ mVibratorOnTimer = new BatchTimer(Uid.this, VIBRATOR_ON,
+ mUnpluggables, BatteryStatsImpl.this.mOnBatteryInternal, in);
+ } else {
+ mVibratorOnTimer = null;
+ }
+ if (in.readInt() != 0) {
mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES];
for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) {
mUserActivityCounters[i] = new Counter(mUnpluggables, in);
@@ -3256,14 +3450,14 @@ public final class BatteryStatsImpl extends BatteryStats {
mSpeedBins = new SamplingCounter[getCpuSpeedSteps()];
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedUserTime = mUserTime;
mUnpluggedSystemTime = mSystemTime;
mUnpluggedStarts = mStarts;
mUnpluggedForegroundTime = mForegroundTime;
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
}
void detach() {
@@ -3550,11 +3744,11 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggables.add(this);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedWakeups = mWakeups;
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
}
void detach() {
@@ -3712,13 +3906,13 @@ public final class BatteryStatsImpl extends BatteryStats {
mUnpluggables.add(this);
}
- public void unplug(long batteryUptime, long batteryRealtime) {
+ public void unplug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
mUnpluggedStartTime = getStartTimeToNowLocked(batteryUptime);
mUnpluggedStarts = mStarts;
mUnpluggedLaunches = mLaunches;
}
- public void plug(long batteryUptime, long batteryRealtime) {
+ public void plug(long elapsedRealtime, long batteryUptime, long batteryRealtime) {
}
void detach() {
@@ -4367,7 +4561,7 @@ public final class BatteryStatsImpl extends BatteryStats {
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
- doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
+ doUnplugLocked(realtime, mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime);
} else {
updateKernelWakelocksLocked();
mHistoryCur.batteryLevel = (byte)level;
@@ -4383,7 +4577,7 @@ public final class BatteryStatsImpl extends BatteryStats {
mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
}
updateDischargeScreenLevelsLocked(mScreenOn, mScreenOn);
- doPlugLocked(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
+ doPlugLocked(realtime, getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime));
}
if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) {
if (mFile != null) {
@@ -5161,11 +5355,14 @@ public final class BatteryStatsImpl extends BatteryStats {
}
u.mAudioTurnedOn = false;
if (in.readInt() != 0) {
- u.mAudioTurnedOnTimer.readSummaryFromParcelLocked(in);
+ u.createAudioTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
}
u.mVideoTurnedOn = false;
if (in.readInt() != 0) {
- u.mVideoTurnedOnTimer.readSummaryFromParcelLocked(in);
+ u.createVideoTurnedOnTimerLocked().readSummaryFromParcelLocked(in);
+ }
+ if (in.readInt() != 0) {
+ u.createVibratorOnTimerLocked().readSummaryFromParcelLocked(in);
}
if (in.readInt() != 0) {
@@ -5367,6 +5564,12 @@ public final class BatteryStatsImpl extends BatteryStats {
} else {
out.writeInt(0);
}
+ if (u.mVibratorOnTimer != null) {
+ out.writeInt(1);
+ u.mVibratorOnTimer.writeSummaryFromParcelLocked(out, NOWREAL);
+ } else {
+ out.writeInt(0);
+ }
if (u.mUserActivityCounters == null) {
out.writeInt(0);
@@ -5753,7 +5956,7 @@ public final class BatteryStatsImpl extends BatteryStats {
if (SystemProperties.getBoolean(PROP_QTAGUID_ENABLED, false)) {
try {
mNetworkSummaryCache = mNetworkStatsFactory.readNetworkStatsSummaryDev();
- } catch (IllegalStateException e) {
+ } catch (IOException e) {
Log.wtf(TAG, "problem reading network stats", e);
}
}
@@ -5777,7 +5980,7 @@ public final class BatteryStatsImpl extends BatteryStats {
try {
mNetworkDetailCache = mNetworkStatsFactory
.readNetworkStatsDetail().groupedByUid();
- } catch (IllegalStateException e) {
+ } catch (IOException e) {
Log.wtf(TAG, "problem reading network stats", e);
}
}
diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java
index 88e58dc..6fb72f1 100644
--- a/core/java/com/android/internal/os/SomeArgs.java
+++ b/core/java/com/android/internal/os/SomeArgs.java
@@ -39,6 +39,7 @@ public final class SomeArgs {
public Object arg2;
public Object arg3;
public Object arg4;
+ public Object arg5;
public int argi1;
public int argi2;
public int argi3;
@@ -85,6 +86,7 @@ public final class SomeArgs {
arg2 = null;
arg3 = null;
arg4 = null;
+ arg5 = null;
argi1 = 0;
argi2 = 0;
argi3 = 0;
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index d24513a..fd7e3b0 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -76,18 +76,6 @@ class ZygoteConnection {
private final String peerSecurityContext;
/**
- * A long-lived reference to the original command socket used to launch
- * this peer. If "peer wait" mode is specified, the process that requested
- * the new VM instance intends to track the lifetime of the spawned instance
- * via the command socket. In this case, the command socket is closed
- * in the Zygote and placed here in the spawned instance so that it will
- * not be collected and finalized. This field remains null at all times
- * in the original Zygote process, and in all spawned processes where
- * "peer-wait" mode was not requested.
- */
- private static LocalSocket sPeerWaitSocket = null;
-
- /**
* Constructs instance from connected socket.
*
* @param socket non-null; connected socket
@@ -298,11 +286,6 @@ class ZygoteConnection {
* <li> --rlimit=r,c,m<i>tuple of values for setrlimit() call.
* <code>r</code> is the resource, <code>c</code> and <code>m</code>
* are the settings for current and max value.</i>
- * <li> --peer-wait indicates that the command socket should
- * be inherited by (and set to close-on-exec in) the spawned process
- * and used to track the lifetime of that process. The spawning process
- * then exits. Without this flag, it is retained by the spawning process
- * (and closed in the child) in expectation of a new spawn request.
* <li> --classpath=<i>colon-separated classpath</i> indicates
* that the specified class (which must b first non-flag argument) should
* be loaded from jar files in the specified classpath. Incompatible with
@@ -330,9 +313,6 @@ class ZygoteConnection {
/** from --setgroups */
int[] gids;
- /** from --peer-wait */
- boolean peerWait;
-
/**
* From --enable-debugger, --enable-checkjni, --enable-assert,
* --enable-safemode, and --enable-jni-logging.
@@ -437,8 +417,6 @@ class ZygoteConnection {
debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
} else if (arg.equals("--enable-assert")) {
debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
- } else if (arg.equals("--peer-wait")) {
- peerWait = true;
} else if (arg.equals("--runtime-init")) {
runtimeInit = true;
} else if (arg.startsWith("--seinfo=")) {
@@ -897,23 +875,8 @@ class ZygoteConnection {
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
- /*
- * Close the socket, unless we're in "peer wait" mode, in which
- * case it's used to track the liveness of this process.
- */
-
- if (parsedArgs.peerWait) {
- try {
- ZygoteInit.setCloseOnExec(mSocket.getFileDescriptor(), true);
- sPeerWaitSocket = mSocket;
- } catch (IOException ex) {
- Log.e(TAG, "Zygote Child: error setting peer wait "
- + "socket to be close-on-exec", ex);
- }
- } else {
- closeSocket();
- ZygoteInit.closeServerSocket();
- }
+ closeSocket();
+ ZygoteInit.closeServerSocket();
if (descriptors != null) {
try {
@@ -1044,18 +1007,6 @@ class ZygoteConnection {
return true;
}
- /*
- * If the peer wants to use the socket to wait on the
- * newly spawned process, then we're all done.
- */
- if (parsedArgs.peerWait) {
- try {
- mSocket.close();
- } catch (IOException ex) {
- Log.e(TAG, "Zygote: error closing sockets", ex);
- }
- return true;
- }
return false;
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9e43749..7eddc9c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -19,10 +19,8 @@ package com.android.internal.os;
import static libcore.io.OsConstants.S_IRWXG;
import static libcore.io.OsConstants.S_IRWXO;
-import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.os.Debug;
import android.os.Process;
@@ -88,12 +86,6 @@ public class ZygoteInit {
static final int GC_LOOP_COUNT = 10;
/**
- * If true, zygote forks for each peer. If false, a select loop is used
- * inside a single process. The latter is preferred.
- */
- private static final boolean ZYGOTE_FORK_MODE = false;
-
- /**
* The name of a resource file that contains classes to preload.
*/
private static final String PRELOADED_CLASSES = "preloaded-classes";
@@ -549,11 +541,7 @@ public class ZygoteInit {
Log.i(TAG, "Accepting command socket connections");
- if (ZYGOTE_FORK_MODE) {
- runForkMode();
- } else {
- runSelectLoopMode();
- }
+ runSelectLoop();
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
@@ -566,44 +554,6 @@ public class ZygoteInit {
}
/**
- * Runs the zygote in accept-and-fork mode. In this mode, each peer
- * gets its own zygote spawner process. This code is retained for
- * reference only.
- *
- * @throws MethodAndArgsCaller in a child process when a main() should
- * be executed.
- */
- private static void runForkMode() throws MethodAndArgsCaller {
- while (true) {
- ZygoteConnection peer = acceptCommandPeer();
-
- int pid;
-
- pid = Zygote.fork();
-
- if (pid == 0) {
- // The child process should handle the peer requests
-
- // The child does not accept any more connections
- try {
- sServerSocket.close();
- } catch (IOException ex) {
- Log.e(TAG, "Zygote Child: error closing sockets", ex);
- } finally {
- sServerSocket = null;
- }
-
- peer.run();
- break;
- } else if (pid > 0) {
- peer.closeSocket();
- } else {
- throw new RuntimeException("Error invoking fork()");
- }
- }
- }
-
- /**
* Runs the zygote process's select loop. Accepts new connections as
* they happen, and reads commands from connections one spawn-request's
* worth at a time.
@@ -611,9 +561,9 @@ public class ZygoteInit {
* @throws MethodAndArgsCaller in a child process when a main() should
* be executed.
*/
- private static void runSelectLoopMode() throws MethodAndArgsCaller {
- ArrayList<FileDescriptor> fds = new ArrayList();
- ArrayList<ZygoteConnection> peers = new ArrayList();
+ private static void runSelectLoop() throws MethodAndArgsCaller {
+ ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
+ ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
FileDescriptor[] fdArray = new FileDescriptor[4];
fds.add(sServerSocket.getFileDescriptor());
@@ -734,17 +684,6 @@ public class ZygoteInit {
throws IOException;
/**
- * Sets the permitted and effective capability sets of this process.
- *
- * @param permittedCapabilities permitted set
- * @param effectiveCapabilities effective set
- * @throws IOException on error
- */
- static native void setCapabilities(
- long permittedCapabilities,
- long effectiveCapabilities) throws IOException;
-
- /**
* Invokes select() on the provider array of file descriptors (selecting
* for readability only). Array elements of null are ignored.
*
diff --git a/core/java/com/android/internal/policy/PolicyManager.java b/core/java/com/android/internal/policy/PolicyManager.java
index 5274e54..462b3a9 100644
--- a/core/java/com/android/internal/policy/PolicyManager.java
+++ b/core/java/com/android/internal/policy/PolicyManager.java
@@ -22,8 +22,6 @@ import android.view.LayoutInflater;
import android.view.Window;
import android.view.WindowManagerPolicy;
-import com.android.internal.policy.IPolicy;
-
/**
* {@hide}
*/
diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java
index a91aa3c..23e87fc 100644
--- a/core/java/com/android/internal/statusbar/StatusBarNotification.java
+++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java
@@ -21,23 +21,13 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
-/*
-boolean clearable = !n.ongoingEvent && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0);
-
-
-// TODO: make this restriction do something smarter like never fill
-// more than two screens. "Why would anyone need more than 80 characters." :-/
-final int maxTickerLen = 80;
-if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) {
- truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen);
-}
-*/
-
/**
- * Class encapsulating a Notification. Sent by the NotificationManagerService to the IStatusBar (in System UI).
+ * Class encapsulating a Notification. Sent by the NotificationManagerService to clients including
+ * the IStatusBar (in System UI).
*/
public class StatusBarNotification implements Parcelable {
public final String pkg;
+ public final String basePkg;
public final int id;
public final String tag;
public final int uid;
@@ -47,6 +37,7 @@ public class StatusBarNotification implements Parcelable {
public final Notification notification;
public final int score;
public final UserHandle user;
+ public final long postTime;
/** This is temporarily needed for the JB MR1 PDK. */
@Deprecated
@@ -57,10 +48,23 @@ public class StatusBarNotification implements Parcelable {
public StatusBarNotification(String pkg, int id, String tag, int uid, int initialPid, int score,
Notification notification, UserHandle user) {
+ this(pkg, null, id, tag, uid, initialPid, score, notification, user);
+ }
+
+ public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+ int initialPid, int score, Notification notification, UserHandle user) {
+ this(pkg, basePkg, id, tag, uid, initialPid, score, notification, user,
+ System.currentTimeMillis());
+ }
+
+ public StatusBarNotification(String pkg, String basePkg, int id, String tag, int uid,
+ int initialPid, int score, Notification notification, UserHandle user,
+ long postTime) {
if (pkg == null) throw new NullPointerException();
if (notification == null) throw new NullPointerException();
this.pkg = pkg;
+ this.basePkg = pkg;
this.id = id;
this.tag = tag;
this.uid = uid;
@@ -69,10 +73,13 @@ public class StatusBarNotification implements Parcelable {
this.notification = notification;
this.user = user;
this.notification.setUser(user);
+
+ this.postTime = postTime;
}
public StatusBarNotification(Parcel in) {
this.pkg = in.readString();
+ this.basePkg = in.readString();
this.id = in.readInt();
if (in.readInt() != 0) {
this.tag = in.readString();
@@ -84,11 +91,13 @@ public class StatusBarNotification implements Parcelable {
this.score = in.readInt();
this.notification = new Notification(in);
this.user = UserHandle.readFromParcel(in);
- this.notification.setUser(user);
+ this.notification.setUser(this.user);
+ this.postTime = in.readLong();
}
public void writeToParcel(Parcel out, int flags) {
out.writeString(this.pkg);
+ out.writeString(this.basePkg);
out.writeInt(this.id);
if (this.tag != null) {
out.writeInt(1);
@@ -101,6 +110,8 @@ public class StatusBarNotification implements Parcelable {
out.writeInt(this.score);
this.notification.writeToParcel(out, flags);
user.writeToParcel(out, flags);
+
+ out.writeLong(this.postTime);
}
public int describeContents() {
@@ -123,14 +134,17 @@ public class StatusBarNotification implements Parcelable {
@Override
public StatusBarNotification clone() {
- return new StatusBarNotification(this.pkg, this.id, this.tag, this.uid, this.initialPid,
- this.score, this.notification.clone(), this.user);
+ return new StatusBarNotification(this.pkg, this.basePkg,
+ this.id, this.tag, this.uid, this.initialPid,
+ this.score, this.notification.clone(), this.user, this.postTime);
}
@Override
public String toString() {
- return "StatusBarNotification(pkg=" + pkg + " id=" + id + " tag=" + tag + " score=" + score
- + " notn=" + notification + " user=" + user + ")";
+ return String.format(
+ "StatusBarNotification(pkg=%s user=%s id=%d tag=%s score=%d: %s)",
+ this.pkg, this.user, this.id, this.tag,
+ this.score, this.notification);
}
public boolean isOngoing() {
diff --git a/core/java/com/android/internal/util/FastXmlSerializer.java b/core/java/com/android/internal/util/FastXmlSerializer.java
index 592a8fa..7a04080 100644
--- a/core/java/com/android/internal/util/FastXmlSerializer.java
+++ b/core/java/com/android/internal/util/FastXmlSerializer.java
@@ -50,6 +50,8 @@ public class FastXmlSerializer implements XmlSerializer {
private static final int BUFFER_LEN = 8192;
+ private static String sSpace = " ";
+
private final char[] mText = new char[BUFFER_LEN];
private int mPos;
@@ -59,8 +61,12 @@ public class FastXmlSerializer implements XmlSerializer {
private CharsetEncoder mCharset;
private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
+ private boolean mIndent = false;
private boolean mInTag;
+ private int mNesting = 0;
+ private boolean mLineStart = true;
+
private void append(char c) throws IOException {
int pos = mPos;
if (pos >= (BUFFER_LEN-1)) {
@@ -113,6 +119,14 @@ public class FastXmlSerializer implements XmlSerializer {
append(str, 0, str.length());
}
+ private void appendIndent(int indent) throws IOException {
+ indent *= 4;
+ if (indent > sSpace.length()) {
+ indent = sSpace.length();
+ }
+ append(sSpace, 0, indent);
+ }
+
private void escapeAndAppendString(final String string) throws IOException {
final int N = string.length();
final char NE = (char)ESCAPE_TABLE.length;
@@ -161,6 +175,7 @@ public class FastXmlSerializer implements XmlSerializer {
escapeAndAppendString(value);
append('"');
+ mLineStart = false;
return this;
}
@@ -185,9 +200,13 @@ public class FastXmlSerializer implements XmlSerializer {
public XmlSerializer endTag(String namespace, String name) throws IOException,
IllegalArgumentException, IllegalStateException {
+ mNesting--;
if (mInTag) {
append(" />\n");
} else {
+ if (mIndent && mLineStart) {
+ appendIndent(mNesting);
+ }
append("</");
if (namespace != null) {
append(namespace);
@@ -196,6 +215,7 @@ public class FastXmlSerializer implements XmlSerializer {
append(name);
append(">\n");
}
+ mLineStart = true;
mInTag = false;
return this;
}
@@ -278,6 +298,7 @@ public class FastXmlSerializer implements XmlSerializer {
public void setFeature(String name, boolean state) throws IllegalArgumentException,
IllegalStateException {
if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
+ mIndent = true;
return;
}
throw new UnsupportedOperationException();
@@ -325,6 +346,7 @@ public class FastXmlSerializer implements XmlSerializer {
IllegalArgumentException, IllegalStateException {
append("<?xml version='1.0' encoding='utf-8' standalone='"
+ (standalone ? "yes" : "no") + "' ?>\n");
+ mLineStart = true;
}
public XmlSerializer startTag(String namespace, String name) throws IOException,
@@ -332,6 +354,10 @@ public class FastXmlSerializer implements XmlSerializer {
if (mInTag) {
append(">\n");
}
+ if (mIndent) {
+ appendIndent(mNesting);
+ }
+ mNesting++;
append('<');
if (namespace != null) {
append(namespace);
@@ -339,6 +365,7 @@ public class FastXmlSerializer implements XmlSerializer {
}
append(name);
mInTag = true;
+ mLineStart = false;
return this;
}
@@ -349,6 +376,9 @@ public class FastXmlSerializer implements XmlSerializer {
mInTag = false;
}
escapeAndAppendString(buf, start, len);
+ if (mIndent) {
+ mLineStart = buf[start+len-1] == '\n';
+ }
return this;
}
@@ -359,6 +389,9 @@ public class FastXmlSerializer implements XmlSerializer {
mInTag = false;
}
escapeAndAppendString(text);
+ if (mIndent) {
+ mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n');
+ }
return this;
}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index dd5918b..d01a817 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -21,29 +21,47 @@ import java.io.Writer;
/**
* Lightweight wrapper around {@link PrintWriter} that automatically indents
- * newlines based on internal state. Delays writing indent until first actual
- * write on a newline, enabling indent modification after newline.
+ * newlines based on internal state. It also automatically wraps long lines
+ * based on given line length.
+ * <p>
+ * Delays writing indent until first actual write on a newline, enabling indent
+ * modification after newline.
*/
public class IndentingPrintWriter extends PrintWriter {
- private final String mIndent;
+ private final String mSingleIndent;
+ private final int mWrapLength;
- private StringBuilder mBuilder = new StringBuilder();
- private char[] mCurrent;
+ /** Mutable version of current indent */
+ private StringBuilder mIndentBuilder = new StringBuilder();
+ /** Cache of current {@link #mIndentBuilder} value */
+ private char[] mCurrentIndent;
+ /** Length of current line being built, excluding any indent */
+ private int mCurrentLength;
+
+ /**
+ * Flag indicating if we're currently sitting on an empty line, and that
+ * next write should be prefixed with the current indent.
+ */
private boolean mEmptyLine = true;
- public IndentingPrintWriter(Writer writer, String indent) {
+ public IndentingPrintWriter(Writer writer, String singleIndent) {
+ this(writer, singleIndent, -1);
+ }
+
+ public IndentingPrintWriter(Writer writer, String singleIndent, int wrapLength) {
super(writer);
- mIndent = indent;
+ mSingleIndent = singleIndent;
+ mWrapLength = wrapLength;
}
public void increaseIndent() {
- mBuilder.append(mIndent);
- mCurrent = null;
+ mIndentBuilder.append(mSingleIndent);
+ mCurrentIndent = null;
}
public void decreaseIndent() {
- mBuilder.delete(0, mIndent.length());
- mCurrent = null;
+ mIndentBuilder.delete(0, mSingleIndent.length());
+ mCurrentIndent = null;
}
public void printPair(String key, Object value) {
@@ -52,33 +70,56 @@ public class IndentingPrintWriter extends PrintWriter {
@Override
public void write(char[] buf, int offset, int count) {
+ final int indentLength = mIndentBuilder.length();
final int bufferEnd = offset + count;
int lineStart = offset;
int lineEnd = offset;
+
+ // March through incoming buffer looking for newlines
while (lineEnd < bufferEnd) {
char ch = buf[lineEnd++];
+ mCurrentLength++;
if (ch == '\n') {
- writeIndent();
+ maybeWriteIndent();
super.write(buf, lineStart, lineEnd - lineStart);
lineStart = lineEnd;
mEmptyLine = true;
+ mCurrentLength = 0;
+ }
+
+ // Wrap if we've pushed beyond line length
+ if (mWrapLength > 0 && mCurrentLength >= mWrapLength - indentLength) {
+ if (!mEmptyLine) {
+ // Give ourselves a fresh line to work with
+ super.write('\n');
+ mEmptyLine = true;
+ mCurrentLength = lineEnd - lineStart;
+ } else {
+ // We need more than a dedicated line, slice it hard
+ maybeWriteIndent();
+ super.write(buf, lineStart, lineEnd - lineStart);
+ super.write('\n');
+ mEmptyLine = true;
+ lineStart = lineEnd;
+ mCurrentLength = 0;
+ }
}
}
if (lineStart != lineEnd) {
- writeIndent();
+ maybeWriteIndent();
super.write(buf, lineStart, lineEnd - lineStart);
}
}
- private void writeIndent() {
+ private void maybeWriteIndent() {
if (mEmptyLine) {
mEmptyLine = false;
- if (mBuilder.length() != 0) {
- if (mCurrent == null) {
- mCurrent = mBuilder.toString().toCharArray();
+ if (mIndentBuilder.length() != 0) {
+ if (mCurrentIndent == null) {
+ mCurrentIndent = mIndentBuilder.toString().toCharArray();
}
- super.write(mCurrent, 0, mCurrent.length);
+ super.write(mCurrentIndent, 0, mCurrentIndent.length);
}
}
}
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
index 72e1f0f..81571fe 100644
--- a/core/java/com/android/internal/util/ProcFileReader.java
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -19,6 +19,7 @@ package com.android.internal.util;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.net.ProtocolException;
import java.nio.charset.Charsets;
/**
@@ -82,12 +83,15 @@ public class ProcFileReader implements Closeable {
}
/**
- * Find buffer index of next token delimiter, usually space or newline. Will
- * fill buffer as needed.
+ * Find buffer index of next token delimiter, usually space or newline.
+ * Fills buffer as needed.
+ *
+ * @return Index of next delimeter, otherwise -1 if no tokens remain on
+ * current line.
*/
private int nextTokenIndex() throws IOException {
if (mLineFinished) {
- throw new IOException("no tokens remaining on current line");
+ return -1;
}
int i = 0;
@@ -105,7 +109,7 @@ public class ProcFileReader implements Closeable {
}
} while (fillBuf() > 0);
- throw new IOException("end of stream while looking for token boundary");
+ throw new ProtocolException("End of stream while looking for token boundary");
}
/**
@@ -136,7 +140,7 @@ public class ProcFileReader implements Closeable {
}
} while (fillBuf() > 0);
- throw new IOException("end of stream while looking for line boundary");
+ throw new ProtocolException("End of stream while looking for line boundary");
}
/**
@@ -144,9 +148,11 @@ public class ProcFileReader implements Closeable {
*/
public String nextString() throws IOException {
final int tokenIndex = nextTokenIndex();
- final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
- consumeBuf(tokenIndex + 1);
- return s;
+ if (tokenIndex == -1) {
+ throw new ProtocolException("Missing required string");
+ } else {
+ return parseAndConsumeString(tokenIndex);
+ }
}
/**
@@ -154,6 +160,33 @@ public class ProcFileReader implements Closeable {
*/
public long nextLong() throws IOException {
final int tokenIndex = nextTokenIndex();
+ if (tokenIndex == -1) {
+ throw new ProtocolException("Missing required long");
+ } else {
+ return parseAndConsumeLong(tokenIndex);
+ }
+ }
+
+ /**
+ * Parse and return next token as base-10 encoded {@code long}, or return
+ * the given default value if no remaining tokens on current line.
+ */
+ public long nextOptionalLong(long def) throws IOException {
+ final int tokenIndex = nextTokenIndex();
+ if (tokenIndex == -1) {
+ return def;
+ } else {
+ return parseAndConsumeLong(tokenIndex);
+ }
+ }
+
+ private String parseAndConsumeString(int tokenIndex) throws IOException {
+ final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
+ consumeBuf(tokenIndex + 1);
+ return s;
+ }
+
+ private long parseAndConsumeLong(int tokenIndex) throws IOException {
final boolean negative = mBuffer[0] == '-';
// TODO: refactor into something like IntegralToString
@@ -193,6 +226,7 @@ public class ProcFileReader implements Closeable {
return (int) value;
}
+ @Override
public void close() throws IOException {
mStream.close();
}
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 0ea7b83..58d4aa7 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -27,6 +27,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Vector;
@@ -35,7 +36,7 @@ import java.util.Vector;
*
* <p>The state machine defined here is a hierarchical state machine which processes messages
* and can have states arranged hierarchically.</p>
- *
+ *
* <p>A state is a <code>State</code> object and must implement
* <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
* The enter/exit methods are equivalent to the construction and destruction
@@ -81,8 +82,8 @@ import java.util.Vector;
* machine will cause <code>haltedProcessMessage</code> to be invoked.</p>
*
* <p>If it is desirable to completely stop the state machine call <code>quit</code> or
- * <code>abort</code>. These will call <code>exit</code> of the current state and its parents, call
- * <code>onQuiting</code> and then exit Thread/Loopers.</p>
+ * <code>quitNow</code>. These will call <code>exit</code> of the current state and its parents,
+ * call <code>onQuiting</code> and then exit Thread/Loopers.</p>
*
* <p>In addition to <code>processMessage</code> each <code>State</code> has
* an <code>enter</code> method and <code>exit</exit> method which may be overridden.</p>
@@ -148,7 +149,7 @@ class HelloWorld extends StateMachine {
class State1 extends State {
&#64;Override public boolean processMessage(Message message) {
- Log.d(TAG, "Hello World");
+ log("Hello World");
return HANDLED;
}
}
@@ -232,8 +233,6 @@ state mP2 {
* <p>The implementation is below and also in StateMachineTest:</p>
<code>
class Hsm1 extends StateMachine {
- private static final String TAG = "hsm1";
-
public static final int CMD_1 = 1;
public static final int CMD_2 = 2;
public static final int CMD_3 = 3;
@@ -241,16 +240,16 @@ class Hsm1 extends StateMachine {
public static final int CMD_5 = 5;
public static Hsm1 makeHsm1() {
- Log.d(TAG, "makeHsm1 E");
+ log("makeHsm1 E");
Hsm1 sm = new Hsm1("hsm1");
sm.start();
- Log.d(TAG, "makeHsm1 X");
+ log("makeHsm1 X");
return sm;
}
Hsm1(String name) {
super(name);
- Log.d(TAG, "ctor E");
+ log("ctor E");
// Add states, use indentation to show hierarchy
addState(mP1);
@@ -260,16 +259,16 @@ class Hsm1 extends StateMachine {
// Set the initial state
setInitialState(mS1);
- Log.d(TAG, "ctor X");
+ log("ctor X");
}
class P1 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mP1.enter");
+ log("mP1.enter");
}
&#64;Override public boolean processMessage(Message message) {
boolean retVal;
- Log.d(TAG, "mP1.processMessage what=" + message.what);
+ log("mP1.processMessage what=" + message.what);
switch(message.what) {
case CMD_2:
// CMD_2 will arrive in mS2 before CMD_3
@@ -286,16 +285,16 @@ class Hsm1 extends StateMachine {
return retVal;
}
&#64;Override public void exit() {
- Log.d(TAG, "mP1.exit");
+ log("mP1.exit");
}
}
class S1 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mS1.enter");
+ log("mS1.enter");
}
&#64;Override public boolean processMessage(Message message) {
- Log.d(TAG, "S1.processMessage what=" + message.what);
+ log("S1.processMessage what=" + message.what);
if (message.what == CMD_1) {
// Transition to ourself to show that enter/exit is called
transitionTo(mS1);
@@ -306,17 +305,17 @@ class Hsm1 extends StateMachine {
}
}
&#64;Override public void exit() {
- Log.d(TAG, "mS1.exit");
+ log("mS1.exit");
}
}
class S2 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mS2.enter");
+ log("mS2.enter");
}
&#64;Override public boolean processMessage(Message message) {
boolean retVal;
- Log.d(TAG, "mS2.processMessage what=" + message.what);
+ log("mS2.processMessage what=" + message.what);
switch(message.what) {
case(CMD_2):
sendMessage(obtainMessage(CMD_4));
@@ -334,17 +333,17 @@ class Hsm1 extends StateMachine {
return retVal;
}
&#64;Override public void exit() {
- Log.d(TAG, "mS2.exit");
+ log("mS2.exit");
}
}
class P2 extends State {
&#64;Override public void enter() {
- Log.d(TAG, "mP2.enter");
+ log("mP2.enter");
sendMessage(obtainMessage(CMD_5));
}
&#64;Override public boolean processMessage(Message message) {
- Log.d(TAG, "P2.processMessage what=" + message.what);
+ log("P2.processMessage what=" + message.what);
switch(message.what) {
case(CMD_3):
break;
@@ -357,13 +356,13 @@ class Hsm1 extends StateMachine {
return HANDLED;
}
&#64;Override public void exit() {
- Log.d(TAG, "mP2.exit");
+ log("mP2.exit");
}
}
&#64;Override
void onHalting() {
- Log.d(TAG, "halting");
+ log("halting");
synchronized (this) {
this.notifyAll();
}
@@ -386,7 +385,7 @@ synchronize(hsm) {
// wait for the messages to be handled
hsm.wait();
} catch (InterruptedException e) {
- Log.e(TAG, "exception while waiting " + e.getMessage());
+ loge("exception while waiting " + e.getMessage());
}
}
</code>
@@ -418,8 +417,7 @@ D/hsm1 ( 1999): halting
</code>
*/
public class StateMachine {
-
- private static final String TAG = "StateMachine";
+ // Name of the state machine and used as logging tag
private String mName;
/** Message.what value when quitting */
@@ -447,36 +445,44 @@ public class StateMachine {
* {@hide}
*/
public static class LogRec {
+ private StateMachine mSm;
private long mTime;
private int mWhat;
private String mInfo;
- private State mState;
- private State mOrgState;
+ private IState mState;
+ private IState mOrgState;
+ private IState mDstState;
/**
* Constructor
*
* @param msg
- * @param state that handled the message
+ * @param state the state which handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
+ * @param transToState is the state that was transitioned to after the message was
+ * processed.
*/
- LogRec(Message msg, String info, State state, State orgState) {
- update(msg, info, state, orgState);
+ LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,
+ IState transToState) {
+ update(sm, msg, info, state, orgState, transToState);
}
/**
* Update the information in the record.
* @param state that handled the message
- * @param orgState is the first state the received the message but
- * did not processes the message.
+ * @param orgState is the first state the received the message
+ * @param dstState is the state that was the transition target when logging
*/
- public void update(Message msg, String info, State state, State orgState) {
+ public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,
+ IState dstState) {
+ mSm = sm;
mTime = System.currentTimeMillis();
mWhat = (msg != null) ? msg.what : 0;
mInfo = info;
mState = state;
mOrgState = orgState;
+ mDstState = dstState;
}
/**
@@ -503,32 +509,39 @@ public class StateMachine {
/**
* @return the state that handled this message
*/
- public State getState() {
+ public IState getState() {
return mState;
}
/**
- * @return the original state that received the message.
+ * @return the state destination state if a transition is occurring or null if none.
*/
- public State getOriginalState() {
- return mOrgState;
+ public IState getDestState() {
+ return mDstState;
}
/**
- * @return as string
+ * @return the original state that received the message.
*/
- public String toString(StateMachine sm) {
+ public IState getOriginalState() {
+ return mOrgState;
+ }
+
+ @Override
+ public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("time=");
Calendar c = Calendar.getInstance();
c.setTimeInMillis(mTime);
sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
- sb.append(" state=");
+ sb.append(" processed=");
sb.append(mState == null ? "<null>" : mState.getName());
- sb.append(" orgState=");
+ sb.append(" org=");
sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
+ sb.append(" dest=");
+ sb.append(mDstState == null ? "<null>" : mDstState.getName());
sb.append(" what=");
- String what = sm.getWhatToString(mWhat);
+ String what = mSm != null ? mSm.getWhatToString(mWhat) : "";
if (TextUtils.isEmpty(what)) {
sb.append(mWhat);
sb.append("(0x");
@@ -537,7 +550,7 @@ public class StateMachine {
} else {
sb.append(what);
}
- if ( ! TextUtils.isEmpty(mInfo)) {
+ if (!TextUtils.isEmpty(mInfo)) {
sb.append(" ");
sb.append(mInfo);
}
@@ -560,10 +573,11 @@ public class StateMachine {
private static final int DEFAULT_SIZE = 20;
- private Vector<LogRec> mLogRecords = new Vector<LogRec>();
+ private Vector<LogRec> mLogRecVector = new Vector<LogRec>();
private int mMaxSize = DEFAULT_SIZE;
private int mOldestIndex = 0;
private int mCount = 0;
+ private boolean mLogOnlyTransitions = false;
/**
* private constructor use add
@@ -579,14 +593,22 @@ public class StateMachine {
synchronized void setSize(int maxSize) {
mMaxSize = maxSize;
mCount = 0;
- mLogRecords.clear();
+ mLogRecVector.clear();
+ }
+
+ synchronized void setLogOnlyTransitions(boolean enable) {
+ mLogOnlyTransitions = enable;
+ }
+
+ synchronized boolean logOnlyTransitions() {
+ return mLogOnlyTransitions;
}
/**
* @return the number of recent records.
*/
synchronized int size() {
- return mLogRecords.size();
+ return mLogRecVector.size();
}
/**
@@ -600,7 +622,7 @@ public class StateMachine {
* Clear the list of records.
*/
synchronized void cleanup() {
- mLogRecords.clear();
+ mLogRecVector.clear();
}
/**
@@ -616,7 +638,7 @@ public class StateMachine {
if (nextIndex >= size()) {
return null;
} else {
- return mLogRecords.get(nextIndex);
+ return mLogRecVector.get(nextIndex);
}
}
@@ -628,23 +650,26 @@ public class StateMachine {
* @param state that handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
+ * @param transToState is the state that was transitioned to after the message was
+ * processed.
+ *
*/
- synchronized void add(Message msg, String messageInfo, State state, State orgState) {
+ synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,
+ IState orgState, IState transToState) {
mCount += 1;
- if (mLogRecords.size() < mMaxSize) {
- mLogRecords.add(new LogRec(msg, messageInfo, state, orgState));
+ if (mLogRecVector.size() < mMaxSize) {
+ mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));
} else {
- LogRec pmi = mLogRecords.get(mOldestIndex);
+ LogRec pmi = mLogRecVector.get(mOldestIndex);
mOldestIndex += 1;
if (mOldestIndex >= mMaxSize) {
mOldestIndex = 0;
}
- pmi.update(msg, messageInfo, state, orgState);
+ pmi.update(sm, msg, messageInfo, state, orgState, transToState);
}
}
}
-
private static class SmHandler extends Handler {
/** The debug flag */
@@ -702,15 +727,13 @@ public class StateMachine {
*/
@Override
public String toString() {
- return "state=" + state.getName() + ",active=" + active
- + ",parent=" + ((parentStateInfo == null) ?
- "null" : parentStateInfo.state.getName());
+ return "state=" + state.getName() + ",active=" + active + ",parent="
+ + ((parentStateInfo == null) ? "null" : parentStateInfo.state.getName());
}
}
/** The map of all of the states in the state machine */
- private HashMap<State, StateInfo> mStateInfo =
- new HashMap<State, StateInfo>();
+ private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
/** The initial state that will process the first message */
private State mInitialState;
@@ -750,66 +773,97 @@ public class StateMachine {
*/
@Override
public final void handleMessage(Message msg) {
- if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
+ if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
/** Save the current message */
mMsg = msg;
+ /** State that processed the message */
+ State msgProcessedState = null;
if (mIsConstructionCompleted) {
/** Normal path */
- processMsg(msg);
- } else if (!mIsConstructionCompleted &&
- (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
+ msgProcessedState = processMsg(msg);
+ } else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)
+ && (mMsg.obj == mSmHandlerObj)) {
/** Initial one time path. */
mIsConstructionCompleted = true;
invokeEnterMethods(0);
} else {
- throw new RuntimeException("StateMachine.handleMessage: " +
- "The start method not called, received msg: " + msg);
+ throw new RuntimeException("StateMachine.handleMessage: "
+ + "The start method not called, received msg: " + msg);
}
- performTransitions();
+ performTransitions(msgProcessedState, msg);
- if (mDbg) Log.d(TAG, "handleMessage: X");
+ // We need to check if mSm == null here as we could be quitting.
+ if (mDbg && mSm != null) mSm.log("handleMessage: X");
}
/**
* Do any transitions
+ * @param msgProcessedState is the state that processed the message
*/
- private void performTransitions() {
+ private void performTransitions(State msgProcessedState, Message msg) {
/**
* If transitionTo has been called, exit and then enter
* the appropriate states. We loop on this to allow
* enter and exit methods to use transitionTo.
*/
- State destState = null;
- while (mDestState != null) {
- if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
+ State orgState = mStateStack[mStateStackTopIndex].state;
- /**
- * Save mDestState locally and set to null
- * to know if enter/exit use transitionTo.
- */
- destState = mDestState;
- mDestState = null;
+ /**
+ * Record whether message needs to be logged before we transition and
+ * and we won't log special messages SM_INIT_CMD or SM_QUIT_CMD which
+ * always set msg.obj to the handler.
+ */
+ boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);
+ if (mLogRecords.logOnlyTransitions()) {
+ /** Record only if there is a transition */
+ if (mDestState != null) {
+ mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,
+ orgState, mDestState);
+ }
+ } else if (recordLogMsg) {
+ /** Record message */
+ mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,
+ mDestState);
+ }
+
+ State destState = mDestState;
+ if (destState != null) {
/**
- * Determine the states to exit and enter and return the
- * common ancestor state of the enter/exit states. Then
- * invoke the exit methods then the enter methods.
+ * Process the transitions including transitions in the enter/exit methods
*/
- StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
- invokeExitMethods(commonStateInfo);
- int stateStackEnteringIndex = moveTempStateStackToStateStack();
- invokeEnterMethods(stateStackEnteringIndex);
+ while (true) {
+ if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
+
+ /**
+ * Determine the states to exit and enter and return the
+ * common ancestor state of the enter/exit states. Then
+ * invoke the exit methods then the enter methods.
+ */
+ StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
+ invokeExitMethods(commonStateInfo);
+ int stateStackEnteringIndex = moveTempStateStackToStateStack();
+ invokeEnterMethods(stateStackEnteringIndex);
+ /**
+ * Since we have transitioned to a new state we need to have
+ * any deferred messages moved to the front of the message queue
+ * so they will be processed before any other messages in the
+ * message queue.
+ */
+ moveDeferredMessageAtFrontOfQueue();
- /**
- * Since we have transitioned to a new state we need to have
- * any deferred messages moved to the front of the message queue
- * so they will be processed before any other messages in the
- * message queue.
- */
- moveDeferredMessageAtFrontOfQueue();
+ if (destState != mDestState) {
+ // A new mDestState so continue looping
+ destState = mDestState;
+ } else {
+ // No change in mDestState so we're done
+ break;
+ }
+ }
+ mDestState = null;
}
/**
@@ -860,7 +914,7 @@ public class StateMachine {
* Complete the construction of the state machine.
*/
private final void completeConstruction() {
- if (mDbg) Log.d(TAG, "completeConstruction: E");
+ if (mDbg) mSm.log("completeConstruction: E");
/**
* Determine the maximum depth of the state hierarchy
@@ -876,7 +930,7 @@ public class StateMachine {
maxDepth = depth;
}
}
- if (mDbg) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth);
+ if (mDbg) mSm.log("completeConstruction: maxDepth=" + maxDepth);
mStateStack = new StateInfo[maxDepth];
mTempStateStack = new StateInfo[maxDepth];
@@ -885,18 +939,19 @@ public class StateMachine {
/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
- if (mDbg) Log.d(TAG, "completeConstruction: X");
+ if (mDbg) mSm.log("completeConstruction: X");
}
/**
* Process the message. If the current state doesn't handle
* it, call the states parent and so on. If it is never handled then
* call the state machines unhandledMessage method.
+ * @return the state that processed the message
*/
- private final void processMsg(Message msg) {
+ private final State processMsg(Message msg) {
StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
if (mDbg) {
- Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
+ mSm.log("processMsg: " + curStateInfo.state.getName());
}
if (isQuit(msg)) {
@@ -915,23 +970,11 @@ public class StateMachine {
break;
}
if (mDbg) {
- Log.d(TAG, "processMsg: " + curStateInfo.state.getName());
- }
- }
-
- /**
- * Record that we processed the message
- */
- if (mSm.recordLogRec(msg)) {
- if (curStateInfo != null) {
- State orgState = mStateStack[mStateStackTopIndex].state;
- mLogRecords.add(msg, mSm.getLogRecString(msg), curStateInfo.state,
- orgState);
- } else {
- mLogRecords.add(msg, mSm.getLogRecString(msg), null, null);
+ mSm.log("processMsg: " + curStateInfo.state.getName());
}
}
}
+ return (curStateInfo != null) ? curStateInfo.state : null;
}
/**
@@ -939,10 +982,10 @@ public class StateMachine {
* up to the common ancestor state.
*/
private final void invokeExitMethods(StateInfo commonStateInfo) {
- while ((mStateStackTopIndex >= 0) &&
- (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
+ while ((mStateStackTopIndex >= 0)
+ && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
State curState = mStateStack[mStateStackTopIndex].state;
- if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
+ if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
curState.exit();
mStateStack[mStateStackTopIndex].active = false;
mStateStackTopIndex -= 1;
@@ -954,7 +997,7 @@ public class StateMachine {
*/
private final void invokeEnterMethods(int stateStackEnteringIndex) {
for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
- if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName());
+ if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
mStateStack[i].state.enter();
mStateStack[i].active = true;
}
@@ -970,9 +1013,9 @@ public class StateMachine {
* as the most resent message and end with the oldest
* messages at the front of the queue.
*/
- for (int i = mDeferredMessages.size() - 1; i >= 0; i-- ) {
+ for (int i = mDeferredMessages.size() - 1; i >= 0; i--) {
Message curMsg = mDeferredMessages.get(i);
- if (mDbg) Log.d(TAG, "moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
+ if (mDbg) mSm.log("moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what);
sendMessageAtFrontOfQueue(curMsg);
}
mDeferredMessages.clear();
@@ -990,7 +1033,7 @@ public class StateMachine {
int i = mTempStateStackCount - 1;
int j = startingIndex;
while (i >= 0) {
- if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j);
+ if (mDbg) mSm.log("moveTempStackToStateStack: i=" + i + ",j=" + j);
mStateStack[j] = mTempStateStack[i];
j += 1;
i -= 1;
@@ -998,9 +1041,9 @@ public class StateMachine {
mStateStackTopIndex = j - 1;
if (mDbg) {
- Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop="
- + mStateStackTopIndex + ",startingIndex=" + startingIndex
- + ",Top=" + mStateStack[mStateStackTopIndex].state.getName());
+ mSm.log("moveTempStackToStateStack: X mStateStackTop=" + mStateStackTopIndex
+ + ",startingIndex=" + startingIndex + ",Top="
+ + mStateStack[mStateStackTopIndex].state.getName());
}
return startingIndex;
}
@@ -1031,8 +1074,8 @@ public class StateMachine {
} while ((curStateInfo != null) && !curStateInfo.active);
if (mDbg) {
- Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
- + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
+ mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
+ + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
}
return curStateInfo;
}
@@ -1042,8 +1085,7 @@ public class StateMachine {
*/
private final void setupInitialStateStack() {
if (mDbg) {
- Log.d(TAG, "setupInitialStateStack: E mInitialState="
- + mInitialState.getName());
+ mSm.log("setupInitialStateStack: E mInitialState=" + mInitialState.getName());
}
StateInfo curStateInfo = mStateInfo.get(mInitialState);
@@ -1083,8 +1125,8 @@ public class StateMachine {
*/
private final StateInfo addState(State state, State parent) {
if (mDbg) {
- Log.d(TAG, "addStateInternal: E state=" + state.getName()
- + ",parent=" + ((parent == null) ? "" : parent.getName()));
+ mSm.log("addStateInternal: E state=" + state.getName() + ",parent="
+ + ((parent == null) ? "" : parent.getName()));
}
StateInfo parentStateInfo = null;
if (parent != null) {
@@ -1101,14 +1143,14 @@ public class StateMachine {
}
// Validate that we aren't adding the same state in two different hierarchies.
- if ((stateInfo.parentStateInfo != null) &&
- (stateInfo.parentStateInfo != parentStateInfo)) {
- throw new RuntimeException("state already added");
+ if ((stateInfo.parentStateInfo != null)
+ && (stateInfo.parentStateInfo != parentStateInfo)) {
+ throw new RuntimeException("state already added");
}
stateInfo.state = state;
stateInfo.parentStateInfo = parentStateInfo;
stateInfo.active = false;
- if (mDbg) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo);
+ if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);
return stateInfo;
}
@@ -1128,19 +1170,19 @@ public class StateMachine {
/** @see StateMachine#setInitialState(State) */
private final void setInitialState(State initialState) {
- if (mDbg) Log.d(TAG, "setInitialState: initialState=" + initialState.getName());
+ if (mDbg) mSm.log("setInitialState: initialState=" + initialState.getName());
mInitialState = initialState;
}
/** @see StateMachine#transitionTo(IState) */
private final void transitionTo(IState destState) {
mDestState = (State) destState;
- if (mDbg) Log.d(TAG, "transitionTo: destState=" + mDestState.getName());
+ if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
}
/** @see StateMachine#deferMessage(Message) */
private final void deferMessage(Message msg) {
- if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what);
+ if (mDbg) mSm.log("deferMessage: msg=" + msg.what);
/* Copy the "msg" to "newMsg" as "msg" will be recycled */
Message newMsg = obtainMessage();
@@ -1151,17 +1193,17 @@ public class StateMachine {
/** @see StateMachine#quit() */
private final void quit() {
- if (mDbg) Log.d(TAG, "quit:");
+ if (mDbg) mSm.log("quit:");
sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
}
/** @see StateMachine#quitNow() */
private final void quitNow() {
- if (mDbg) Log.d(TAG, "abort:");
+ if (mDbg) mSm.log("quitNow:");
sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));
}
- /** Validate that the message was sent by quit or abort. */
+ /** Validate that the message was sent by quit or quitNow. */
private final boolean isQuit(Message msg) {
return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);
}
@@ -1224,20 +1266,6 @@ public class StateMachine {
}
/**
- * @return current message
- */
- protected final Message getCurrentMessage() {
- return mSmHandler.getCurrentMessage();
- }
-
- /**
- * @return current state
- */
- protected final IState getCurrentState() {
- return mSmHandler.getCurrentState();
- }
-
- /**
* Add a new state to the state machine, parent will be null
* @param state to add
*/
@@ -1256,6 +1284,26 @@ public class StateMachine {
}
/**
+ * @return current message
+ */
+ protected final Message getCurrentMessage() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.getCurrentMessage();
+ }
+
+ /**
+ * @return current state
+ */
+ protected final IState getCurrentState() {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.getCurrentState();
+ }
+
+ /**
* transition to destination state. Upon returning
* from processMessage the current state's exit will
* be executed and upon the next message arriving
@@ -1303,7 +1351,7 @@ public class StateMachine {
* @param msg that couldn't be handled.
*/
protected void unhandledMessage(Message msg) {
- if (mSmHandler.mDbg) Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
+ if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);
}
/**
@@ -1347,43 +1395,69 @@ public class StateMachine {
}
/**
+ * Set to log only messages that cause a state transition
+ *
+ * @param enable {@code true} to enable, {@code false} to disable
+ */
+ public final void setLogOnlyTransitions(boolean enable) {
+ mSmHandler.mLogRecords.setLogOnlyTransitions(enable);
+ }
+
+ /**
* @return number of log records
*/
public final int getLogRecSize() {
- return mSmHandler.mLogRecords.size();
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return 0;
+ return smh.mLogRecords.size();
}
/**
* @return the total number of records processed
*/
public final int getLogRecCount() {
- return mSmHandler.mLogRecords.count();
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return 0;
+ return smh.mLogRecords.count();
}
/**
- * @return a log record
+ * @return a log record, or null if index is out of range
*/
public final LogRec getLogRec(int index) {
- return mSmHandler.mLogRecords.get(index);
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return null;
+ return smh.mLogRecords.get(index);
}
/**
- * Add the string to LogRecords.
- *
- * @param string
+ * @return a copy of LogRecs as a collection
*/
- protected void addLogRec(String string) {
- mSmHandler.mLogRecords.add(null, string, null, null);
+ public final Collection<LogRec> copyLogRecs() {
+ Vector<LogRec> vlr = new Vector<LogRec>();
+ SmHandler smh = mSmHandler;
+ if (smh != null) {
+ for (LogRec lr : smh.mLogRecords.mLogRecVector) {
+ vlr.add(lr);
+ }
+ }
+ return vlr;
}
/**
- * Add the string and state to LogRecords
+ * Add the string to LogRecords.
*
* @param string
- * @param state current state
*/
- protected void addLogRec(String string, State state) {
- mSmHandler.mLogRecords.add(null, string, state, null);
+ protected void addLogRec(String string) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+ smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),
+ smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);
}
/**
@@ -1412,168 +1486,213 @@ public class StateMachine {
}
/**
- * @return Handler
+ * @return Handler, maybe null if state machine has quit.
*/
public final Handler getHandler() {
return mSmHandler;
}
/**
- * Get a message and set Message.target = this.
+ * Get a message and set Message.target state machine handler.
*
- * @return message or null if SM has quit
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage()
- {
- if (mSmHandler == null) return null;
-
+ public final Message obtainMessage() {
return Message.obtain(mSmHandler);
}
/**
- * Get a message and set Message.target = this and what
+ * Get a message and set Message.target state machine handler, what.
+ *
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
*
* @param what is the assigned to Message.what.
- * @return message or null if SM has quit
+ * @return A Message object from the global pool
*/
public final Message obtainMessage(int what) {
- if (mSmHandler == null) return null;
-
return Message.obtain(mSmHandler, what);
}
/**
- * Get a message and set Message.target = this,
+ * Get a message and set Message.target state machine handler,
* what and obj.
*
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
* @param what is the assigned to Message.what.
* @param obj is assigned to Message.obj.
- * @return message or null if SM has quit
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage(int what, Object obj)
- {
- if (mSmHandler == null) return null;
-
+ public final Message obtainMessage(int what, Object obj) {
return Message.obtain(mSmHandler, what, obj);
}
/**
- * Get a message and set Message.target = this,
+ * Get a message and set Message.target state machine handler,
* what, arg1 and arg2
*
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
* @param what is assigned to Message.what
* @param arg1 is assigned to Message.arg1
* @param arg2 is assigned to Message.arg2
- * @return A Message object from the global pool or null if
- * SM has quit
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage(int what, int arg1, int arg2)
- {
- if (mSmHandler == null) return null;
-
+ public final Message obtainMessage(int what, int arg1, int arg2) {
return Message.obtain(mSmHandler, what, arg1, arg2);
}
/**
- * Get a message and set Message.target = this,
+ * Get a message and set Message.target state machine handler,
* what, arg1, arg2 and obj
*
+ * Note: The handler can be null if the state machine has quit,
+ * which means target will be null and may cause a AndroidRuntimeException
+ * in MessageQueue#enqueMessage if sent directly or if sent using
+ * StateMachine#sendMessage the message will just be ignored.
+ *
* @param what is assigned to Message.what
* @param arg1 is assigned to Message.arg1
* @param arg2 is assigned to Message.arg2
* @param obj is assigned to Message.obj
- * @return A Message object from the global pool or null if
- * SM has quit
+ * @return A Message object from the global pool
*/
- public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
- {
- if (mSmHandler == null) return null;
-
+ public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {
return Message.obtain(mSmHandler, what, arg1, arg2, obj);
}
/**
* Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessage(int what) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessage(obtainMessage(what));
+ smh.sendMessage(obtainMessage(what));
}
/**
* Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessage(int what, Object obj) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessage(obtainMessage(what,obj));
+ smh.sendMessage(obtainMessage(what, obj));
}
/**
* Enqueue a message to this state machine.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessage(Message msg) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessage(msg);
+ smh.sendMessage(msg);
}
/**
* Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessageDelayed(int what, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessageDelayed(obtainMessage(what), delayMillis);
+ smh.sendMessageDelayed(obtainMessage(what), delayMillis);
}
/**
* Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessageDelayed(int what, Object obj, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+ smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
}
/**
* Enqueue a message to this state machine after a delay.
+ *
+ * Message is ignored if state machine has quit.
*/
public final void sendMessageDelayed(Message msg, long delayMillis) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.sendMessageDelayed(msg, delayMillis);
+ smh.sendMessageDelayed(msg, delayMillis);
}
/**
* Enqueue a message to the front of the queue for this state machine.
* Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
*/
protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
- mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
}
/**
* Enqueue a message to the front of the queue for this state machine.
* Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
*/
protected final void sendMessageAtFrontOfQueue(int what) {
- mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what));
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(obtainMessage(what));
}
/**
* Enqueue a message to the front of the queue for this state machine.
* Protected, may only be called by instances of StateMachine.
+ *
+ * Message is ignored if state machine has quit.
*/
protected final void sendMessageAtFrontOfQueue(Message msg) {
- mSmHandler.sendMessageAtFrontOfQueue(msg);
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.sendMessageAtFrontOfQueue(msg);
}
/**
@@ -1581,7 +1700,23 @@ public class StateMachine {
* Protected, may only be called by instances of StateMachine.
*/
protected final void removeMessages(int what) {
- mSmHandler.removeMessages(what);
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
+
+ smh.removeMessages(what);
+ }
+
+ /**
+ * Validate that the message was sent by
+ * {@link StateMachine#quit} or {@link StateMachine#quitNow}.
+ * */
+ protected final boolean isQuit(Message msg) {
+ // mSmHandler can be null if the state machine has quit.
+ SmHandler smh = mSmHandler;
+ if (smh == null) return msg.what == SM_QUIT_CMD;
+
+ return smh.isQuit(msg);
}
/**
@@ -1589,9 +1724,10 @@ public class StateMachine {
*/
protected final void quit() {
// mSmHandler can be null if the state machine is already stopped.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.quit();
+ smh.quit();
}
/**
@@ -1599,9 +1735,10 @@ public class StateMachine {
*/
protected final void quitNow() {
// mSmHandler can be null if the state machine is already stopped.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.quitNow();
+ smh.quitNow();
}
/**
@@ -1609,9 +1746,10 @@ public class StateMachine {
*/
public boolean isDbg() {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return false;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return false;
- return mSmHandler.isDbg();
+ return smh.isDbg();
}
/**
@@ -1621,9 +1759,10 @@ public class StateMachine {
*/
public void setDbg(boolean dbg) {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
- mSmHandler.setDbg(dbg);
+ smh.setDbg(dbg);
}
/**
@@ -1631,10 +1770,11 @@ public class StateMachine {
*/
public void start() {
// mSmHandler can be null if the state machine has quit.
- if (mSmHandler == null) return;
+ SmHandler smh = mSmHandler;
+ if (smh == null) return;
/** Send the complete construction message */
- mSmHandler.completeConstruction();
+ smh.completeConstruction();
}
/**
@@ -1647,10 +1787,84 @@ public class StateMachine {
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(getName() + ":");
pw.println(" total records=" + getLogRecCount());
- for (int i=0; i < getLogRecSize(); i++) {
- pw.printf(" rec[%d]: %s\n", i, getLogRec(i).toString(this));
+ for (int i = 0; i < getLogRecSize(); i++) {
+ pw.printf(" rec[%d]: %s\n", i, getLogRec(i).toString());
pw.flush();
}
pw.println("curState=" + getCurrentState().getName());
}
+
+ /**
+ * Log with debug and add to the LogRecords.
+ *
+ * @param s is string log
+ */
+ protected void logAndAddLogRec(String s) {
+ addLogRec(s);
+ log(s);
+ }
+
+ /**
+ * Log with debug
+ *
+ * @param s is string log
+ */
+ protected void log(String s) {
+ Log.d(mName, s);
+ }
+
+ /**
+ * Log with debug attribute
+ *
+ * @param s is string log
+ */
+ protected void logd(String s) {
+ Log.d(mName, s);
+ }
+
+ /**
+ * Log with verbose attribute
+ *
+ * @param s is string log
+ */
+ protected void logv(String s) {
+ Log.v(mName, s);
+ }
+
+ /**
+ * Log with info attribute
+ *
+ * @param s is string log
+ */
+ protected void logi(String s) {
+ Log.i(mName, s);
+ }
+
+ /**
+ * Log with warning attribute
+ *
+ * @param s is string log
+ */
+ protected void logw(String s) {
+ Log.w(mName, s);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ */
+ protected void loge(String s) {
+ Log.e(mName, s);
+ }
+
+ /**
+ * Log with error attribute
+ *
+ * @param s is string log
+ * @param e is a Throwable which logs additional information.
+ */
+ protected void loge(String s, Throwable e) {
+ Log.e(mName, s, e);
+ }
}
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 6a09fe0..93f6cf6 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -123,18 +123,15 @@ public class XmlUtils
return Integer.parseInt(nm.substring(index), base) * sign;
}
- public static final int
- convertValueToUnsignedInt(String value, int defaultValue)
- {
- if (null == value)
+ public static int convertValueToUnsignedInt(String value, int defaultValue) {
+ if (null == value) {
return defaultValue;
+ }
return parseUnsignedIntAttribute(value);
}
- public static final int
- parseUnsignedIntAttribute(CharSequence charSeq)
- {
+ public static int parseUnsignedIntAttribute(CharSequence charSeq) {
String value = charSeq.toString();
long bits;
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index b76e89d..02bd4ac 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -34,7 +34,7 @@ public class BaseIWindow extends IWindow.Stub {
}
@Override
- public void resized(Rect frame, Rect contentInsets,
+ public void resized(Rect frame, Rect overscanInsets, Rect contentInsets,
Rect visibleInsets, boolean reportDraw, Configuration newConfig) {
if (reportDraw) {
try {
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index 0cfe4fd..6120a09 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -84,6 +84,10 @@ public class ActionBarContainer extends FrameLayout {
mBackground = bg;
if (bg != null) {
bg.setCallback(this);
+ if (mActionBarView != null) {
+ mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
+ mActionBarView.getRight(), mActionBarView.getBottom());
+ }
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
@@ -98,6 +102,10 @@ public class ActionBarContainer extends FrameLayout {
mStackedBackground = bg;
if (bg != null) {
bg.setCallback(this);
+ if ((mIsStacked && mStackedBackground != null)) {
+ mStackedBackground.setBounds(mTabContainer.getLeft(), mTabContainer.getTop(),
+ mTabContainer.getRight(), mTabContainer.getBottom());
+ }
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
@@ -112,6 +120,9 @@ public class ActionBarContainer extends FrameLayout {
mSplitBackground = bg;
if (bg != null) {
bg.setCallback(this);
+ if (mIsSplit && mSplitBackground != null) {
+ mSplitBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
}
setWillNotDraw(mIsSplit ? mSplitBackground == null :
mBackground == null && mStackedBackground == null);
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index a129496..18a696e 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -41,7 +41,7 @@ public class ActionBarOverlayLayout extends FrameLayout {
private ActionBarView mActionView;
private View mActionBarBottom;
private int mLastSystemUiVisibility;
- private final Rect mZeroRect = new Rect(0, 0, 0, 0);
+ private final Rect mLocalInsets = new Rect();
static final int[] mActionBarSizeAttr = new int [] {
com.android.internal.R.attr.actionBarSize
@@ -165,13 +165,8 @@ public class ActionBarOverlayLayout extends FrameLayout {
// make sure its content is not being covered by system UI... though it
// will still be covered by the action bar since they have requested it to
// overlay.
- if ((vis & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
- changed |= applyInsets(mContent, insets, true, true, true, true);
- // The insets are now consumed.
- insets.set(0, 0, 0, 0);
- } else {
- changed |= applyInsets(mContent, mZeroRect, true, true, true, true);
- }
+ boolean res = computeFitSystemWindows(insets, mLocalInsets);
+ changed |= applyInsets(mContent, mLocalInsets, true, true, true, true);
if (stable || mActionBarTop.getVisibility() == VISIBLE) {
@@ -190,7 +185,7 @@ public class ActionBarOverlayLayout extends FrameLayout {
if (mActionView.isSplitActionBar()) {
if (stable || (mActionBarBottom != null
&& mActionBarBottom.getVisibility() == VISIBLE)) {
- // If action bar is split, adjust buttom insets for it.
+ // If action bar is split, adjust bottom insets for it.
insets.bottom += mActionBarHeight;
}
}
@@ -199,7 +194,8 @@ public class ActionBarOverlayLayout extends FrameLayout {
requestLayout();
}
- return super.fitSystemWindows(insets);
+ super.fitSystemWindows(insets);
+ return true;
}
void pullChildren() {
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 0f964b9..6bb7ac7 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -719,6 +719,7 @@ public class ActionBarView extends AbsActionBarView {
if (mSpinner == null) {
mSpinner = new Spinner(mContext, null,
com.android.internal.R.attr.actionDropDownStyle);
+ mSpinner.setId(com.android.internal.R.id.action_bar_spinner);
mListNavLayout = new LinearLayout(mContext, null,
com.android.internal.R.attr.actionBarTabBarStyle);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
deleted file mode 100644
index af3fd42..0000000
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import com.android.internal.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.graphics.Typeface;
-import android.os.Handler;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import java.lang.ref.WeakReference;
-import java.text.DateFormatSymbols;
-import java.util.Calendar;
-
-/**
- * Displays the time
- */
-public class DigitalClock extends RelativeLayout {
-
- private static final String SYSTEM = "/system/fonts/";
- private static final String SYSTEM_FONT_TIME_BACKGROUND = SYSTEM + "AndroidClock.ttf";
- private static final String SYSTEM_FONT_TIME_FOREGROUND = SYSTEM + "AndroidClock_Highlight.ttf";
- private final static String M12 = "h:mm";
- private final static String M24 = "kk:mm";
-
- private Calendar mCalendar;
- private String mFormat;
- private TextView mTimeDisplayBackground;
- private TextView mTimeDisplayForeground;
- private AmPm mAmPm;
- private ContentObserver mFormatChangeObserver;
- private int mAttached = 0; // for debugging - tells us whether attach/detach is unbalanced
-
- /* called by system on minute ticks */
- private final Handler mHandler = new Handler();
- private BroadcastReceiver mIntentReceiver;
-
- private static final Typeface sBackgroundFont;
- private static final Typeface sForegroundFont;
-
- static {
- sBackgroundFont = Typeface.createFromFile(SYSTEM_FONT_TIME_BACKGROUND);
- sForegroundFont = Typeface.createFromFile(SYSTEM_FONT_TIME_FOREGROUND);
- }
-
- private static class TimeChangedReceiver extends BroadcastReceiver {
- private WeakReference<DigitalClock> mClock;
- private Context mContext;
-
- public TimeChangedReceiver(DigitalClock clock) {
- mClock = new WeakReference<DigitalClock>(clock);
- mContext = clock.getContext();
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // Post a runnable to avoid blocking the broadcast.
- final boolean timezoneChanged =
- intent.getAction().equals(Intent.ACTION_TIMEZONE_CHANGED);
- final DigitalClock clock = mClock.get();
- if (clock != null) {
- clock.mHandler.post(new Runnable() {
- public void run() {
- if (timezoneChanged) {
- clock.mCalendar = Calendar.getInstance();
- }
- clock.updateTime();
- }
- });
- } else {
- try {
- mContext.unregisterReceiver(this);
- } catch (RuntimeException e) {
- // Shouldn't happen
- }
- }
- }
- };
-
- static class AmPm {
- private TextView mAmPmTextView;
- private String mAmString, mPmString;
-
- AmPm(View parent, Typeface tf) {
- // No longer used, uncomment if we decide to use AM/PM indicator again
- // mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm);
- if (mAmPmTextView != null && tf != null) {
- mAmPmTextView.setTypeface(tf);
- }
-
- String[] ampm = new DateFormatSymbols().getAmPmStrings();
- mAmString = ampm[0];
- mPmString = ampm[1];
- }
-
- void setShowAmPm(boolean show) {
- if (mAmPmTextView != null) {
- mAmPmTextView.setVisibility(show ? View.VISIBLE : View.GONE);
- }
- }
-
- void setIsMorning(boolean isMorning) {
- if (mAmPmTextView != null) {
- mAmPmTextView.setText(isMorning ? mAmString : mPmString);
- }
- }
- }
-
- private static class FormatChangeObserver extends ContentObserver {
- private WeakReference<DigitalClock> mClock;
- private Context mContext;
- public FormatChangeObserver(DigitalClock clock) {
- super(new Handler());
- mClock = new WeakReference<DigitalClock>(clock);
- mContext = clock.getContext();
- }
- @Override
- public void onChange(boolean selfChange) {
- DigitalClock digitalClock = mClock.get();
- if (digitalClock != null) {
- digitalClock.setDateFormat();
- digitalClock.updateTime();
- } else {
- try {
- mContext.getContentResolver().unregisterContentObserver(this);
- } catch (RuntimeException e) {
- // Shouldn't happen
- }
- }
- }
- }
-
- public DigitalClock(Context context) {
- this(context, null);
- }
-
- public DigitalClock(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- /* The time display consists of two tones. That's why we have two overlapping text views. */
- mTimeDisplayBackground = (TextView) findViewById(R.id.timeDisplayBackground);
- mTimeDisplayBackground.setTypeface(sBackgroundFont);
- mTimeDisplayBackground.setVisibility(View.INVISIBLE);
-
- mTimeDisplayForeground = (TextView) findViewById(R.id.timeDisplayForeground);
- mTimeDisplayForeground.setTypeface(sForegroundFont);
- mAmPm = new AmPm(this, null);
- mCalendar = Calendar.getInstance();
-
- setDateFormat();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- mAttached++;
-
- /* monitor time ticks, time changed, timezone */
- if (mIntentReceiver == null) {
- mIntentReceiver = new TimeChangedReceiver(this);
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_TIME_TICK);
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
- mContext.registerReceiver(mIntentReceiver, filter);
- }
-
- /* monitor 12/24-hour display preference */
- if (mFormatChangeObserver == null) {
- mFormatChangeObserver = new FormatChangeObserver(this);
- mContext.getContentResolver().registerContentObserver(
- Settings.System.CONTENT_URI, true, mFormatChangeObserver);
- }
-
- updateTime();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
-
- mAttached--;
-
- if (mIntentReceiver != null) {
- mContext.unregisterReceiver(mIntentReceiver);
- }
- if (mFormatChangeObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(
- mFormatChangeObserver);
- }
-
- mFormatChangeObserver = null;
- mIntentReceiver = null;
- }
-
- void updateTime(Calendar c) {
- mCalendar = c;
- updateTime();
- }
-
- public void updateTime() {
- mCalendar.setTimeInMillis(System.currentTimeMillis());
-
- CharSequence newTime = DateFormat.format(mFormat, mCalendar);
- mTimeDisplayBackground.setText(newTime);
- mTimeDisplayForeground.setText(newTime);
- mAmPm.setIsMorning(mCalendar.get(Calendar.AM_PM) == 0);
- }
-
- private void setDateFormat() {
- mFormat = android.text.format.DateFormat.is24HourFormat(getContext())
- ? M24 : M12;
- mAmPm.setShowAmPm(mFormat.equals(M12));
- }
-}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 907b52a..555c7c2 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -123,14 +123,14 @@ public class LockPatternUtils {
*/
public static final int ID_DEFAULT_STATUS_WIDGET = -2;
- protected final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
- protected final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
- protected final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
+ public final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently";
+ public final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline";
+ public final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen";
public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type";
public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate";
- protected final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
- protected final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
- protected final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
+ public final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt";
+ public final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled";
+ public final static String LOCKSCREEN_OPTIONS = "lockscreen.options";
public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK
= "lockscreen.biometric_weak_fallback";
public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY
@@ -138,7 +138,7 @@ public class LockPatternUtils {
public final static String LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS
= "lockscreen.power_button_instantly_locks";
- protected final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
+ public final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory";
private final Context mContext;
private final ContentResolver mContentResolver;
diff --git a/core/java/com/android/internal/widget/LockSettingsService.java b/core/java/com/android/internal/widget/LockSettingsService.java
deleted file mode 100644
index 4ecbd16..0000000
--- a/core/java/com/android/internal/widget/LockSettingsService.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * Copyright (C) 2012 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.widget;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.Arrays;
-
-/**
- * Keeps the lock pattern/password data and related settings for each user.
- * Used by LockPatternUtils. Needs to be a service because Settings app also needs
- * to be able to save lockscreen information for secondary users.
- * @hide
- */
-public class LockSettingsService extends ILockSettings.Stub {
-
- private final DatabaseHelper mOpenHelper;
- private static final String TAG = "LockSettingsService";
-
- private static final String TABLE = "locksettings";
- private static final String COLUMN_KEY = "name";
- private static final String COLUMN_USERID = "user";
- private static final String COLUMN_VALUE = "value";
-
- private static final String[] COLUMNS_FOR_QUERY = {
- COLUMN_VALUE
- };
-
- private static final String SYSTEM_DIRECTORY = "/system/";
- private static final String LOCK_PATTERN_FILE = "gesture.key";
- private static final String LOCK_PASSWORD_FILE = "password.key";
-
- private final Context mContext;
-
- public LockSettingsService(Context context) {
- mContext = context;
- // Open the database
- mOpenHelper = new DatabaseHelper(mContext);
- }
-
- public void systemReady() {
- migrateOldData();
- }
-
- private void migrateOldData() {
- try {
- if (getString("migrated", null, 0) != null) {
- // Already migrated
- return;
- }
-
- final ContentResolver cr = mContext.getContentResolver();
- for (String validSetting : VALID_SETTINGS) {
- String value = Settings.Secure.getString(cr, validSetting);
- if (value != null) {
- setString(validSetting, value, 0);
- }
- }
- // No need to move the password / pattern files. They're already in the right place.
- setString("migrated", "true", 0);
- Slog.i(TAG, "Migrated lock settings to new location");
- } catch (RemoteException re) {
- Slog.e(TAG, "Unable to migrate old data");
- }
- }
-
- private static final void checkWritePermission(int userId) {
- final int callingUid = Binder.getCallingUid();
- if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
- throw new SecurityException("uid=" + callingUid
- + " not authorized to write lock settings");
- }
- }
-
- private static final void checkPasswordReadPermission(int userId) {
- final int callingUid = Binder.getCallingUid();
- if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) {
- throw new SecurityException("uid=" + callingUid
- + " not authorized to read lock password");
- }
- }
-
- private static final void checkReadPermission(int userId) {
- final int callingUid = Binder.getCallingUid();
- if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID
- && UserHandle.getUserId(callingUid) != userId) {
- throw new SecurityException("uid=" + callingUid
- + " not authorized to read settings of user " + userId);
- }
- }
-
- @Override
- public void setBoolean(String key, boolean value, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeToDb(key, value ? "1" : "0", userId);
- }
-
- @Override
- public void setLong(String key, long value, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeToDb(key, Long.toString(value), userId);
- }
-
- @Override
- public void setString(String key, String value, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeToDb(key, value, userId);
- }
-
- @Override
- public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
- //checkReadPermission(userId);
-
- String value = readFromDb(key, null, userId);
- return TextUtils.isEmpty(value) ?
- defaultValue : (value.equals("1") || value.equals("true"));
- }
-
- @Override
- public long getLong(String key, long defaultValue, int userId) throws RemoteException {
- //checkReadPermission(userId);
-
- String value = readFromDb(key, null, userId);
- return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
- }
-
- @Override
- public String getString(String key, String defaultValue, int userId) throws RemoteException {
- //checkReadPermission(userId);
-
- return readFromDb(key, defaultValue, userId);
- }
-
- private String getLockPatternFilename(int userId) {
- String dataSystemDirectory =
- android.os.Environment.getDataDirectory().getAbsolutePath() +
- SYSTEM_DIRECTORY;
- if (userId == 0) {
- // Leave it in the same place for user 0
- return dataSystemDirectory + LOCK_PATTERN_FILE;
- } else {
- return new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
- .getAbsolutePath();
- }
- }
-
- private String getLockPasswordFilename(int userId) {
- String dataSystemDirectory =
- android.os.Environment.getDataDirectory().getAbsolutePath() +
- SYSTEM_DIRECTORY;
- if (userId == 0) {
- // Leave it in the same place for user 0
- return dataSystemDirectory + LOCK_PASSWORD_FILE;
- } else {
- return new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
- .getAbsolutePath();
- }
- }
-
- @Override
- public boolean havePassword(int userId) throws RemoteException {
- // Do we need a permissions check here?
-
- return new File(getLockPasswordFilename(userId)).length() > 0;
- }
-
- @Override
- public boolean havePattern(int userId) throws RemoteException {
- // Do we need a permissions check here?
-
- return new File(getLockPatternFilename(userId)).length() > 0;
- }
-
- @Override
- public void setLockPattern(byte[] hash, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeFile(getLockPatternFilename(userId), hash);
- }
-
- @Override
- public boolean checkPattern(byte[] hash, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
- try {
- // Read all the bytes from the file
- RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
- final byte[] stored = new byte[(int) raf.length()];
- int got = raf.read(stored, 0, stored.length);
- raf.close();
- if (got <= 0) {
- return true;
- }
- // Compare the hash from the file with the entered pattern's hash
- return Arrays.equals(stored, hash);
- } catch (FileNotFoundException fnfe) {
- Slog.e(TAG, "Cannot read file " + fnfe);
- return true;
- } catch (IOException ioe) {
- Slog.e(TAG, "Cannot read file " + ioe);
- return true;
- }
- }
-
- @Override
- public void setLockPassword(byte[] hash, int userId) throws RemoteException {
- checkWritePermission(userId);
-
- writeFile(getLockPasswordFilename(userId), hash);
- }
-
- @Override
- public boolean checkPassword(byte[] hash, int userId) throws RemoteException {
- checkPasswordReadPermission(userId);
-
- try {
- // Read all the bytes from the file
- RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
- final byte[] stored = new byte[(int) raf.length()];
- int got = raf.read(stored, 0, stored.length);
- raf.close();
- if (got <= 0) {
- return true;
- }
- // Compare the hash from the file with the entered password's hash
- return Arrays.equals(stored, hash);
- } catch (FileNotFoundException fnfe) {
- Slog.e(TAG, "Cannot read file " + fnfe);
- return true;
- } catch (IOException ioe) {
- Slog.e(TAG, "Cannot read file " + ioe);
- return true;
- }
- }
-
- @Override
- public void removeUser(int userId) {
- checkWritePermission(userId);
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- try {
- File file = new File(getLockPasswordFilename(userId));
- if (file.exists()) {
- file.delete();
- }
- file = new File(getLockPatternFilename(userId));
- if (file.exists()) {
- file.delete();
- }
-
- db.beginTransaction();
- db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- private void writeFile(String name, byte[] hash) {
- try {
- // Write the hash to file
- RandomAccessFile raf = new RandomAccessFile(name, "rw");
- // Truncate the file if pattern is null, to clear the lock
- if (hash == null || hash.length == 0) {
- raf.setLength(0);
- } else {
- raf.write(hash, 0, hash.length);
- }
- raf.close();
- } catch (IOException ioe) {
- Slog.e(TAG, "Error writing to file " + ioe);
- }
- }
-
- private void writeToDb(String key, String value, int userId) {
- writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
- }
-
- private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
- ContentValues cv = new ContentValues();
- cv.put(COLUMN_KEY, key);
- cv.put(COLUMN_USERID, userId);
- cv.put(COLUMN_VALUE, value);
-
- db.beginTransaction();
- try {
- db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
- new String[] {key, Integer.toString(userId)});
- db.insert(TABLE, null, cv);
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
-
- private String readFromDb(String key, String defaultValue, int userId) {
- Cursor cursor;
- String result = defaultValue;
- SQLiteDatabase db = mOpenHelper.getReadableDatabase();
- if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
- COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
- new String[] { Integer.toString(userId), key },
- null, null, null)) != null) {
- if (cursor.moveToFirst()) {
- result = cursor.getString(0);
- }
- cursor.close();
- }
- return result;
- }
-
- class DatabaseHelper extends SQLiteOpenHelper {
- private static final String TAG = "LockSettingsDB";
- private static final String DATABASE_NAME = "locksettings.db";
-
- private static final int DATABASE_VERSION = 1;
-
- public DatabaseHelper(Context context) {
- super(context, DATABASE_NAME, null, DATABASE_VERSION);
- setWriteAheadLoggingEnabled(true);
- }
-
- private void createTable(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE + " (" +
- "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
- COLUMN_KEY + " TEXT," +
- COLUMN_USERID + " INTEGER," +
- COLUMN_VALUE + " TEXT" +
- ");");
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- createTable(db);
- initializeDefaults(db);
- }
-
- private void initializeDefaults(SQLiteDatabase db) {
- // Get the lockscreen default from a system property, if available
- boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default",
- false);
- if (lockScreenDisable) {
- writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
- }
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
- // Nothing yet
- }
- }
-
- private static final String[] VALID_SETTINGS = new String[] {
- LockPatternUtils.LOCKOUT_PERMANENT_KEY,
- LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
- LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
- LockPatternUtils.PASSWORD_TYPE_KEY,
- LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
- LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
- LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
- LockPatternUtils.LOCKSCREEN_OPTIONS,
- LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
- LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
- LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
- LockPatternUtils.PASSWORD_HISTORY_KEY,
- Secure.LOCK_PATTERN_ENABLED,
- Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
- Secure.LOCK_PATTERN_VISIBLE,
- Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
- };
-}
diff --git a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
index 273f6fe..ba113a3 100644
--- a/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
+++ b/core/java/com/android/internal/widget/SizeAdaptiveLayout.java
@@ -225,13 +225,11 @@ public class SizeAdaptiveLayout extends ViewGroup {
if (unboundedView != null) {
tallestView = unboundedView;
}
- if (heightMode == MeasureSpec.UNSPECIFIED) {
- return tallestView;
- }
- if (heightSize > tallestViewSize) {
+ if (heightMode == MeasureSpec.UNSPECIFIED || heightSize > tallestViewSize) {
return tallestView;
+ } else {
+ return smallestView;
}
- return smallestView;
}
@Override
@@ -272,10 +270,10 @@ public class SizeAdaptiveLayout extends ViewGroup {
final int childWidth = mActiveChild.getMeasuredWidth();
final int childHeight = mActiveChild.getMeasuredHeight();
// TODO investigate setting LAYER_TYPE_HARDWARE on mLastActive
- mActiveChild.layout(0, 0, 0 + childWidth, 0 + childHeight);
+ mActiveChild.layout(0, 0, childWidth, childHeight);
if (DEBUG) Log.d(TAG, "got modesty offset of " + mModestyPanelTop);
- mModestyPanel.layout(0, mModestyPanelTop, 0 + childWidth, mModestyPanelTop + childHeight);
+ mModestyPanel.layout(0, mModestyPanelTop, childWidth, mModestyPanelTop + childHeight);
}
@Override