summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/ActionBar.java23
-rw-r--r--core/java/android/app/Activity.java57
-rw-r--r--core/java/android/app/ActivityManager.java21
-rw-r--r--core/java/android/app/ActivityManagerNative.java8
-rw-r--r--core/java/android/app/ActivityTransitionCoordinator.java227
-rw-r--r--core/java/android/app/ContextImpl.java37
-rw-r--r--core/java/android/app/EnterTransitionCoordinator.java199
-rw-r--r--core/java/android/app/ExitTransitionCoordinator.java194
-rw-r--r--core/java/android/app/IActivityManager.java3
-rw-r--r--core/java/android/app/Notification.java84
-rw-r--r--core/java/android/app/VoiceInteractor.java140
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java38
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/app/backup/BackupTransport.java415
-rw-r--r--core/java/android/app/task/Task.java71
-rw-r--r--core/java/android/app/task/TaskManager.java5
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java12
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java2
-rw-r--r--core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java645
-rw-r--r--core/java/android/bluetooth/BluetoothLeAdvertiser.java615
-rw-r--r--core/java/android/bluetooth/BluetoothLeScanner.aidl20
-rw-r--r--core/java/android/bluetooth/BluetoothLeScanner.java759
-rw-r--r--core/java/android/bluetooth/BluetoothProfile.java6
-rw-r--r--core/java/android/bluetooth/IBluetoothGatt.aidl17
-rw-r--r--core/java/android/bluetooth/le/AdvertiseCallback.java68
-rw-r--r--core/java/android/bluetooth/le/AdvertiseSettings.aidl (renamed from core/java/android/bluetooth/BluetoothLeScanFilter.aidl)4
-rw-r--r--core/java/android/bluetooth/le/AdvertiseSettings.java218
-rw-r--r--core/java/android/bluetooth/le/AdvertisementData.aidl (renamed from core/java/android/bluetooth/BluetoothLeAdvertiser.aidl)4
-rw-r--r--core/java/android/bluetooth/le/AdvertisementData.java344
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeAdvertiser.java368
-rw-r--r--core/java/android/bluetooth/le/BluetoothLeScanner.java371
-rw-r--r--core/java/android/bluetooth/le/ScanCallback.java79
-rw-r--r--core/java/android/bluetooth/le/ScanFilter.aidl (renamed from core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl)4
-rw-r--r--core/java/android/bluetooth/le/ScanFilter.java (renamed from core/java/android/bluetooth/BluetoothLeScanFilter.java)269
-rw-r--r--core/java/android/bluetooth/le/ScanRecord.java278
-rw-r--r--core/java/android/bluetooth/le/ScanResult.aidl (renamed from core/java/android/tv/TvInputInfo.aidl)4
-rw-r--r--core/java/android/bluetooth/le/ScanResult.java162
-rw-r--r--core/java/android/bluetooth/le/ScanSettings.aidl19
-rw-r--r--core/java/android/bluetooth/le/ScanSettings.java221
-rw-r--r--core/java/android/content/Context.java51
-rw-r--r--core/java/android/content/ContextWrapper.java5
-rw-r--r--core/java/android/content/IRestrictionsManager.aidl (renamed from core/java/android/tv/ITvInputHardwareCallback.aidl)13
-rw-r--r--core/java/android/content/RestrictionEntry.java157
-rw-r--r--core/java/android/content/RestrictionsManager.java344
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl7
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java44
-rw-r--r--core/java/android/content/pm/LauncherApps.java147
-rw-r--r--core/java/android/content/pm/PackageParser.java11
-rw-r--r--core/java/android/content/res/Resources.java18
-rw-r--r--core/java/android/hardware/Sensor.java408
-rw-r--r--core/java/android/hardware/SensorEventListener.java12
-rw-r--r--core/java/android/hardware/SensorManager.java12
-rw-r--r--core/java/android/hardware/SystemSensorManager.java25
-rw-r--r--core/java/android/hardware/camera2/CameraCaptureSession.java97
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java229
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java101
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java4
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java48
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java203
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java291
-rw-r--r--core/java/android/hardware/camera2/TotalCaptureResult.java84
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDevice.java31
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java219
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java158
-rw-r--r--core/java/android/hardware/camera2/params/ColorSpaceTransform.java8
-rw-r--r--core/java/android/hardware/camera2/params/LensShadingMap.java8
-rw-r--r--core/java/android/hardware/camera2/params/MeteringRectangle.java64
-rw-r--r--core/java/android/hardware/hdmi/HdmiCec.java4
-rw-r--r--core/java/android/hardware/hdmi/HdmiCecClient.java38
-rw-r--r--core/java/android/hardware/hdmi/HdmiCecManager.java10
-rw-r--r--core/java/android/hardware/hdmi/HdmiCecMessage.java2
-rw-r--r--core/java/android/hardware/hdmi/HdmiPlaybackClient.java2
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java7
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java3
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java78
-rw-r--r--core/java/android/net/ConnectivityManager.java223
-rw-r--r--core/java/android/net/ConnectivityServiceProtocol.java70
-rw-r--r--core/java/android/net/IConnectivityManager.aidl4
-rw-r--r--core/java/android/net/Network.java19
-rw-r--r--core/java/android/net/NetworkAgent.java325
-rw-r--r--core/java/android/net/NetworkFactory.java275
-rw-r--r--core/java/android/net/NetworkInfo.java14
-rw-r--r--core/java/android/net/NetworkRequest.java22
-rw-r--r--core/java/android/nfc/cardemulation/AidGroup.java17
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulation.java51
-rw-r--r--core/java/android/os/BaseBundle.java (renamed from core/java/android/os/CommonBundle.java)114
-rw-r--r--core/java/android/os/BatteryStats.java12
-rw-r--r--core/java/android/os/Bundle.java363
-rw-r--r--core/java/android/os/Environment.java4
-rw-r--r--core/java/android/os/PersistableBundle.java470
-rw-r--r--core/java/android/os/Trace.java2
-rw-r--r--core/java/android/os/storage/StorageManager.java28
-rw-r--r--core/java/android/preference/SeekBarVolumizer.java144
-rw-r--r--core/java/android/preference/VolumePreference.java3
-rw-r--r--core/java/android/provider/ContactsContract.java25
-rw-r--r--core/java/android/provider/Settings.java6
-rw-r--r--core/java/android/provider/TvContract.java741
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java33
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java262
-rw-r--r--core/java/android/speech/tts/Markup.java537
-rw-r--r--core/java/android/speech/tts/SynthesisRequestV2.java38
-rw-r--r--core/java/android/speech/tts/TextToSpeechClient.java156
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java53
-rw-r--r--core/java/android/speech/tts/Utterance.java595
-rw-r--r--core/java/android/tv/ITvInputClient.aidl32
-rw-r--r--core/java/android/tv/ITvInputHardware.aidl46
-rw-r--r--core/java/android/tv/ITvInputManager.aidl58
-rw-r--r--core/java/android/tv/ITvInputService.aidl31
-rw-r--r--core/java/android/tv/ITvInputServiceCallback.aidl28
-rw-r--r--core/java/android/tv/ITvInputSession.aidl39
-rw-r--r--core/java/android/tv/ITvInputSessionCallback.aidl28
-rw-r--r--core/java/android/tv/ITvInputSessionWrapper.java179
-rw-r--r--core/java/android/tv/TvInputHardwareInfo.aidl20
-rw-r--r--core/java/android/tv/TvInputHardwareInfo.java93
-rw-r--r--core/java/android/tv/TvInputInfo.java163
-rw-r--r--core/java/android/tv/TvInputManager.java784
-rw-r--r--core/java/android/tv/TvInputService.java551
-rw-r--r--core/java/android/tv/TvStreamConfig.aidl20
-rw-r--r--core/java/android/tv/TvStreamConfig.java157
-rw-r--r--core/java/android/tv/TvView.java383
-rw-r--r--core/java/android/view/GLES20Canvas.java114
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java2
-rw-r--r--core/java/android/view/GLRenderer.java1534
-rw-r--r--core/java/android/view/HardwareCanvas.java42
-rw-r--r--core/java/android/view/HardwareLayer.java140
-rw-r--r--core/java/android/view/HardwareRenderer.java137
-rw-r--r--core/java/android/view/IWindowManager.aidl3
-rw-r--r--core/java/android/view/RenderNode.java8
-rw-r--r--core/java/android/view/RenderNodeAnimator.java16
-rw-r--r--core/java/android/view/SurfaceControl.java34
-rw-r--r--core/java/android/view/TextureView.java4
-rw-r--r--core/java/android/view/ThreadedRenderer.java80
-rw-r--r--core/java/android/view/View.java24
-rw-r--r--core/java/android/view/ViewGroup.java129
-rw-r--r--core/java/android/view/ViewPropertyAnimator.java16
-rw-r--r--core/java/android/view/ViewRootImpl.java13
-rw-r--r--core/java/android/view/WindowManager.java9
-rw-r--r--core/java/android/view/WindowManagerGlobal.java7
-rw-r--r--core/java/android/view/WindowManagerPolicy.java32
-rw-r--r--core/java/android/view/textservice/SpellCheckerSession.java8
-rw-r--r--core/java/android/webkit/WebSettings.java32
-rw-r--r--core/java/android/widget/AbsListView.java35
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java1
-rw-r--r--core/java/android/widget/ActionMenuView.java60
-rw-r--r--core/java/android/widget/DatePicker.java4
-rw-r--r--core/java/android/widget/ImageView.java2
-rw-r--r--core/java/android/widget/ProgressBar.java5
-rw-r--r--core/java/android/widget/SuggestionsAdapter.java2
-rw-r--r--core/java/android/widget/Toolbar.java704
149 files changed, 9397 insertions, 9975 deletions
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 3c3df01..d4c4318 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.Gravity;
+import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -1013,6 +1014,26 @@ public abstract class ActionBar {
return null;
}
+ /** @hide */
+ public boolean openOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean invalidateOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean onMenuKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /** @hide */
+ public boolean collapseActionView() {
+ return false;
+ }
+
/**
* Listener interface for ActionBar navigation events.
*
@@ -1291,6 +1312,7 @@ public abstract class ActionBar {
public LayoutParams(int width, int height) {
super(width, height);
+ this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
}
public LayoutParams(int width, int height, int gravity) {
@@ -1305,6 +1327,7 @@ public abstract class ActionBar {
public LayoutParams(LayoutParams source) {
super(source);
+ this.gravity = source.gravity;
}
public LayoutParams(ViewGroup.LayoutParams source) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 07de85c..23b5f29 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -716,6 +716,7 @@ public class Activity extends ContextThemeWrapper
HashMap<String, Object> children;
ArrayList<Fragment> fragments;
ArrayMap<String, LoaderManagerImpl> loaders;
+ VoiceInteractor voiceInteractor;
}
/* package */ NonConfigurationInstances mLastNonConfigurationInstances;
@@ -920,6 +921,9 @@ public class Activity extends ContextThemeWrapper
}
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.attachActivity(this);
+ }
mCalled = true;
}
@@ -1830,7 +1834,8 @@ public class Activity extends ContextThemeWrapper
}
}
}
- if (activity == null && children == null && fragments == null && !retainLoaders) {
+ if (activity == null && children == null && fragments == null && !retainLoaders
+ && mVoiceInteractor == null) {
return null;
}
@@ -1839,6 +1844,7 @@ public class Activity extends ContextThemeWrapper
nci.children = children;
nci.fragments = fragments;
nci.loaders = mAllLoaderManagers;
+ nci.voiceInteractor = mVoiceInteractor;
return nci;
}
@@ -2069,15 +2075,16 @@ public class Activity extends ContextThemeWrapper
* <p>In order to use a Toolbar within the Activity's window content the application
* must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
*
- * @param actionBar Toolbar to set as the Activity's action bar
+ * @param toolbar Toolbar to set as the Activity's action bar
*/
- public void setActionBar(@Nullable Toolbar actionBar) {
+ public void setActionBar(@Nullable Toolbar toolbar) {
if (getActionBar() instanceof WindowDecorActionBar) {
throw new IllegalStateException("This Activity already has an action bar supplied " +
"by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
"android:windowActionBar to false in your theme to use a Toolbar instead.");
}
- mActionBar = new ToolbarActionBar(actionBar);
+ mActionBar = new ToolbarActionBar(toolbar, getTitle(), this);
+ mActionBar.invalidateOptionsMenu();
}
/**
@@ -2443,6 +2450,10 @@ public class Activity extends ContextThemeWrapper
* but you can override this to do whatever you want.
*/
public void onBackPressed() {
+ if (mActionBar != null && mActionBar.collapseActionView()) {
+ return;
+ }
+
if (!mFragments.popBackStackImmediate()) {
finishAfterTransition();
}
@@ -2654,6 +2665,14 @@ public class Activity extends ContextThemeWrapper
*/
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
+
+ // Let action bars open menus in response to the menu key prioritized over
+ // the window handling it
+ if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
+ mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
+ return true;
+ }
+
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
@@ -2901,7 +2920,9 @@ public class Activity extends ContextThemeWrapper
* time it needs to be displayed.
*/
public void invalidateOptionsMenu() {
- mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) {
+ mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ }
}
/**
@@ -3111,7 +3132,9 @@ public class Activity extends ContextThemeWrapper
* open, this method does nothing.
*/
public void openOptionsMenu() {
- mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ if (mActionBar == null || !mActionBar.openOptionsMenu()) {
+ mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ }
}
/**
@@ -4194,7 +4217,11 @@ public class Activity extends ContextThemeWrapper
*/
public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode) {
- startActivityFromFragment(fragment, intent, requestCode, null);
+ Bundle options = null;
+ if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
+ options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
+ }
+ startActivityFromFragment(fragment, intent, requestCode, options);
}
/**
@@ -4219,6 +4246,9 @@ public class Activity extends ContextThemeWrapper
*/
public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode, @Nullable Bundle options) {
+ if (options != null) {
+ mActivityTransitionState.startExitOutTransition(this, options);
+ }
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, fragment,
@@ -5625,8 +5655,14 @@ public class Activity extends ContextThemeWrapper
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
- mVoiceInteractor = voiceInteractor != null
- ? new VoiceInteractor(this, this, voiceInteractor, Looper.myLooper()) : null;
+ if (voiceInteractor != null) {
+ if (lastNonConfigurationInstances != null) {
+ mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
+ } else {
+ mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
+ Looper.myLooper());
+ }
+ }
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
@@ -5835,6 +5871,9 @@ public class Activity extends ContextThemeWrapper
if (mLoaderManager != null) {
mLoaderManager.doDestroy();
}
+ if (mVoiceInteractor != null) {
+ mVoiceInteractor.detachActivity();
+ }
}
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index abcb0d0..788ac56 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -738,7 +738,7 @@ public class ActivityManager {
public static final int RECENT_INCLUDE_PROFILES = 0x0004;
/**
- * Return a list of the tasks that the user has recently launched, with
+ * <p></p>Return a list of the tasks that the user has recently launched, with
* the most recent being first and older ones after in order.
*
* <p><b>Note: this method is only intended for debugging and presenting
@@ -750,6 +750,15 @@ public class ActivityManager {
* same time, assumptions made about the meaning of the data here for
* purposes of control flow will be incorrect.</p>
*
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method is
+ * no longer available to third party applications: as the introduction of
+ * document-centric recents means
+ * it can leak personal information to the caller. For backwards compatibility,
+ * it will still return a small subset of its data: at least the caller's
+ * own tasks (though see {@link #getAppTasks()} for the correct supported
+ * way to retrieve that information), and possibly some other tasks
+ * such as home that are known to not be sensitive.
+ *
* @param maxNum The maximum number of entries to return in the list. The
* actual number returned may be smaller, depending on how many tasks the
* user has started and the maximum number the system can remember.
@@ -762,6 +771,7 @@ public class ActivityManager {
* @throws SecurityException Throws SecurityException if the caller does
* not hold the {@link android.Manifest.permission#GET_TASKS} permission.
*/
+ @Deprecated
public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
throws SecurityException {
try {
@@ -946,6 +956,14 @@ public class ActivityManager {
* same time, assumptions made about the meaning of the data here for
* purposes of control flow will be incorrect.</p>
*
+ * @deprecated As of {@link android.os.Build.VERSION_CODES#L}, this method
+ * is no longer available to third party
+ * applications: the introduction of document-centric recents means
+ * it can leak person information to the caller. For backwards compatibility,
+ * it will still retu rn a small subset of its data: at least the caller's
+ * own tasks, and possibly some other tasks
+ * such as home that are known to not be sensitive.
+ *
* @param maxNum The maximum number of entries to return in the list. The
* actual number returned may be smaller, depending on how many tasks the
* user has started.
@@ -956,6 +974,7 @@ public class ActivityManager {
* @throws SecurityException Throws SecurityException if the caller does
* not hold the {@link android.Manifest.permission#GET_TASKS} permission.
*/
+ @Deprecated
public List<RunningTaskInfo> getRunningTasks(int maxNum)
throws SecurityException {
try {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 0f65454..56462ae 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -943,7 +943,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
b = data.readStrongBinder();
IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
int userId = data.readInt();
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
+ String abiOverride = data.readString();
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId,
+ abiOverride);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -3339,7 +3341,8 @@ class ActivityManagerProxy implements IActivityManager
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher,
- IUiAutomationConnection connection, int userId) throws RemoteException {
+ IUiAutomationConnection connection, int userId, String instructionSet)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3350,6 +3353,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
data.writeStrongBinder(connection != null ? connection.asBinder() : null);
data.writeInt(userId);
+ data.writeString(instructionSet);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index d08978b..b739387 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -15,14 +15,23 @@
*/
package android.app;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.transition.Transition;
import android.transition.TransitionSet;
import android.util.ArrayMap;
+import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.Window;
import android.widget.ImageView;
@@ -120,8 +129,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
- // The background fade in/out duration. 150ms is pretty quick, but not abrupt.
- public static final int FADE_BACKGROUND_DURATION_MS = 150;
+ // The background fade in/out duration. TODO: Enable tuning this.
+ public static final int FADE_BACKGROUND_DURATION_MS = 300;
protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
@@ -181,6 +190,16 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
*/
public static final int MSG_CANCEL = 106;
+ /**
+ * When returning, this is the destination location for the shared element.
+ */
+ public static final int MSG_SHARED_ELEMENT_DESTINATION = 107;
+
+ /**
+ * Send the shared element positions.
+ */
+ public static final int MSG_SEND_SHARED_ELEMENT_DESTINATION = 108;
+
final private Window mWindow;
final protected ArrayList<String> mAllSharedElementNames;
final protected ArrayList<View> mSharedElements = new ArrayList<View>();
@@ -334,6 +353,210 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver {
protected abstract Transition getViewsTransition();
+ private static void setSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] parentLoc) {
+ Bundle sharedElementBundle = transitionArgs.getBundle(name);
+ if (sharedElementBundle == null) {
+ return;
+ }
+
+ if (view instanceof ImageView) {
+ int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
+ if (scaleTypeInt >= 0) {
+ ImageView imageView = (ImageView) view;
+ ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
+ imageView.setScaleType(scaleType);
+ if (scaleType == ImageView.ScaleType.MATRIX) {
+ float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
+ Matrix matrix = new Matrix();
+ matrix.setValues(matrixValues);
+ imageView.setImageMatrix(matrix);
+ }
+ }
+ }
+
+ float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
+ view.setTranslationZ(z);
+
+ int x = sharedElementBundle.getInt(KEY_SCREEN_X);
+ int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
+ int width = sharedElementBundle.getInt(KEY_WIDTH);
+ int height = sharedElementBundle.getInt(KEY_HEIGHT);
+
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+
+ int left = x - parentLoc[0];
+ int top = y - parentLoc[1];
+ int right = left + width;
+ int bottom = top + height;
+ view.layout(left, top, right, bottom);
+ }
+
+ protected ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
+ Bundle sharedElementState, final ArrayList<View> snapshots) {
+ ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
+ new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
+ if (sharedElementState != null) {
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElementNames.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mSharedElementNames.get(i);
+ Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
+ name, sharedElementState);
+ if (originalState != null) {
+ originalImageState.put((ImageView) sharedElement, originalState);
+ }
+ View parent = (View) sharedElement.getParent();
+ parent.getLocationOnScreen(tempLoc);
+ setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
+ }
+ }
+ mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
+
+ getDecor().getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ mListener.setSharedElementEnd(mSharedElementNames, mSharedElements,
+ snapshots);
+ return true;
+ }
+ }
+ );
+ return originalImageState;
+ }
+
+ private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
+ Bundle transitionArgs) {
+ if (!(view instanceof ImageView)) {
+ return null;
+ }
+ Bundle bundle = transitionArgs.getBundle(name);
+ if (bundle == null) {
+ return null;
+ }
+ int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
+ if (scaleTypeInt < 0) {
+ return null;
+ }
+
+ ImageView imageView = (ImageView) view;
+ ImageView.ScaleType originalScaleType = imageView.getScaleType();
+
+ Matrix originalMatrix = null;
+ if (originalScaleType == ImageView.ScaleType.MATRIX) {
+ originalMatrix = new Matrix(imageView.getImageMatrix());
+ }
+
+ return Pair.create(originalScaleType, originalMatrix);
+ }
+
+ protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
+ int numSharedElements = names.size();
+ if (numSharedElements == 0) {
+ return null;
+ }
+ ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
+ Context context = getWindow().getContext();
+ int[] parentLoc = new int[2];
+ getDecor().getLocationOnScreen(parentLoc);
+ for (String name: names) {
+ Bundle sharedElementBundle = state.getBundle(name);
+ if (sharedElementBundle != null) {
+ Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
+ View snapshot = new View(context);
+ Resources resources = getWindow().getContext().getResources();
+ if (bitmap != null) {
+ snapshot.setBackground(new BitmapDrawable(resources, bitmap));
+ }
+ snapshot.setViewName(name);
+ setSharedElementState(snapshot, name, state, parentLoc);
+ snapshots.add(snapshot);
+ }
+ }
+ return snapshots;
+ }
+
+ protected static void setOriginalImageViewState(
+ ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
+ for (int i = 0; i < originalState.size(); i++) {
+ ImageView imageView = originalState.keyAt(i);
+ Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
+ imageView.setScaleType(state.first);
+ imageView.setImageMatrix(state.second);
+ }
+ }
+
+ protected Bundle captureSharedElementState() {
+ Bundle bundle = new Bundle();
+ int[] tempLoc = new int[2];
+ for (int i = 0; i < mSharedElementNames.size(); i++) {
+ View sharedElement = mSharedElements.get(i);
+ String name = mSharedElementNames.get(i);
+ captureSharedElementState(sharedElement, name, bundle, tempLoc);
+ }
+ return bundle;
+ }
+
+ /**
+ * Captures placement information for Views with a shared element name for
+ * Activity Transitions.
+ *
+ * @param view The View to capture the placement information for.
+ * @param name The shared element name in the target Activity to apply the placement
+ * information for.
+ * @param transitionArgs Bundle to store shared element placement information.
+ * @param tempLoc A temporary int[2] for capturing the current location of views.
+ */
+ private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
+ int[] tempLoc) {
+ Bundle sharedElementBundle = new Bundle();
+ view.getLocationOnScreen(tempLoc);
+ float scaleX = view.getScaleX();
+ sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
+ int width = Math.round(view.getWidth() * scaleX);
+ sharedElementBundle.putInt(KEY_WIDTH, width);
+
+ float scaleY = view.getScaleY();
+ sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
+ int height = Math.round(view.getHeight() * scaleY);
+ sharedElementBundle.putInt(KEY_HEIGHT, height);
+
+ sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
+
+ if (width > 0 && height > 0) {
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
+ }
+
+ if (view instanceof ImageView) {
+ ImageView imageView = (ImageView) view;
+ int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
+ sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
+ if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
+ float[] matrix = new float[9];
+ imageView.getImageMatrix().getValues(matrix);
+ sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
+ }
+ }
+
+ transitionArgs.putBundle(name, sharedElementBundle);
+ }
+
+ private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
+ for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
+ if (scaleType == SCALE_TYPE_VALUES[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
private Rect mEpicenter;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ff8688d..e03224c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -35,7 +35,9 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.IIntentReceiver;
import android.content.IntentSender;
+import android.content.IRestrictionsManager;
import android.content.ReceiverCallNotAllowedException;
+import android.content.RestrictionsManager;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
@@ -58,7 +60,9 @@ import android.hardware.ISerialManager;
import android.hardware.SerialManager;
import android.hardware.SystemSensorManager;
import android.hardware.hdmi.HdmiCecManager;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiCecService;
+import android.hardware.hdmi.IHdmiControlService;
import android.hardware.camera2.CameraManager;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
@@ -71,6 +75,8 @@ import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaRouter;
import android.media.session.MediaSessionManager;
+import android.media.tv.ITvInputManager;
+import android.media.tv.TvInputManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.EthernetManager;
@@ -115,8 +121,6 @@ import android.service.fingerprint.FingerprintManager;
import android.service.fingerprint.FingerprintManagerReceiver;
import android.service.fingerprint.FingerprintService;
import android.telephony.TelephonyManager;
-import android.tv.ITvInputManager;
-import android.tv.TvInputManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -249,6 +253,8 @@ class ContextImpl extends Context {
private File[] mExternalFilesDirs;
@GuardedBy("mSync")
private File[] mExternalCacheDirs;
+ @GuardedBy("mSync")
+ private File[] mExternalMediaDirs;
private static final String[] EMPTY_FILE_LIST = {};
@@ -386,6 +392,11 @@ class ContextImpl extends Context {
return new HdmiCecManager(IHdmiCecService.Stub.asInterface(b));
}});
+ registerService(HDMI_CONTROL_SERVICE, new StaticServiceFetcher() {
+ public Object createStaticService() {
+ IBinder b = ServiceManager.getService(HDMI_CONTROL_SERVICE);
+ return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b));
+ }});
registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
@@ -653,6 +664,13 @@ class ContextImpl extends Context {
}
});
+ registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE);
+ IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b);
+ return new RestrictionsManager(ctx, service);
+ }
+ });
registerService(PRINT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
@@ -1032,6 +1050,18 @@ class ContextImpl extends Context {
}
@Override
+ public File[] getExternalMediaDirs() {
+ synchronized (mSync) {
+ if (mExternalMediaDirs == null) {
+ mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+ }
+
+ // Create dirs if needed
+ return ensureDirsExistOrFilter(mExternalMediaDirs);
+ }
+ }
+
+ @Override
public File getFileStreamPath(String name) {
return makeFilename(getFilesDir(), name);
}
@@ -1734,7 +1764,8 @@ class ContextImpl extends Context {
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null, null, getUserId());
+ className, profileFile, 0, arguments, null, null, getUserId(),
+ null /* ABI override */);
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index bc97852..4b052e7 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -18,11 +18,7 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.Matrix;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
@@ -33,12 +29,12 @@ import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroupOverlay;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import java.util.ArrayList;
-import java.util.Collection;
/**
* This ActivityTransitionCoordinator is created by the Activity to manage
@@ -56,6 +52,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
private Handler mHandler;
private boolean mIsCanceled;
private ObjectAnimator mBackgroundAnimator;
+ private boolean mIsExitTransitionComplete;
public EnterTransitionCoordinator(Activity activity, ResultReceiver resultReceiver,
ArrayList<String> sharedElementNames,
@@ -76,6 +73,24 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
}
};
mHandler.sendEmptyMessageDelayed(MSG_CANCEL, MAX_WAIT_MS);
+ send(MSG_SEND_SHARED_ELEMENT_DESTINATION, null);
+ }
+ }
+
+ private void sendSharedElementDestination() {
+ ViewGroup decor = getDecor();
+ if (!decor.isLayoutRequested()) {
+ Bundle state = captureSharedElementState();
+ mResultReceiver.send(MSG_SHARED_ELEMENT_DESTINATION, state);
+ } else {
+ getDecor().getViewTreeObserver()
+ .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
}
}
@@ -98,9 +113,8 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
break;
case MSG_EXIT_TRANSITION_COMPLETE:
if (!mIsCanceled) {
- if (!mSharedElementTransitionStarted) {
- send(resultCode, resultData);
- } else {
+ mIsExitTransitionComplete = true;
+ if (mSharedElementTransitionStarted) {
onRemoteExitTransitionComplete();
}
}
@@ -108,6 +122,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
case MSG_CANCEL:
cancel();
break;
+ case MSG_SEND_SHARED_ELEMENT_DESTINATION:
+ sendSharedElementDestination();
+ break;
}
}
@@ -183,6 +200,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
setViewVisibility(mSharedElements, View.VISIBLE);
ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageViewState =
setSharedElementState(sharedElementState, sharedElementSnapshots);
+ requestLayoutForSharedElements();
boolean startEnterTransition = allowOverlappingTransitions();
boolean startSharedElementTransition = true;
@@ -200,6 +218,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
mResultReceiver = null; // all done sending messages.
}
+ private void requestLayoutForSharedElements() {
+ int numSharedElements = mSharedElements.size();
+ for (int i = 0; i < numSharedElements; i++) {
+ mSharedElements.get(i).requestLayout();
+ }
+ }
+
private Transition beginTransition(boolean startEnterTransition,
boolean startSharedElementTransition) {
Transition sharedElementTransition = null;
@@ -213,6 +238,19 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
}
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
+ if (startSharedElementTransition) {
+ if (transition == null) {
+ sharedElementTransitionStarted();
+ } else {
+ transition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionStart(Transition transition) {
+ transition.removeListener(this);
+ sharedElementTransitionStarted();
+ }
+ });
+ }
+ }
if (transition != null) {
TransitionManager.beginDelayedTransition(getDecor(), transition);
if (startSharedElementTransition && !mSharedElementNames.isEmpty()) {
@@ -224,6 +262,13 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
return transition;
}
+ private void sharedElementTransitionStarted() {
+ mSharedElementTransitionStarted = true;
+ if (mIsExitTransitionComplete) {
+ send(MSG_EXIT_TRANSITION_COMPLETE, null);
+ }
+ }
+
private void startEnterTransition(Transition transition) {
setViewVisibility(mTransitioningViews, View.VISIBLE);
if (!mIsReturning) {
@@ -310,142 +355,4 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator {
startEnterTransition(transition);
}
}
-
- private ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
- int numSharedElements = names.size();
- if (numSharedElements == 0) {
- return null;
- }
- ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
- Context context = getWindow().getContext();
- int[] parentLoc = new int[2];
- getDecor().getLocationOnScreen(parentLoc);
- for (String name: names) {
- Bundle sharedElementBundle = state.getBundle(name);
- if (sharedElementBundle != null) {
- Bitmap bitmap = sharedElementBundle.getParcelable(KEY_BITMAP);
- View snapshot = new View(context);
- Resources resources = getWindow().getContext().getResources();
- snapshot.setBackground(new BitmapDrawable(resources, bitmap));
- snapshot.setViewName(name);
- setSharedElementState(snapshot, name, state, parentLoc);
- snapshots.add(snapshot);
- }
- }
- return snapshots;
- }
-
- private static void setSharedElementState(View view, String name, Bundle transitionArgs,
- int[] parentLoc) {
- Bundle sharedElementBundle = transitionArgs.getBundle(name);
- if (sharedElementBundle == null) {
- return;
- }
-
- if (view instanceof ImageView) {
- int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
- if (scaleTypeInt >= 0) {
- ImageView imageView = (ImageView) view;
- ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
- imageView.setScaleType(scaleType);
- if (scaleType == ImageView.ScaleType.MATRIX) {
- float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
- Matrix matrix = new Matrix();
- matrix.setValues(matrixValues);
- imageView.setImageMatrix(matrix);
- }
- }
- }
-
- float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
- view.setTranslationZ(z);
-
- int x = sharedElementBundle.getInt(KEY_SCREEN_X);
- int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
- int width = sharedElementBundle.getInt(KEY_WIDTH);
- int height = sharedElementBundle.getInt(KEY_HEIGHT);
-
- int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
- int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
- view.measure(widthSpec, heightSpec);
-
- int left = x - parentLoc[0];
- int top = y - parentLoc[1];
- int right = left + width;
- int bottom = top + height;
- view.layout(left, top, right, bottom);
- }
-
- private ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> setSharedElementState(
- Bundle sharedElementState, final ArrayList<View> snapshots) {
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalImageState =
- new ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>>();
- if (sharedElementState != null) {
- int[] tempLoc = new int[2];
- for (int i = 0; i < mSharedElementNames.size(); i++) {
- View sharedElement = mSharedElements.get(i);
- String name = mSharedElementNames.get(i);
- Pair<ImageView.ScaleType, Matrix> originalState = getOldImageState(sharedElement,
- name, sharedElementState);
- if (originalState != null) {
- originalImageState.put((ImageView) sharedElement, originalState);
- }
- View parent = (View) sharedElement.getParent();
- parent.getLocationOnScreen(tempLoc);
- setSharedElementState(sharedElement, name, sharedElementState, tempLoc);
- sharedElement.requestLayout();
- }
- }
- mListener.setSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
-
- getDecor().getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
- mListener.setSharedElementEnd(mSharedElementNames, mSharedElements,
- snapshots);
- mSharedElementTransitionStarted = true;
- return true;
- }
- }
- );
- return originalImageState;
- }
-
- private static Pair<ImageView.ScaleType, Matrix> getOldImageState(View view, String name,
- Bundle transitionArgs) {
- if (!(view instanceof ImageView)) {
- return null;
- }
- Bundle bundle = transitionArgs.getBundle(name);
- if (bundle == null) {
- return null;
- }
- int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
- if (scaleTypeInt < 0) {
- return null;
- }
-
- ImageView imageView = (ImageView) view;
- ImageView.ScaleType originalScaleType = imageView.getScaleType();
-
- Matrix originalMatrix = null;
- if (originalScaleType == ImageView.ScaleType.MATRIX) {
- originalMatrix = new Matrix(imageView.getImageMatrix());
- }
-
- return Pair.create(originalScaleType, originalMatrix);
- }
-
- private static void setOriginalImageViewState(
- ArrayMap<ImageView, Pair<ImageView.ScaleType, Matrix>> originalState) {
- for (int i = 0; i < originalState.size(); i++) {
- ImageView imageView = originalState.keyAt(i);
- Pair<ImageView.ScaleType, Matrix> state = originalState.valueAt(i);
- imageView.setScaleType(state.first);
- imageView.setImageMatrix(state.second);
- }
- }
-
}
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 93eb53e..ba1638f 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -19,8 +19,6 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -29,7 +27,7 @@ import android.os.Message;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.view.View;
-import android.widget.ImageView;
+import android.view.ViewTreeObserver;
import java.util.ArrayList;
@@ -62,6 +60,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
private boolean mIsHidden;
+ private boolean mExitTransitionStarted;
+
+ private Bundle mExitSharedElementBundle;
+
public ExitTransitionCoordinator(Activity activity, ArrayList<String> names,
ArrayList<String> accepted, ArrayList<String> mapped, boolean isReturning) {
super(activity.getWindow(), names, accepted, mapped, getListener(activity, isReturning),
@@ -102,15 +104,32 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
setViewVisibility(mSharedElements, View.VISIBLE);
mIsHidden = true;
break;
+ case MSG_SHARED_ELEMENT_DESTINATION:
+ mExitSharedElementBundle = resultData;
+ if (mExitTransitionStarted) {
+ startSharedElementExit();
+ }
+ break;
+ }
+ }
+
+ private void startSharedElementExit() {
+ if (!mSharedElements.isEmpty() && getSharedElementTransition() != null) {
+ Transition transition = getSharedElementExitTransition();
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ ArrayList<View> sharedElementSnapshots = createSnapshots(mExitSharedElementBundle,
+ mSharedElementNames);
+ setSharedElementState(mExitSharedElementBundle, sharedElementSnapshots);
}
}
private void hideSharedElements() {
setViewVisibility(mSharedElements, View.INVISIBLE);
+ finishIfNecessary();
}
public void startExit() {
- beginTransition();
+ beginTransitions();
setViewVisibility(mTransitioningViews, View.INVISIBLE);
}
@@ -140,7 +159,30 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
}
}
}, options);
- startExit();
+ Transition sharedElementTransition = mSharedElements.isEmpty()
+ ? null : getSharedElementTransition();
+ if (sharedElementTransition == null) {
+ sharedElementTransitionComplete();
+ }
+ Transition transition = mergeTransitions(sharedElementTransition, getExitTransition());
+ if (transition == null) {
+ mExitTransitionStarted = true;
+ } else {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
+ setViewVisibility(mTransitioningViews, View.INVISIBLE);
+ getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ getDecor().getViewTreeObserver().removeOnPreDrawListener(this);
+ mExitTransitionStarted = true;
+ if (mExitSharedElementBundle != null) {
+ startSharedElementExit();
+ }
+ notifyComplete();
+ return true;
+ }
+ });
+ }
}
private void fadeOutBackground() {
@@ -162,40 +204,62 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
}
}
- private void beginTransition() {
- Transition sharedElementTransition = configureTransition(getSharedElementTransition());
- Transition viewsTransition = configureTransition(getViewsTransition());
- viewsTransition = addTargets(viewsTransition, mTransitioningViews);
- if (sharedElementTransition == null || mSharedElements.isEmpty()) {
- sharedElementTransitionComplete();
- sharedElementTransition = null;
+ private Transition getExitTransition() {
+ Transition viewsTransition = null;
+ if (!mTransitioningViews.isEmpty()) {
+ viewsTransition = configureTransition(getViewsTransition());
+ }
+ if (viewsTransition == null) {
+ exitTransitionComplete();
} else {
- sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
+ viewsTransition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
- sharedElementTransitionComplete();
+ exitTransitionComplete();
+ if (mIsHidden) {
+ setViewVisibility(mTransitioningViews, View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ super.onTransitionCancel(transition);
}
});
}
- if (viewsTransition == null || mTransitioningViews.isEmpty()) {
- exitTransitionComplete();
- viewsTransition = null;
+ return viewsTransition;
+ }
+
+ private Transition getSharedElementExitTransition() {
+ Transition sharedElementTransition = null;
+ if (!mSharedElements.isEmpty()) {
+ sharedElementTransition = configureTransition(getSharedElementTransition());
+ }
+ if (sharedElementTransition == null) {
+ sharedElementTransitionComplete();
} else {
- viewsTransition.addListener(new Transition.TransitionListenerAdapter() {
+ sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
- exitTransitionComplete();
+ sharedElementTransitionComplete();
if (mIsHidden) {
- setViewVisibility(mTransitioningViews, View.VISIBLE);
+ setViewVisibility(mSharedElements, View.VISIBLE);
}
}
});
+ mSharedElements.get(0).invalidate();
}
+ return sharedElementTransition;
+ }
+
+ private void beginTransitions() {
+ Transition sharedElementTransition = getSharedElementExitTransition();
+ Transition viewsTransition = getExitTransition();
Transition transition = mergeTransitions(sharedElementTransition, viewsTransition);
- TransitionManager.beginDelayedTransition(getDecor(), transition);
- if (viewsTransition == null && sharedElementTransition != null) {
- mSharedElements.get(0).requestLayout();
+ mExitTransitionStarted = true;
+ if (transition != null) {
+ TransitionManager.beginDelayedTransition(getDecor(), transition);
}
}
@@ -205,18 +269,12 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
}
protected boolean isReadyToNotify() {
- return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady;
+ return mSharedElementBundle != null && mResultReceiver != null && mIsBackgroundReady
+ && mExitTransitionStarted;
}
private void sharedElementTransitionComplete() {
- Bundle bundle = new Bundle();
- int[] tempLoc = new int[2];
- for (int i = 0; i < mSharedElementNames.size(); i++) {
- View sharedElement = mSharedElements.get(i);
- String name = mSharedElementNames.get(i);
- captureSharedElementState(sharedElement, name, bundle, tempLoc);
- }
- mSharedElementBundle = bundle;
+ mSharedElementBundle = captureSharedElementState();
notifyComplete();
}
@@ -230,15 +288,23 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
mExitNotified = true;
mResultReceiver.send(MSG_EXIT_TRANSITION_COMPLETE, null);
mResultReceiver = null; // done talking
- if (mIsReturning) {
- mActivity.finish();
- mActivity.overridePendingTransition(0, 0);
- }
- mActivity = null;
+ finishIfNecessary();
}
}
}
+ private void finishIfNecessary() {
+ if (mIsReturning && mExitNotified && mActivity != null && (mSharedElements.isEmpty()
+ || mSharedElements.get(0).getVisibility() == View.INVISIBLE)) {
+ mActivity.finish();
+ mActivity.overridePendingTransition(0, 0);
+ mActivity = null;
+ }
+ if (!mIsReturning && mExitNotified) {
+ mActivity = null; // don't need it anymore
+ }
+ }
+
@Override
protected Transition getViewsTransition() {
if (mIsReturning) {
@@ -255,58 +321,4 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator {
return getWindow().getSharedElementExitTransition();
}
}
-
- /**
- * Captures placement information for Views with a shared element name for
- * Activity Transitions.
- *
- * @param view The View to capture the placement information for.
- * @param name The shared element name in the target Activity to apply the placement
- * information for.
- * @param transitionArgs Bundle to store shared element placement information.
- * @param tempLoc A temporary int[2] for capturing the current location of views.
- */
- private static void captureSharedElementState(View view, String name, Bundle transitionArgs,
- int[] tempLoc) {
- Bundle sharedElementBundle = new Bundle();
- view.getLocationOnScreen(tempLoc);
- float scaleX = view.getScaleX();
- sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]);
- int width = Math.round(view.getWidth() * scaleX);
- sharedElementBundle.putInt(KEY_WIDTH, width);
-
- float scaleY = view.getScaleY();
- sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]);
- int height = Math.round(view.getHeight() * scaleY);
- sharedElementBundle.putInt(KEY_HEIGHT, height);
-
- sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
-
- Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
- view.draw(canvas);
- sharedElementBundle.putParcelable(KEY_BITMAP, bitmap);
-
- if (view instanceof ImageView) {
- ImageView imageView = (ImageView) view;
- int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
- sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
- if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
- float[] matrix = new float[9];
- imageView.getImageMatrix().getValues(matrix);
- sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
- }
- }
-
- transitionArgs.putBundle(name, sharedElementBundle);
- }
-
- private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
- for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
- if (scaleType == SCALE_TYPE_VALUES[i]) {
- return i;
- }
- }
- return -1;
- }
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8434c2a..bf2d7e5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -176,7 +176,8 @@ public interface IActivityManager extends IInterface {
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher,
- IUiAutomationConnection connection, int userId) throws RemoteException;
+ IUiAutomationConnection connection, int userId,
+ String abiOverride) throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 6e23b11..8dba1dc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -989,10 +989,9 @@ public class Notification implements Parcelable
* <pre class="prettyprint">
* Notification.Action action = new Notification.Action.Builder(
* R.drawable.archive_all, "Archive all", actionIntent)
- * .apply(new Notification.Action.WearableExtender()
+ * .extend(new Notification.Action.WearableExtender()
* .setAvailableOffline(false))
- * .build();
- * </pre>
+ * .build();</pre>
*/
public static final class WearableExtender implements Extender {
/** Notification action extra which contains wearable extensions */
@@ -1672,7 +1671,6 @@ public class Notification implements Parcelable
private Notification mPublicVersion = null;
private final NotificationColorUtil mColorUtil;
private ArrayList<String> mPeople;
- private boolean mPreQuantum;
private int mColor = COLOR_DEFAULT;
/**
@@ -1695,6 +1693,15 @@ public class Notification implements Parcelable
* object.
*/
public Builder(Context context) {
+ /*
+ * Important compatibility note!
+ * Some apps out in the wild create a Notification.Builder in their Activity subclass
+ * constructor for later use. At this point Activities - themselves subclasses of
+ * ContextWrapper - do not have their inner Context populated yet. This means that
+ * any calls to Context methods from within this constructor can cause NPEs in existing
+ * apps. Any data populated from mContext should therefore be populated lazily to
+ * preserve compatibility.
+ */
mContext = context;
// Set defaults to match the defaults of a Notification
@@ -1703,7 +1710,6 @@ public class Notification implements Parcelable
mPriority = PRIORITY_DEFAULT;
mPeople = new ArrayList<String>();
- mPreQuantum = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L;
mColorUtil = NotificationColorUtil.getInstance();
}
@@ -3307,8 +3313,7 @@ public class Notification implements Parcelable
* <pre class="prettyprint">
* Notification.WearableExtender wearableExtender = new Notification.WearableExtender(
* notification);
- * List&lt;Notification&gt; pages = wearableExtender.getPages();
- * </pre>
+ * List&lt;Notification&gt; pages = wearableExtender.getPages();</pre>
*/
public static final class WearableExtender implements Extender {
/**
@@ -3357,6 +3362,14 @@ public class Notification implements Parcelable
*/
public static final int SIZE_LARGE = 4;
+ /**
+ * Size value for use with {@link #setCustomSizePreset} to show this notification
+ * full screen.
+ * <p>This value is only applicable for custom display notifications created using
+ * {@link #setDisplayIntent}.
+ */
+ public static final int SIZE_FULL_SCREEN = 5;
+
/** Notification extra which contains wearable extensions */
private static final String EXTRA_WEARABLE_EXTENSIONS = "android.wearable.EXTENSIONS";
@@ -3553,7 +3566,27 @@ public class Notification implements Parcelable
/**
* Set an intent to launch inside of an activity view when displaying
- * this notification. This {@link PendingIntent} should be for an activity.
+ * this notification. The {@link PendingIntent} provided should be for an activity.
+ *
+ * <pre class="prettyprint">
+ * Intent displayIntent = new Intent(context, MyDisplayActivity.class);
+ * PendingIntent displayPendingIntent = PendingIntent.getActivity(context,
+ * 0, displayIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ * Notification notif = new Notification.Builder(context)
+ * .extend(new Notification.WearableExtender()
+ * .setDisplayIntent(displayPendingIntent)
+ * .setCustomSizePreset(Notification.WearableExtender.SIZE_MEDIUM))
+ * .build();</pre>
+ *
+ * <p>The activity to launch needs to allow embedding, must be exported, and
+ * should have an empty task affinity.
+ *
+ * <p>Example AndroidManifest.xml entry:
+ * <pre class="prettyprint">
+ * &lt;activity android:name=&quot;com.example.MyDisplayActivity&quot;
+ * android:exported=&quot;true&quot;
+ * android:allowEmbedded=&quot;true&quot;
+ * android:taskAffinity=&quot;&quot; /&gt;</pre>
*
* @param intent the {@link PendingIntent} for an activity
* @return this object for method chaining
@@ -3687,12 +3720,17 @@ public class Notification implements Parcelable
/**
* Set an action from this notification's actions to be clickable with the content of
- * this notification page. This action will no longer display separately from the
- * notification content. This action's icon will display with optional subtext provided
- * by the action's title.
- * @param actionIndex The index of the action to hoist on the current notification page.
- * If wearable actions are present, this index will apply to that list,
- * otherwise it will apply to the main notification's actions list.
+ * this notification. This action will no longer display separately from the
+ * notification's content.
+ *
+ * <p>For notifications with multiple pages, child pages can also have content actions
+ * set, although the list of available actions comes from the main notification and not
+ * from the child page's notification.
+ *
+ * @param actionIndex The index of the action to hoist onto the current notification page.
+ * If wearable actions were added to the main notification, this index
+ * will apply to that list, otherwise it will apply to the regular
+ * actions list.
*/
public WearableExtender setContentAction(int actionIndex) {
mContentActionIndex = actionIndex;
@@ -3700,14 +3738,18 @@ public class Notification implements Parcelable
}
/**
- * Get the action index of an action from this notification to show as clickable with
- * the content of this notification page. When the user clicks this notification page,
- * this action will trigger. This action will no longer display separately from the
- * notification content. The action's icon will display with optional subtext provided
- * by the action's title.
+ * Get the index of the notification action, if any, that was specified as being clickable
+ * with the content of this notification. This action will no longer display separately
+ * from the notification's content.
+ *
+ * <p>For notifications with multiple pages, child pages can also have content actions
+ * set, although the list of available actions comes from the main notification and not
+ * from the child page's notification.
+ *
+ * <p>If wearable specific actions were added to the main notification, this index will
+ * apply to that list, otherwise it will apply to the regular actions list.
*
- * <p>If wearable specific actions are present, this index will apply to that list,
- * otherwise it will apply to the main notification's actions list.
+ * @return the action index or {@link #UNSET_ACTION_INDEX} if no action was selected.
*/
public int getContentAction() {
return mContentActionIndex;
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 6dc48b0..85e970c 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -30,18 +30,39 @@ import com.android.internal.app.IVoiceInteractorRequest;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
-import java.util.WeakHashMap;
+import java.util.ArrayList;
/**
- * Interface for an {@link Activity} to interact with the user through voice.
+ * Interface for an {@link Activity} to interact with the user through voice. Use
+ * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor}
+ * to retrieve the interface, if the activity is currently involved in a voice interaction.
+ *
+ * <p>The voice interactor revolves around submitting voice interaction requests to the
+ * back-end voice interaction service that is working with the user. These requests are
+ * submitted with {@link #submitRequest}, providing a new instance of a
+ * {@link Request} subclass describing the type of operation to perform -- currently the
+ * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}.
+ *
+ * <p>Once a request is submitted, the voice system will process it and eventually deliver
+ * the result to the request object. The application can cancel a pending request at any
+ * time.
+ *
+ * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that
+ * if an activity is being restarted with retained state, it will retain the current
+ * VoiceInteractor and any outstanding requests. Because of this, you should always use
+ * {@link Request#getActivity() Request.getActivity} to get back to the activity of a
+ * request, rather than holding on to the activity instance yourself, either explicitly
+ * or implicitly through a non-static inner class.
*/
public class VoiceInteractor {
static final String TAG = "VoiceInteractor";
static final boolean DEBUG = true;
- final Context mContext;
- final Activity mActivity;
final IVoiceInteractor mInteractor;
+
+ Context mContext;
+ Activity mActivity;
+
final HandlerCaller mHandlerCaller;
final HandlerCaller.Callback mHandlerCallerCallback = new HandlerCaller.Callback() {
@Override
@@ -60,6 +81,16 @@ public class VoiceInteractor {
request.clear();
}
break;
+ case MSG_ABORT_VOICE_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onAbortVoice: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " result=" + args.arg1);
+ if (request != null) {
+ ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2);
+ request.clear();
+ }
+ break;
case MSG_COMMAND_RESULT:
request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
if (DEBUG) Log.d(TAG, "onCommandResult: req="
@@ -94,6 +125,12 @@ public class VoiceInteractor {
}
@Override
+ public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
+ MSG_ABORT_VOICE_RESULT, request, result));
+ }
+
+ @Override
public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
Bundle result) {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
@@ -110,8 +147,9 @@ public class VoiceInteractor {
final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
static final int MSG_CONFIRMATION_RESULT = 1;
- static final int MSG_COMMAND_RESULT = 2;
- static final int MSG_CANCEL_RESULT = 3;
+ static final int MSG_ABORT_VOICE_RESULT = 2;
+ static final int MSG_COMMAND_RESULT = 3;
+ static final int MSG_CANCEL_RESULT = 4;
public static abstract class Request {
IVoiceInteractorRequest mRequestInterface;
@@ -140,6 +178,12 @@ public class VoiceInteractor {
public void onCancel() {
}
+ public void onAttached(Activity activity) {
+ }
+
+ public void onDetached() {
+ }
+
void clear() {
mRequestInterface = null;
mContext = null;
@@ -180,9 +224,42 @@ public class VoiceInteractor {
IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
IVoiceInteractorCallback callback) throws RemoteException {
- return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
+ return interactor.startConfirmation(packageName, callback, mPrompt, mExtras);
+ }
+ }
+
+ public static class AbortVoiceRequest extends Request {
+ final CharSequence mMessage;
+ final Bundle mExtras;
+
+ /**
+ * Reports that the current interaction can not be complete with voice, so the
+ * application will need to switch to a traditional input UI. Applications should
+ * only use this when they need to completely bail out of the voice interaction
+ * and switch to a traditional UI. When the response comes back, the voice
+ * system has handled the request and is ready to switch; at that point the application
+ * can start a new non-voice activity. Be sure when starting the new activity
+ * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+ * interaction task.
+ *
+ * @param message Optional message to tell user about not being able to complete
+ * the interaction with voice.
+ * @param extras Additional optional information.
+ */
+ public AbortVoiceRequest(CharSequence message, Bundle extras) {
+ mMessage = message;
+ mExtras = extras;
}
- }
+
+ public void onAbortResult(Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startAbortVoice(packageName, callback, mMessage, mExtras);
+ }
+ }
public static class CommandRequest extends Request {
final String mCommand;
@@ -220,11 +297,11 @@ public class VoiceInteractor {
}
}
- VoiceInteractor(Context context, Activity activity, IVoiceInteractor interactor,
+ VoiceInteractor(IVoiceInteractor interactor, Context context, Activity activity,
Looper looper) {
+ mInteractor = interactor;
mContext = context;
mActivity = activity;
- mInteractor = interactor;
mHandlerCaller = new HandlerCaller(context, looper, mHandlerCallerCallback, true);
}
@@ -238,6 +315,49 @@ public class VoiceInteractor {
}
}
+ private ArrayList<Request> makeRequestList() {
+ final int N = mActiveRequests.size();
+ if (N < 1) {
+ return null;
+ }
+ ArrayList<Request> list = new ArrayList<Request>(N);
+ for (int i=0; i<N; i++) {
+ list.add(mActiveRequests.valueAt(i));
+ }
+ return list;
+ }
+
+ void attachActivity(Activity activity) {
+ if (mActivity == activity) {
+ return;
+ }
+ mContext = activity;
+ mActivity = activity;
+ ArrayList<Request> reqs = makeRequestList();
+ if (reqs != null) {
+ for (int i=0; i<reqs.size(); i++) {
+ Request req = reqs.get(i);
+ req.mContext = activity;
+ req.mActivity = activity;
+ req.onAttached(activity);
+ }
+ }
+ }
+
+ void detachActivity() {
+ ArrayList<Request> reqs = makeRequestList();
+ if (reqs != null) {
+ for (int i=0; i<reqs.size(); i++) {
+ Request req = reqs.get(i);
+ req.onDetached();
+ req.mActivity = null;
+ req.mContext = null;
+ }
+ }
+ mContext = null;
+ mActivity = null;
+ }
+
public boolean submitRequest(Request request) {
try {
IVoiceInteractorRequest ireq = request.submit(mInteractor,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3ef16a9..24a354f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -25,6 +25,7 @@ import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.RestrictionsManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
@@ -103,6 +104,17 @@ public class DevicePolicyManager {
= "android.app.action.ACTION_PROVISION_MANAGED_PROFILE";
/**
+ * A broadcast intent with this action can be sent to ManagedProvisionning to specify that the
+ * user has already consented to the creation of the managed profile.
+ * The intent must contain the extras
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and
+ * {@link #EXTRA_PROVISIONING_TOKEN}
+ * @hide
+ */
+ public static final String ACTION_PROVISIONING_USER_HAS_CONSENTED
+ = "android.app.action.USER_HAS_CONSENTED";
+
+ /**
* A String extra holding the name of the package of the mobile device management application
* that starts the managed provisioning flow. This package will be set as the profile owner.
* <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}.
@@ -111,6 +123,13 @@ public class DevicePolicyManager {
= "deviceAdminPackageName";
/**
+ * An int extra used to identify the consent of the user to create the managed profile.
+ * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
+ */
+ public static final String EXTRA_PROVISIONING_TOKEN
+ = "android.app.extra.token";
+
+ /**
* A String extra holding the default name of the profile that is created during managed profile
* provisioning.
* <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
@@ -2281,4 +2300,23 @@ public class DevicePolicyManager {
}
}
+ /**
+ * Designates a specific broadcast receiver component as the provider for
+ * making permission requests of a local or remote administrator of the user.
+ * <p/>
+ * Only a profile owner can designate the restrictions provider.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param receiver The component name of a BroadcastReceiver that handles the
+ * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null,
+ * it removes the restrictions provider previously assigned.
+ */
+ public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) {
+ if (mService != null) {
+ try {
+ mService.setRestrictionsProvider(admin, receiver);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set permission provider on device policy service");
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 4639195..7d7a312 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -121,6 +121,9 @@ interface IDevicePolicyManager {
void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+ void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
+ ComponentName getRestrictionsProvider(int userHandle);
+
void setUserRestriction(in ComponentName who, in String key, boolean enable);
void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
void clearCrossProfileIntentFilters(in ComponentName admin);
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
new file mode 100644
index 0000000..46f082e
--- /dev/null
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2014 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.backup;
+
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Concrete class that provides a stable-API bridge between IBackupTransport
+ * and its implementations.
+ *
+ * @hide
+ */
+public class BackupTransport {
+ public static final int TRANSPORT_OK = 0;
+ public static final int TRANSPORT_ERROR = 1;
+ public static final int TRANSPORT_NOT_INITIALIZED = 2;
+ public static final int TRANSPORT_PACKAGE_REJECTED = 3;
+ public static final int AGENT_ERROR = 4;
+ public static final int AGENT_UNKNOWN = 5;
+
+ IBackupTransport mBinderImpl = new TransportImpl();
+ /** @hide */
+ public IBinder getBinder() {
+ return mBinderImpl.asBinder();
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Transport self-description and general configuration interfaces
+ //
+
+ /**
+ * Ask the transport for the name under which it should be registered. This will
+ * typically be its host service's component name, but need not be.
+ */
+ public String name() {
+ throw new UnsupportedOperationException("Transport name() not implemented");
+ }
+
+ /**
+ * Ask the transport for an Intent that can be used to launch any internal
+ * configuration Activity that it wishes to present. For example, the transport
+ * may offer a UI for allowing the user to supply login credentials for the
+ * transport's off-device backend.
+ *
+ * If the transport does not supply any user-facing configuration UI, it should
+ * return null from this method.
+ *
+ * @return An Intent that can be passed to Context.startActivity() in order to
+ * launch the transport's configuration UI. This method will return null
+ * if the transport does not offer any user-facing configuration UI.
+ */
+ public Intent configurationIntent() {
+ return null;
+ }
+
+ /**
+ * On demand, supply a one-line string that can be shown to the user that
+ * describes the current backend destination. For example, a transport that
+ * can potentially associate backup data with arbitrary user accounts should
+ * include the name of the currently-active account here.
+ *
+ * @return A string describing the destination to which the transport is currently
+ * sending data. This method should not return null.
+ */
+ public String currentDestinationString() {
+ throw new UnsupportedOperationException(
+ "Transport currentDestinationString() not implemented");
+ }
+
+ /**
+ * Ask the transport where, on local device storage, to keep backup state blobs.
+ * This is per-transport so that mock transports used for testing can coexist with
+ * "live" backup services without interfering with the live bookkeeping. The
+ * returned string should be a name that is expected to be unambiguous among all
+ * available backup transports; the name of the class implementing the transport
+ * is a good choice.
+ *
+ * @return A unique name, suitable for use as a file or directory name, that the
+ * Backup Manager could use to disambiguate state files associated with
+ * different backup transports.
+ */
+ public String transportDirName() {
+ throw new UnsupportedOperationException(
+ "Transport transportDirName() not implemented");
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Device-level operations common to both key/value and full-data storage
+
+ /**
+ * Initialize the server side storage for this device, erasing all stored data.
+ * The transport may send the request immediately, or may buffer it. After
+ * this is called, {@link #finishBackup} will be called to ensure the request
+ * is sent and received successfully.
+ *
+ * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far) or
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure).
+ */
+ public int initializeDevice() {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Erase the given application's data from the backup destination. This clears
+ * out the given package's data from the current backup set, making it as though
+ * the app had never yet been backed up. After this is called, {@link finishBackup}
+ * must be called to ensure that the operation is recorded successfully.
+ *
+ * @return the same error codes as {@link #performBackup}.
+ */
+ public int clearBackupData(PackageInfo packageInfo) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Finish sending application data to the backup destination. This must be
+ * called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData}
+ * to ensure that all data is sent and the operation properly finalized. Only when this
+ * method returns true can a backup be assumed to have succeeded.
+ *
+ * @return the same error codes as {@link #performBackup} or {@link #performFullBackup}.
+ */
+ public int finishBackup() {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key/value incremental backup support interfaces
+
+ /**
+ * Verify that this is a suitable time for a key/value backup pass. This should return zero
+ * if a backup is reasonable right now, some positive value otherwise. This method
+ * will be called outside of the {@link #performBackup}/{@link #finishBackup} pair.
+ *
+ * <p>If this is not a suitable time for a backup, the transport should return a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ *
+ * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+ * in milliseconds to suggest deferring the backup pass for a while.
+ */
+ public long requestBackupTime() {
+ return 0;
+ }
+
+ /**
+ * Send one application's key/value data update to the backup destination. The
+ * transport may send the data immediately, or may buffer it. After this is called,
+ * {@link #finishBackup} will be called to ensure the data is sent and recorded successfully.
+ *
+ * @param packageInfo The identity of the application whose data is being backed up.
+ * This specifically includes the signature list for the package.
+ * @param data The data stream that resulted from invoking the application's
+ * BackupService.doBackup() method. This may be a pipe rather than a file on
+ * persistent media, so it may not be seekable.
+ * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
+ * must be erased prior to the storage of the data provided here. The purpose of this
+ * is to provide a guarantee that no stale data exists in the restore set when the
+ * device begins providing incremental backups.
+ * @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
+ * {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
+ * become lost due to inactivity purge or some other reason and needs re-initializing)
+ */
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key/value dataset restore interfaces
+
+ /**
+ * Get the set of all backups currently available over this transport.
+ *
+ * @return Descriptions of the set of restore images available for this device,
+ * or null if an error occurred (the attempt should be rescheduled).
+ **/
+ public RestoreSet[] getAvailableRestoreSets() {
+ return null;
+ }
+
+ /**
+ * Get the identifying token of the backup set currently being stored from
+ * this device. This is used in the case of applications wishing to restore
+ * their last-known-good data.
+ *
+ * @return A token that can be passed to {@link #startRestore}, or 0 if there
+ * is no backup set available corresponding to the current device state.
+ */
+ public long getCurrentRestoreSet() {
+ return 0;
+ }
+
+ /**
+ * Start restoring application data from backup. After calling this function,
+ * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
+ * to walk through the actual application data.
+ *
+ * @param token A backup token as returned by {@link #getAvailableRestoreSets}
+ * or {@link #getCurrentRestoreSet}.
+ * @param packages List of applications to restore (if data is available).
+ * Application data will be restored in the order given.
+ * @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far, call
+ * {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR}
+ * (an error occurred, the restore should be aborted and rescheduled).
+ */
+ public int startRestore(long token, PackageInfo[] packages) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Get the package name of the next application with data in the backup store.
+ *
+ * @return The name of one of the packages supplied to {@link #startRestore},
+ * or "" (the empty string) if no more backup data is available,
+ * or null if an error occurred (the restore should be aborted and rescheduled).
+ */
+ public String nextRestorePackage() {
+ return null;
+ }
+
+ /**
+ * Get the data for the application returned by {@link #nextRestorePackage}.
+ * @param data An open, writable file into which the backup data should be stored.
+ * @return the same error codes as {@link #startRestore}.
+ */
+ public int getRestoreData(ParcelFileDescriptor outFd) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * End a restore session (aborting any in-process data transfer as necessary),
+ * freeing any resources and connections used during the restore process.
+ */
+ public void finishRestore() {
+ throw new UnsupportedOperationException(
+ "Transport finishRestore() not implemented");
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Full backup interfaces
+
+ /**
+ * Verify that this is a suitable time for a full-data backup pass. This should return zero
+ * if a backup is reasonable right now, some positive value otherwise. This method
+ * will be called outside of the {@link #performFullBackup}/{@link #finishBackup} pair.
+ *
+ * <p>If this is not a suitable time for a backup, the transport should return a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ *
+ * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+ * in milliseconds to suggest deferring the backup pass for a while.
+ *
+ * @see #requestBackupTime()
+ */
+ public long requestFullBackupTime() {
+ return 0;
+ }
+
+ /**
+ * Begin the process of sending an application's full-data archive to the backend.
+ * The description of the package whose data will be delivered is provided, as well as
+ * the socket file descriptor on which the transport will receive the data itself.
+ *
+ * <p>If the package is not eligible for backup, the transport should return
+ * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}. In this case the system will
+ * simply proceed with the next candidate if any, or finish the full backup operation
+ * if all apps have been processed.
+ *
+ * <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this
+ * method, the OS will proceed to call {@link #sendBackupData()} one or more times
+ * to deliver the application's data as a streamed tarball. The transport should not
+ * read() from the socket except as instructed to via the {@link #sendBackupData(int)}
+ * method.
+ *
+ * <p>After all data has been delivered to the transport, the system will call
+ * {@link #finishBackup()}. At this point the transport should commit the data to
+ * its datastore, if appropriate, and close the socket that had been provided in
+ * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
+ *
+ * @param targetPackage The package whose data is to follow.
+ * @param socket The socket file descriptor through which the data will be provided.
+ * If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still
+ * close this file descriptor now; otherwise it should be cached for use during
+ * succeeding calls to {@link #sendBackupData(int)}, and closed in response to
+ * {@link #finishBackup()}.
+ * @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not
+ * to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering
+ * backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes
+ * performing a backup at this time.
+ */
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
+ return BackupTransport.TRANSPORT_PACKAGE_REJECTED;
+ }
+
+ /**
+ * Tells the transport to read {@code numBytes} bytes of data from the socket file
+ * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}
+ * call, and deliver those bytes to the datastore.
+ *
+ * @param numBytes The number of bytes of tarball data available to be read from the
+ * socket.
+ * @return TRANSPORT_OK on successful processing of the data; TRANSPORT_ERROR to
+ * indicate a fatal error situation. If an error is returned, the system will
+ * call finishBackup() and stop attempting backups until after a backoff and retry
+ * interval.
+ */
+ public int sendBackupData(int numBytes) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Bridge between the actual IBackupTransport implementation and the stable API. If the
+ * binder interface needs to change, we use this layer to translate so that we can
+ * (if appropriate) decouple those framework-side changes from the BackupTransport
+ * implementations.
+ */
+ class TransportImpl extends IBackupTransport.Stub {
+
+ @Override
+ public String name() throws RemoteException {
+ return BackupTransport.this.name();
+ }
+
+ @Override
+ public Intent configurationIntent() throws RemoteException {
+ return BackupTransport.this.configurationIntent();
+ }
+
+ @Override
+ public String currentDestinationString() throws RemoteException {
+ return BackupTransport.this.currentDestinationString();
+ }
+
+ @Override
+ public String transportDirName() throws RemoteException {
+ return BackupTransport.this.transportDirName();
+ }
+
+ @Override
+ public long requestBackupTime() throws RemoteException {
+ return BackupTransport.this.requestBackupTime();
+ }
+
+ @Override
+ public int initializeDevice() throws RemoteException {
+ return BackupTransport.this.initializeDevice();
+ }
+
+ @Override
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd)
+ throws RemoteException {
+ return BackupTransport.this.performBackup(packageInfo, inFd);
+ }
+
+ @Override
+ public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
+ return BackupTransport.this.clearBackupData(packageInfo);
+ }
+
+ @Override
+ public int finishBackup() throws RemoteException {
+ return BackupTransport.this.finishBackup();
+ }
+
+ @Override
+ public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+ return BackupTransport.this.getAvailableRestoreSets();
+ }
+
+ @Override
+ public long getCurrentRestoreSet() throws RemoteException {
+ return BackupTransport.this.getCurrentRestoreSet();
+ }
+
+ @Override
+ public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
+ return BackupTransport.this.startRestore(token, packages);
+ }
+
+ @Override
+ public String nextRestorePackage() throws RemoteException {
+ return BackupTransport.this.nextRestorePackage();
+ }
+
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+ return BackupTransport.this.getRestoreData(outFd);
+ }
+
+ @Override
+ public void finishRestore() throws RemoteException {
+ BackupTransport.this.finishRestore();
+ }
+ }
+}
diff --git a/core/java/android/app/task/Task.java b/core/java/android/app/task/Task.java
index dd184a5..ca4aeb2 100644
--- a/core/java/android/app/task/Task.java
+++ b/core/java/android/app/task/Task.java
@@ -27,10 +27,13 @@ import android.os.Parcelable;
* using the {@link Task.Builder}.
*/
public class Task implements Parcelable {
-
public interface NetworkType {
- public final int ANY = 0;
- public final int UNMETERED = 1;
+ /** Default. */
+ public final int NONE = 0;
+ /** This task requires network connectivity. */
+ public final int ANY = 1;
+ /** This task requires network connectivity that is unmetered. */
+ public final int UNMETERED = 2;
}
/**
@@ -48,6 +51,8 @@ public class Task implements Parcelable {
private final ComponentName service;
private final boolean requireCharging;
private final boolean requireDeviceIdle;
+ private final boolean hasEarlyConstraint;
+ private final boolean hasLateConstraint;
private final int networkCapabilities;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
@@ -59,7 +64,7 @@ public class Task implements Parcelable {
/**
* Unique task id associated with this class. This is assigned to your task by the scheduler.
*/
- public int getTaskId() {
+ public int getId() {
return taskId;
}
@@ -146,6 +151,24 @@ public class Task implements Parcelable {
return backoffPolicy;
}
+ /**
+ * User can specify an early constraint of 0L, which is valid, so we keep track of whether the
+ * function was called at all.
+ * @hide
+ */
+ public boolean hasEarlyConstraint() {
+ return hasEarlyConstraint;
+ }
+
+ /**
+ * User can specify a late constraint of 0L, which is valid, so we keep track of whether the
+ * function was called at all.
+ * @hide
+ */
+ public boolean hasLateConstraint() {
+ return hasLateConstraint;
+ }
+
private Task(Parcel in) {
taskId = in.readInt();
extras = in.readBundle();
@@ -159,6 +182,8 @@ public class Task implements Parcelable {
intervalMillis = in.readLong();
initialBackoffMillis = in.readLong();
backoffPolicy = in.readInt();
+ hasEarlyConstraint = in.readInt() == 1;
+ hasLateConstraint = in.readInt() == 1;
}
private Task(Task.Builder b) {
@@ -174,6 +199,8 @@ public class Task implements Parcelable {
intervalMillis = b.mIntervalMillis;
initialBackoffMillis = b.mInitialBackoffMillis;
backoffPolicy = b.mBackoffPolicy;
+ hasEarlyConstraint = b.mHasEarlyConstraint;
+ hasLateConstraint = b.mHasLateConstraint;
}
@Override
@@ -195,6 +222,8 @@ public class Task implements Parcelable {
out.writeLong(intervalMillis);
out.writeLong(initialBackoffMillis);
out.writeInt(backoffPolicy);
+ out.writeInt(hasEarlyConstraint ? 1 : 0);
+ out.writeInt(hasLateConstraint ? 1 : 0);
}
public static final Creator<Task> CREATOR = new Creator<Task>() {
@@ -212,7 +241,7 @@ public class Task implements Parcelable {
/**
* Builder class for constructing {@link Task} objects.
*/
- public final class Builder {
+ public static final class Builder {
private int mTaskId;
private Bundle mExtras;
private ComponentName mTaskService;
@@ -225,6 +254,8 @@ public class Task implements Parcelable {
private long mMaxExecutionDelayMillis;
// Periodic parameters.
private boolean mIsPeriodic;
+ private boolean mHasEarlyConstraint;
+ private boolean mHasLateConstraint;
private long mIntervalMillis;
// Back-off parameters.
private long mInitialBackoffMillis = 5000L;
@@ -307,6 +338,7 @@ public class Task implements Parcelable {
public Builder setPeriodic(long intervalMillis) {
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
+ mHasEarlyConstraint = mHasLateConstraint = true;
return this;
}
@@ -320,6 +352,7 @@ public class Task implements Parcelable {
*/
public Builder setMinimumLatency(long minLatencyMillis) {
mMinLatencyMillis = minLatencyMillis;
+ mHasEarlyConstraint = true;
return this;
}
@@ -332,6 +365,7 @@ public class Task implements Parcelable {
*/
public Builder setOverrideDeadline(long maxExecutionDelayMillis) {
mMaxExecutionDelayMillis = maxExecutionDelayMillis;
+ mHasLateConstraint = true;
return this;
}
@@ -360,31 +394,18 @@ public class Task implements Parcelable {
* @return The task object to hand to the TaskManager. This object is immutable.
*/
public Task build() {
- // Check that extras bundle only contains primitive types.
- try {
- for (String key : extras.keySet()) {
- Object value = extras.get(key);
- if (value == null) continue;
- if (value instanceof Long) continue;
- if (value instanceof Integer) continue;
- if (value instanceof Boolean) continue;
- if (value instanceof Float) continue;
- if (value instanceof Double) continue;
- if (value instanceof String) continue;
- throw new IllegalArgumentException("Unexpected value type: "
- + value.getClass().getName());
- }
- } catch (IllegalArgumentException e) {
- throw e;
- } catch (RuntimeException exc) {
- throw new IllegalArgumentException("error unparcelling Bundle", exc);
+ if (mExtras == null) {
+ mExtras = Bundle.EMPTY;
+ }
+ if (mTaskId < 0) {
+ throw new IllegalArgumentException("Task id must be greater than 0.");
}
// Check that a deadline was not set on a periodic task.
- if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
+ if (mIsPeriodic && mHasLateConstraint) {
throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
"periodic task.");
}
- if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
+ if (mIsPeriodic && mHasEarlyConstraint) {
throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
"periodic task");
}
diff --git a/core/java/android/app/task/TaskManager.java b/core/java/android/app/task/TaskManager.java
index 0fbe37d..00f57da 100644
--- a/core/java/android/app/task/TaskManager.java
+++ b/core/java/android/app/task/TaskManager.java
@@ -34,14 +34,13 @@ public abstract class TaskManager {
* if the run-time for your task is too short, or perhaps the system can't resolve the
* requisite {@link TaskService} in your package.
*/
- public static final int RESULT_INVALID_PARAMETERS = -1;
-
+ public static final int RESULT_FAILURE = 0;
/**
* Returned from {@link #schedule(Task)} if this application has made too many requests for
* work over too short a time.
*/
// TODO: Determine if this is necessary.
- public static final int RESULT_OVER_QUOTA = -2;
+ public static final int RESULT_SUCCESS = 1;
/**
* @param task The task you wish scheduled. See
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index d3e9089..e5bf7d0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -317,9 +317,9 @@ public class AppWidgetManager {
public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED";
/**
- * Sent to providers after AppWidget state related to the provider has been restored from
- * backup. The intent contains information about how to translate AppWidget ids from the
- * restored data to their new equivalents.
+ * Sent to an {@link AppWidgetProvider} after AppWidget state related to that provider has
+ * been restored from backup. The intent contains information about how to translate AppWidget
+ * ids from the restored data to their new equivalents.
*
* <p>The intent will contain the following extras:
*
@@ -343,7 +343,7 @@ public class AppWidgetManager {
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
- * @see {@link #ACTION_APPWIDGET_HOST_RESTORED} for the corresponding host broadcast
+ * @see #ACTION_APPWIDGET_HOST_RESTORED
*/
public static final String ACTION_APPWIDGET_RESTORED
= "android.appwidget.action.APPWIDGET_RESTORED";
@@ -352,7 +352,7 @@ public class AppWidgetManager {
* Sent to widget hosts after AppWidget state related to the host has been restored from
* backup. The intent contains information about how to translate AppWidget ids from the
* restored data to their new equivalents. If an application maintains multiple separate
- * widget hosts instances, it will receive this broadcast separately for each one.
+ * widget host instances, it will receive this broadcast separately for each one.
*
* <p>The intent will contain the following extras:
*
@@ -380,7 +380,7 @@ public class AppWidgetManager {
* <p class="note">This is a protected intent that can only be sent
* by the system.
*
- * @see {@link #ACTION_APPWIDGET_RESTORED} for the corresponding provider broadcast
+ * @see #ACTION_APPWIDGET_RESTORED
*/
public static final String ACTION_APPWIDGET_HOST_RESTORED
= "android.appwidget.action.APPWIDGET_HOST_RESTORED";
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 9e1c995..42c2aeb 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -18,6 +18,8 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.BluetoothLeScanner;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java b/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java
deleted file mode 100644
index 2fa5e49..0000000
--- a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.java
+++ /dev/null
@@ -1,645 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-import android.annotation.Nullable;
-import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData;
-import android.os.Parcel;
-import android.os.ParcelUuid;
-import android.os.Parcelable;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Represents Bluetooth LE advertise and scan response data. This could be either the advertisement
- * data to be advertised, or the scan record obtained from BLE scans.
- * <p>
- * The exact bluetooth advertising and scan response data fields and types are defined in Bluetooth
- * 4.0 specification, Volume 3, Part C, Section 11 and 18, as well as Supplement to the Bluetooth
- * Core Specification Version 4. Currently the following fields are allowed to be set:
- * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
- * <li>Tx power level which is the transmission power level.
- * <li>Service data which is the data associated with a service.
- * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
- *
- * @see BluetoothLeAdvertiser
- */
-public final class BluetoothLeAdvertiseScanData {
- private static final String TAG = "BluetoothLeAdvertiseScanData";
-
- /**
- * Bluetooth LE Advertising Data type, the data will be placed in AdvData field of advertising
- * packet.
- */
- public static final int ADVERTISING_DATA = 0;
- /**
- * Bluetooth LE scan response data, the data will be placed in ScanRspData field of advertising
- * packet.
- * <p>
- */
- public static final int SCAN_RESPONSE_DATA = 1;
- /**
- * Scan record parsed from Bluetooth LE scans. The content can contain a concatenation of
- * advertising data and scan response data.
- */
- public static final int PARSED_SCAN_RECORD = 2;
-
- /**
- * Base data type which contains the common fields for {@link AdvertisementData} and
- * {@link ScanRecord}.
- */
- public abstract static class AdvertiseBaseData {
-
- private final int mDataType;
-
- @Nullable
- private final List<ParcelUuid> mServiceUuids;
-
- private final int mManufacturerId;
- @Nullable
- private final byte[] mManufacturerSpecificData;
-
- @Nullable
- private final ParcelUuid mServiceDataUuid;
- @Nullable
- private final byte[] mServiceData;
-
- private AdvertiseBaseData(int dataType,
- List<ParcelUuid> serviceUuids,
- ParcelUuid serviceDataUuid, byte[] serviceData,
- int manufacturerId,
- byte[] manufacturerSpecificData) {
- mDataType = dataType;
- mServiceUuids = serviceUuids;
- mManufacturerId = manufacturerId;
- mManufacturerSpecificData = manufacturerSpecificData;
- mServiceDataUuid = serviceDataUuid;
- mServiceData = serviceData;
- }
-
- /**
- * Returns the type of data, indicating whether the data is advertising data, scan response
- * data or scan record.
- */
- public int getDataType() {
- return mDataType;
- }
-
- /**
- * Returns a list of service uuids within the advertisement that are used to identify the
- * bluetooth gatt services.
- */
- public List<ParcelUuid> getServiceUuids() {
- return mServiceUuids;
- }
-
- /**
- * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth
- * SIG.
- */
- public int getManufacturerId() {
- return mManufacturerId;
- }
-
- /**
- * Returns the manufacturer specific data which is the content of manufacturer specific data
- * field. The first 2 bytes of the data contain the company id.
- */
- public byte[] getManufacturerSpecificData() {
- return mManufacturerSpecificData;
- }
-
- /**
- * Returns a 16 bit uuid of the service that the service data is associated with.
- */
- public ParcelUuid getServiceDataUuid() {
- return mServiceDataUuid;
- }
-
- /**
- * Returns service data. The first two bytes should be a 16 bit service uuid associated with
- * the service data.
- */
- public byte[] getServiceData() {
- return mServiceData;
- }
-
- @Override
- public String toString() {
- return "AdvertiseBaseData [mDataType=" + mDataType + ", mServiceUuids=" + mServiceUuids
- + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
- + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
- + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) + "]";
- }
- }
-
- /**
- * Advertisement data packet for Bluetooth LE advertising. This represents the data to be
- * broadcasted in Bluetooth LE advertising.
- * <p>
- * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to
- * be advertised.
- *
- * @see BluetoothLeAdvertiser
- */
- public static final class AdvertisementData extends AdvertiseBaseData implements Parcelable {
-
- private boolean mIncludeTxPowerLevel;
-
- /**
- * Whether the transmission power level will be included in the advertisement packet.
- */
- public boolean getIncludeTxPowerLevel() {
- return mIncludeTxPowerLevel;
- }
-
- /**
- * Returns a {@link Builder} to build {@link AdvertisementData}.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(getDataType());
- List<ParcelUuid> uuids = getServiceUuids();
- if (uuids == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(uuids.size());
- dest.writeList(uuids);
- }
-
- dest.writeInt(getManufacturerId());
- byte[] manufacturerData = getManufacturerSpecificData();
- if (manufacturerData == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(manufacturerData.length);
- dest.writeByteArray(manufacturerData);
- }
-
- ParcelUuid serviceDataUuid = getServiceDataUuid();
- if (serviceDataUuid == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(1);
- dest.writeParcelable(serviceDataUuid, flags);
- byte[] serviceData = getServiceData();
- if (serviceData == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(serviceData.length);
- dest.writeByteArray(serviceData);
- }
- }
- dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
- }
-
- private AdvertisementData(int dataType,
- List<ParcelUuid> serviceUuids,
- ParcelUuid serviceDataUuid, byte[] serviceData,
- int manufacturerId,
- byte[] manufacturerSpecificData, boolean mIncludeTxPowerLevel) {
- super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId,
- manufacturerSpecificData);
- this.mIncludeTxPowerLevel = mIncludeTxPowerLevel;
- }
-
- public static final Parcelable.Creator<AdvertisementData> CREATOR =
- new Creator<AdvertisementData>() {
- @Override
- public AdvertisementData[] newArray(int size) {
- return new AdvertisementData[size];
- }
-
- @Override
- public AdvertisementData createFromParcel(Parcel in) {
- Builder builder = newBuilder();
- int dataType = in.readInt();
- builder.dataType(dataType);
- if (in.readInt() > 0) {
- List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
- in.readList(uuids, ParcelUuid.class.getClassLoader());
- builder.serviceUuids(uuids);
- }
- int manufacturerId = in.readInt();
- int manufacturerDataLength = in.readInt();
- if (manufacturerDataLength > 0) {
- byte[] manufacturerData = new byte[manufacturerDataLength];
- in.readByteArray(manufacturerData);
- builder.manufacturerData(manufacturerId, manufacturerData);
- }
- if (in.readInt() == 1) {
- ParcelUuid serviceDataUuid = in.readParcelable(
- ParcelUuid.class.getClassLoader());
- int serviceDataLength = in.readInt();
- if (serviceDataLength > 0) {
- byte[] serviceData = new byte[serviceDataLength];
- in.readByteArray(serviceData);
- builder.serviceData(serviceDataUuid, serviceData);
- }
- }
- builder.includeTxPowerLevel(in.readByte() == 1);
- return builder.build();
- }
- };
-
- /**
- * Builder for {@link BluetoothLeAdvertiseScanData.AdvertisementData}. Use
- * {@link AdvertisementData#newBuilder()} to get an instance of the Builder.
- */
- public static final class Builder {
- private static final int MAX_ADVERTISING_DATA_BYTES = 31;
- // Each fields need one byte for field length and another byte for field type.
- private static final int OVERHEAD_BYTES_PER_FIELD = 2;
- // Flags field will be set by system.
- private static final int FLAGS_FIELD_BYTES = 3;
-
- private int mDataType;
- @Nullable
- private List<ParcelUuid> mServiceUuids;
- private boolean mIncludeTxPowerLevel;
- private int mManufacturerId;
- @Nullable
- private byte[] mManufacturerSpecificData;
- @Nullable
- private ParcelUuid mServiceDataUuid;
- @Nullable
- private byte[] mServiceData;
-
- /**
- * Set data type.
- *
- * @param dataType Data type, could only be
- * {@link BluetoothLeAdvertiseScanData#ADVERTISING_DATA}
- * @throws IllegalArgumentException If the {@code dataType} is invalid.
- */
- public Builder dataType(int dataType) {
- if (mDataType != ADVERTISING_DATA && mDataType != SCAN_RESPONSE_DATA) {
- throw new IllegalArgumentException("invalid data type - " + dataType);
- }
- mDataType = dataType;
- return this;
- }
-
- /**
- * Set the service uuids. Note the corresponding bluetooth Gatt services need to be
- * already added on the device before start BLE advertising.
- *
- * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or
- * 128-bit uuids.
- * @throws IllegalArgumentException If the {@code serviceUuids} are null.
- */
- public Builder serviceUuids(List<ParcelUuid> serviceUuids) {
- if (serviceUuids == null) {
- throw new IllegalArgumentException("serivceUuids are null");
- }
- mServiceUuids = serviceUuids;
- return this;
- }
-
- /**
- * Add service data to advertisement.
- *
- * @param serviceDataUuid A 16 bit uuid of the service data
- * @param serviceData Service data - the first two bytes of the service data are the
- * service data uuid.
- * @throws IllegalArgumentException If the {@code serviceDataUuid} or
- * {@code serviceData} is empty.
- */
- public Builder serviceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
- if (serviceDataUuid == null || serviceData == null) {
- throw new IllegalArgumentException(
- "serviceDataUuid or serviceDataUuid is null");
- }
- mServiceDataUuid = serviceDataUuid;
- mServiceData = serviceData;
- return this;
- }
-
- /**
- * Set manufacturer id and data. See <a
- * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned
- * manufacturer identifies</a> for the existing company identifiers.
- *
- * @param manufacturerId Manufacturer id assigned by Bluetooth SIG.
- * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of
- * the manufacturer specific data are the manufacturer id.
- * @throws IllegalArgumentException If the {@code manufacturerId} is negative or
- * {@code manufacturerSpecificData} is null.
- */
- public Builder manufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
- if (manufacturerId < 0) {
- throw new IllegalArgumentException(
- "invalid manufacturerId - " + manufacturerId);
- }
- if (manufacturerSpecificData == null) {
- throw new IllegalArgumentException("manufacturerSpecificData is null");
- }
- mManufacturerId = manufacturerId;
- mManufacturerSpecificData = manufacturerSpecificData;
- return this;
- }
-
- /**
- * Whether the transmission power level should be included in the advertising packet.
- */
- public Builder includeTxPowerLevel(boolean includeTxPowerLevel) {
- mIncludeTxPowerLevel = includeTxPowerLevel;
- return this;
- }
-
- /**
- * Build the {@link BluetoothLeAdvertiseScanData}.
- *
- * @throws IllegalArgumentException If the data size is larger than 31 bytes.
- */
- public AdvertisementData build() {
- if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) {
- throw new IllegalArgumentException(
- "advertisement data size is larger than 31 bytes");
- }
- return new AdvertisementData(mDataType,
- mServiceUuids,
- mServiceDataUuid,
- mServiceData, mManufacturerId, mManufacturerSpecificData,
- mIncludeTxPowerLevel);
- }
-
- // Compute the size of the advertisement data.
- private int totalBytes() {
- int size = FLAGS_FIELD_BYTES; // flags field is always set.
- if (mServiceUuids != null) {
- int num16BitUuids = 0;
- int num32BitUuids = 0;
- int num128BitUuids = 0;
- for (ParcelUuid uuid : mServiceUuids) {
- if (BluetoothUuid.is16BitUuid(uuid)) {
- ++num16BitUuids;
- } else if (BluetoothUuid.is32BitUuid(uuid)) {
- ++num32BitUuids;
- } else {
- ++num128BitUuids;
- }
- }
- // 16 bit service uuids are grouped into one field when doing advertising.
- if (num16BitUuids != 0) {
- size += OVERHEAD_BYTES_PER_FIELD +
- num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
- }
- // 32 bit service uuids are grouped into one field when doing advertising.
- if (num32BitUuids != 0) {
- size += OVERHEAD_BYTES_PER_FIELD +
- num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
- }
- // 128 bit service uuids are grouped into one field when doing advertising.
- if (num128BitUuids != 0) {
- size += OVERHEAD_BYTES_PER_FIELD +
- num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
- }
- }
- if (mServiceData != null) {
- size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length;
- }
- if (mManufacturerSpecificData != null) {
- size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length;
- }
- if (mIncludeTxPowerLevel) {
- size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
- }
- return size;
- }
- }
-
- }
-
- /**
- * Represents a scan record from Bluetooth LE scan.
- */
- public static final class ScanRecord extends AdvertiseBaseData {
- // Flags of the advertising data.
- private final int mAdvertiseFlags;
-
- // Transmission power level(in dB).
- private final int mTxPowerLevel;
-
- // Local name of the Bluetooth LE device.
- private final String mLocalName;
-
- /**
- * Returns the advertising flags indicating the discoverable mode and capability of the
- * device. Returns -1 if the flag field is not set.
- */
- public int getAdvertiseFlags() {
- return mAdvertiseFlags;
- }
-
- /**
- * Returns the transmission power level of the packet in dBm. Returns
- * {@link Integer#MIN_VALUE} if the field is not set. This value can be used to calculate
- * the path loss of a received packet using the following equation:
- * <p>
- * <code>pathloss = txPowerLevel - rssi</code>
- */
- public int getTxPowerLevel() {
- return mTxPowerLevel;
- }
-
- /**
- * Returns the local name of the BLE device. The is a UTF-8 encoded string.
- */
- @Nullable
- public String getLocalName() {
- return mLocalName;
- }
-
- ScanRecord(int dataType,
- List<ParcelUuid> serviceUuids,
- ParcelUuid serviceDataUuid, byte[] serviceData,
- int manufacturerId,
- byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel,
- String localName) {
- super(dataType, serviceUuids, serviceDataUuid, serviceData, manufacturerId,
- manufacturerSpecificData);
- mLocalName = localName;
- mAdvertiseFlags = advertiseFlags;
- mTxPowerLevel = txPowerLevel;
- }
-
- /**
- * Get a {@link Parser} to parse the scan record byte array into {@link ScanRecord}.
- */
- public static Parser getParser() {
- return new Parser();
- }
-
- /**
- * A parser class used to parse a Bluetooth LE scan record to
- * {@link BluetoothLeAdvertiseScanData}. Note not all field types would be parsed.
- */
- public static final class Parser {
- private static final String PARSER_TAG = "BluetoothLeAdvertiseDataParser";
-
- // The following data type values are assigned by Bluetooth SIG.
- // For more details refer to Bluetooth 4.0 specification, Volume 3, Part C, Section 18.
- private static final int DATA_TYPE_FLAGS = 0x01;
- private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
- private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
- private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
- private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
- private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
- private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
- private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
- private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
- private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
- private static final int DATA_TYPE_SERVICE_DATA = 0x16;
- private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
-
- // Helper method to extract bytes from byte array.
- private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
- byte[] bytes = new byte[length];
- System.arraycopy(scanRecord, start, bytes, 0, length);
- return bytes;
- }
-
- /**
- * Parse scan record to {@link BluetoothLeAdvertiseScanData.ScanRecord}.
- * <p>
- * The format is defined in Bluetooth 4.0 specification, Volume 3, Part C, Section 11
- * and 18.
- * <p>
- * All numerical multi-byte entities and values shall use little-endian
- * <strong>byte</strong> order.
- *
- * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
- */
- public ScanRecord parseFromScanRecord(byte[] scanRecord) {
- if (scanRecord == null) {
- return null;
- }
-
- int currentPos = 0;
- int advertiseFlag = -1;
- List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
- String localName = null;
- int txPowerLevel = Integer.MIN_VALUE;
- ParcelUuid serviceDataUuid = null;
- byte[] serviceData = null;
- int manufacturerId = -1;
- byte[] manufacturerSpecificData = null;
-
- try {
- while (currentPos < scanRecord.length) {
- // length is unsigned int.
- int length = scanRecord[currentPos++] & 0xFF;
- if (length == 0) {
- break;
- }
- // Note the length includes the length of the field type itself.
- int dataLength = length - 1;
- // fieldType is unsigned int.
- int fieldType = scanRecord[currentPos++] & 0xFF;
- switch (fieldType) {
- case DATA_TYPE_FLAGS:
- advertiseFlag = scanRecord[currentPos] & 0xFF;
- break;
- case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
- case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
- parseServiceUuid(scanRecord, currentPos,
- dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
- break;
- case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
- case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
- parseServiceUuid(scanRecord, currentPos, dataLength,
- BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
- break;
- case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
- case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
- parseServiceUuid(scanRecord, currentPos, dataLength,
- BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
- break;
- case DATA_TYPE_LOCAL_NAME_SHORT:
- case DATA_TYPE_LOCAL_NAME_COMPLETE:
- localName = new String(
- extractBytes(scanRecord, currentPos, dataLength));
- break;
- case DATA_TYPE_TX_POWER_LEVEL:
- txPowerLevel = scanRecord[currentPos];
- break;
- case DATA_TYPE_SERVICE_DATA:
- serviceData = extractBytes(scanRecord, currentPos, dataLength);
- // The first two bytes of the service data are service data uuid.
- int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
- byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
- serviceUuidLength);
- serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes);
- break;
- case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
- manufacturerSpecificData = extractBytes(scanRecord, currentPos,
- dataLength);
- // The first two bytes of the manufacturer specific data are
- // manufacturer ids in little endian.
- manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) +
- (manufacturerSpecificData[0] & 0xFF);
- break;
- default:
- // Just ignore, we don't handle such data type.
- break;
- }
- currentPos += dataLength;
- }
-
- if (serviceUuids.isEmpty()) {
- serviceUuids = null;
- }
- return new ScanRecord(PARSED_SCAN_RECORD,
- serviceUuids, serviceDataUuid, serviceData,
- manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel,
- localName);
- } catch (IndexOutOfBoundsException e) {
- Log.e(PARSER_TAG,
- "unable to parse scan record: " + Arrays.toString(scanRecord));
- return null;
- }
- }
-
- // Parse service uuids.
- private int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
- int uuidLength, List<ParcelUuid> serviceUuids) {
- while (dataLength > 0) {
- byte[] uuidBytes = extractBytes(scanRecord, currentPos,
- uuidLength);
- serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
- dataLength -= uuidLength;
- currentPos += uuidLength;
- }
- return currentPos;
- }
- }
- }
-
-}
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/BluetoothLeAdvertiser.java
deleted file mode 100644
index 30c90c4..0000000
--- a/core/java/android/bluetooth/BluetoothLeAdvertiser.java
+++ /dev/null
@@ -1,615 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-import android.bluetooth.BluetoothLeAdvertiseScanData.AdvertisementData;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.ParcelUuid;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop
- * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by
- * {@link BluetoothLeAdvertiseScanData.AdvertisementData}.
- * <p>
- * To get an instance of {@link BluetoothLeAdvertiser}, call the
- * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
- * <p>
- * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
- * @see BluetoothLeAdvertiseScanData.AdvertisementData
- */
-public class BluetoothLeAdvertiser {
-
- private static final String TAG = "BluetoothLeAdvertiser";
-
- /**
- * The {@link Settings} provide a way to adjust advertising preferences for each individual
- * advertisement. Use {@link Settings.Builder} to create a {@link Settings} instance.
- */
- public static final class Settings implements Parcelable {
- /**
- * Perform Bluetooth LE advertising in low power mode. This is the default and preferred
- * advertising mode as it consumes the least power.
- */
- public static final int ADVERTISE_MODE_LOW_POWER = 0;
- /**
- * Perform Bluetooth LE advertising in balanced power mode. This is balanced between
- * advertising frequency and power consumption.
- */
- public static final int ADVERTISE_MODE_BALANCED = 1;
- /**
- * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest
- * power consumption and should not be used for background continuous advertising.
- */
- public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
-
- /**
- * Advertise using the lowest transmission(tx) power level. An app can use low transmission
- * power to restrict the visibility range of its advertising packet.
- */
- public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
- /**
- * Advertise using low tx power level.
- */
- public static final int ADVERTISE_TX_POWER_LOW = 1;
- /**
- * Advertise using medium tx power level.
- */
- public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
- /**
- * Advertise using high tx power level. This is corresponding to largest visibility range of
- * the advertising packet.
- */
- public static final int ADVERTISE_TX_POWER_HIGH = 3;
-
- /**
- * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.0
- * vol6, part B, section 4.4.2 - Advertising state.
- */
- public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0;
- /**
- * Scannable undirected advertise type, as defined in same spec mentioned above. This event
- * type allows a scanner to send a scan request asking additional information about the
- * advertiser.
- */
- public static final int ADVERTISE_TYPE_SCANNABLE = 1;
- /**
- * Connectable undirected advertising type, as defined in same spec mentioned above. This
- * event type allows a scanner to send scan request asking additional information about the
- * advertiser. It also allows an initiator to send a connect request for connection.
- */
- public static final int ADVERTISE_TYPE_CONNECTABLE = 2;
-
- private final int mAdvertiseMode;
- private final int mAdvertiseTxPowerLevel;
- private final int mAdvertiseEventType;
-
- private Settings(int advertiseMode, int advertiseTxPowerLevel,
- int advertiseEventType) {
- mAdvertiseMode = advertiseMode;
- mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
- mAdvertiseEventType = advertiseEventType;
- }
-
- private Settings(Parcel in) {
- mAdvertiseMode = in.readInt();
- mAdvertiseTxPowerLevel = in.readInt();
- mAdvertiseEventType = in.readInt();
- }
-
- /**
- * Creates a {@link Builder} to construct a {@link Settings} object.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- * Returns the advertise mode.
- */
- public int getMode() {
- return mAdvertiseMode;
- }
-
- /**
- * Returns the tx power level for advertising.
- */
- public int getTxPowerLevel() {
- return mAdvertiseTxPowerLevel;
- }
-
- /**
- * Returns the advertise event type.
- */
- public int getType() {
- return mAdvertiseEventType;
- }
-
- @Override
- public String toString() {
- return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel="
- + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]";
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mAdvertiseMode);
- dest.writeInt(mAdvertiseTxPowerLevel);
- dest.writeInt(mAdvertiseEventType);
- }
-
- public static final Parcelable.Creator<Settings> CREATOR =
- new Creator<BluetoothLeAdvertiser.Settings>() {
- @Override
- public Settings[] newArray(int size) {
- return new Settings[size];
- }
-
- @Override
- public Settings createFromParcel(Parcel in) {
- return new Settings(in);
- }
- };
-
- /**
- * Builder class for {@link BluetoothLeAdvertiser.Settings}. Caller should use
- * {@link Settings#newBuilder()} to get an instance of the builder.
- */
- public static final class Builder {
- private int mMode = ADVERTISE_MODE_LOW_POWER;
- private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
- private int mType = ADVERTISE_TYPE_NON_CONNECTABLE;
-
- // Private constructor, use Settings.newBuilder() get an instance of BUILDER.
- private Builder() {
- }
-
- /**
- * Set advertise mode to control the advertising power and latency.
- *
- * @param advertiseMode Bluetooth LE Advertising mode, can only be one of
- * {@link Settings#ADVERTISE_MODE_LOW_POWER},
- * {@link Settings#ADVERTISE_MODE_BALANCED}, or
- * {@link Settings#ADVERTISE_MODE_LOW_LATENCY}.
- * @throws IllegalArgumentException If the advertiseMode is invalid.
- */
- public Builder advertiseMode(int advertiseMode) {
- if (advertiseMode < ADVERTISE_MODE_LOW_POWER
- || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) {
- throw new IllegalArgumentException("unknown mode " + advertiseMode);
- }
- mMode = advertiseMode;
- return this;
- }
-
- /**
- * Set advertise tx power level to control the transmission power level for the
- * advertising.
- *
- * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one
- * of {@link Settings#ADVERTISE_TX_POWER_ULTRA_LOW},
- * {@link Settings#ADVERTISE_TX_POWER_LOW},
- * {@link Settings#ADVERTISE_TX_POWER_MEDIUM} or
- * {@link Settings#ADVERTISE_TX_POWER_HIGH}.
- * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
- */
- public Builder txPowerLevel(int txPowerLevel) {
- if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW
- || txPowerLevel > ADVERTISE_TX_POWER_HIGH) {
- throw new IllegalArgumentException("unknown tx power level " + txPowerLevel);
- }
- mTxPowerLevel = txPowerLevel;
- return this;
- }
-
- /**
- * Set advertise type to control the event type of advertising.
- *
- * @param type Bluetooth LE Advertising type, can be either
- * {@link Settings#ADVERTISE_TYPE_NON_CONNECTABLE},
- * {@link Settings#ADVERTISE_TYPE_SCANNABLE} or
- * {@link Settings#ADVERTISE_TYPE_CONNECTABLE}.
- * @throws IllegalArgumentException If the {@code type} is invalid.
- */
- public Builder type(int type) {
- if (type < ADVERTISE_TYPE_NON_CONNECTABLE
- || type > ADVERTISE_TYPE_CONNECTABLE) {
- throw new IllegalArgumentException("unknown advertise type " + type);
- }
- mType = type;
- return this;
- }
-
- /**
- * Build the {@link Settings} object.
- */
- public Settings build() {
- return new Settings(mMode, mTxPowerLevel, mType);
- }
- }
- }
-
- /**
- * Callback of Bluetooth LE advertising, which is used to deliver operation status for start and
- * stop advertising.
- */
- public interface AdvertiseCallback {
-
- /**
- * The operation is success.
- *
- * @hide
- */
- public static final int SUCCESS = 0;
- /**
- * Fails to start advertising as the advertisement data contains services that are not added
- * to the local bluetooth Gatt server.
- */
- public static final int ADVERTISING_SERVICE_UNKNOWN = 1;
- /**
- * Fails to start advertising as system runs out of quota for advertisers.
- */
- public static final int TOO_MANY_ADVERTISERS = 2;
-
- /**
- * Fails to start advertising as the advertising is already started.
- */
- public static final int ADVERTISING_ALREADY_STARTED = 3;
- /**
- * Fails to stop advertising as the advertising is not started.
- */
- public static final int ADVERISING_NOT_STARTED = 4;
-
- /**
- * Operation fails due to bluetooth controller failure.
- */
- public static final int CONTROLLER_FAILURE = 5;
-
- /**
- * Callback when advertising operation succeeds.
- *
- * @param settingsInEffect The actual settings used for advertising, which may be different
- * from what the app asks.
- */
- public void onSuccess(Settings settingsInEffect);
-
- /**
- * Callback when advertising operation fails.
- *
- * @param errorCode Error code for failures.
- */
- public void onFailure(int errorCode);
- }
-
- private final IBluetoothGatt mBluetoothGatt;
- private final Handler mHandler;
- private final Map<Settings, AdvertiseCallbackWrapper>
- mLeAdvertisers = new HashMap<Settings, AdvertiseCallbackWrapper>();
-
- // Package private constructor, use BluetoothAdapter.getLeAdvertiser() instead.
- BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) {
- mBluetoothGatt = bluetoothGatt;
- mHandler = new Handler(Looper.getMainLooper());
- }
-
- /**
- * Bluetooth GATT interface callbacks for advertising.
- */
- private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
- private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
- private final AdvertiseCallback mAdvertiseCallback;
- private final AdvertisementData mAdvertisement;
- private final AdvertisementData mScanResponse;
- private final Settings mSettings;
- private final IBluetoothGatt mBluetoothGatt;
-
- // mLeHandle 0: not registered
- // -1: scan stopped
- // >0: registered and scan started
- private int mLeHandle;
- private boolean isAdvertising = false;
-
- public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
- AdvertisementData advertiseData, AdvertisementData scanResponse, Settings settings,
- IBluetoothGatt bluetoothGatt) {
- mAdvertiseCallback = advertiseCallback;
- mAdvertisement = advertiseData;
- mScanResponse = scanResponse;
- mSettings = settings;
- mBluetoothGatt = bluetoothGatt;
- mLeHandle = 0;
- }
-
- public boolean advertiseStarted() {
- boolean started = false;
- synchronized (this) {
- if (mLeHandle == -1) {
- return false;
- }
- try {
- wait(LE_CALLBACK_TIMEOUT_MILLIS);
- } catch (InterruptedException e) {
- Log.e(TAG, "Callback reg wait interrupted: " + e);
- }
- started = (mLeHandle > 0 && isAdvertising);
- }
- return started;
- }
-
- public boolean advertiseStopped() {
- synchronized (this) {
- try {
- wait(LE_CALLBACK_TIMEOUT_MILLIS);
- } catch (InterruptedException e) {
- Log.e(TAG, "Callback reg wait interrupted: " + e);
- }
- return !isAdvertising;
- }
- }
-
- /**
- * Application interface registered - app is ready to go
- */
- @Override
- public void onClientRegistered(int status, int clientIf) {
- Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
- synchronized (this) {
- if (status == BluetoothGatt.GATT_SUCCESS) {
- mLeHandle = clientIf;
- try {
- mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement,
- mScanResponse, mSettings);
- } catch (RemoteException e) {
- Log.e(TAG, "fail to start le advertise: " + e);
- mLeHandle = -1;
- notifyAll();
- } catch (Exception e) {
- Log.e(TAG, "fail to start advertise: " + e.getStackTrace());
- }
- } else {
- // registration failed
- mLeHandle = -1;
- notifyAll();
- }
- }
- }
-
- @Override
- public void onClientConnectionState(int status, int clientIf,
- boolean connected, String address) {
- // no op
- }
-
- @Override
- public void onScanResult(String address, int rssi, byte[] advData) {
- // no op
- }
-
- @Override
- public void onGetService(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid) {
- // no op
- }
-
- @Override
- public void onGetIncludedService(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int inclSrvcType, int inclSrvcInstId,
- ParcelUuid inclSrvcUuid) {
- // no op
- }
-
- @Override
- public void onGetCharacteristic(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int charProps) {
- // no op
- }
-
- @Override
- public void onGetDescriptor(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descInstId, ParcelUuid descUuid) {
- // no op
- }
-
- @Override
- public void onSearchComplete(String address, int status) {
- // no op
- }
-
- @Override
- public void onCharacteristicRead(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid, byte[] value) {
- // no op
- }
-
- @Override
- public void onCharacteristicWrite(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid) {
- // no op
- }
-
- @Override
- public void onNotify(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- byte[] value) {
- // no op
- }
-
- @Override
- public void onDescriptorRead(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descInstId, ParcelUuid descrUuid, byte[] value) {
- // no op
- }
-
- @Override
- public void onDescriptorWrite(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descInstId, ParcelUuid descrUuid) {
- // no op
- }
-
- @Override
- public void onExecuteWrite(String address, int status) {
- // no op
- }
-
- @Override
- public void onReadRemoteRssi(String address, int rssi, int status) {
- // no op
- }
-
- @Override
- public void onAdvertiseStateChange(int advertiseState, int status) {
- // no op
- }
-
- @Override
- public void onMultiAdvertiseCallback(int status) {
- synchronized (this) {
- if (status == 0) {
- isAdvertising = !isAdvertising;
- if (!isAdvertising) {
- try {
- mBluetoothGatt.unregisterClient(mLeHandle);
- mLeHandle = -1;
- } catch (RemoteException e) {
- Log.e(TAG, "remote exception when unregistering", e);
- }
- }
- mAdvertiseCallback.onSuccess(null);
- } else {
- mAdvertiseCallback.onFailure(status);
- }
- notifyAll();
- }
-
- }
-
- /**
- * Callback reporting LE ATT MTU.
- *
- * @hide
- */
- public void onConfigureMTU(String address, int mtu, int status) {
- // no op
- }
- }
-
- /**
- * Start Bluetooth LE Advertising.
- *
- * @param settings {@link Settings} for Bluetooth LE advertising.
- * @param advertiseData {@link AdvertisementData} to be advertised.
- * @param callback {@link AdvertiseCallback} for advertising status.
- */
- public void startAdvertising(Settings settings,
- AdvertisementData advertiseData, final AdvertiseCallback callback) {
- startAdvertising(settings, advertiseData, null, callback);
- }
-
- /**
- * Start Bluetooth LE Advertising.
- * @param settings {@link Settings} for Bluetooth LE advertising.
- * @param advertiseData {@link AdvertisementData} to be advertised in advertisement packet.
- * @param scanResponse {@link AdvertisementData} for scan response.
- * @param callback {@link AdvertiseCallback} for advertising status.
- */
- public void startAdvertising(Settings settings,
- AdvertisementData advertiseData, AdvertisementData scanResponse,
- final AdvertiseCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback cannot be null");
- }
- if (mLeAdvertisers.containsKey(settings)) {
- postCallbackFailure(callback, AdvertiseCallback.ADVERTISING_ALREADY_STARTED);
- return;
- }
- AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
- scanResponse, settings, mBluetoothGatt);
- UUID uuid = UUID.randomUUID();
- try {
- mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
- if (wrapper.advertiseStarted()) {
- mLeAdvertisers.put(settings, wrapper);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "failed to stop advertising", e);
- }
- }
-
- /**
- * Stop Bluetooth LE advertising. Returns immediately, the operation status will be delivered
- * through the {@code callback}.
- * <p>
- * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- *
- * @param settings {@link Settings} used to start Bluetooth LE advertising.
- * @param callback {@link AdvertiseCallback} for delivering stopping advertising status.
- */
- public void stopAdvertising(final Settings settings, final AdvertiseCallback callback) {
- if (callback == null) {
- throw new IllegalArgumentException("callback cannot be null");
- }
- AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(settings);
- if (wrapper == null) {
- postCallbackFailure(callback, AdvertiseCallback.ADVERISING_NOT_STARTED);
- return;
- }
- try {
- mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle);
- if (wrapper.advertiseStopped()) {
- mLeAdvertisers.remove(settings);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "failed to stop advertising", e);
- }
- }
-
- private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- callback.onFailure(error);
- }
- });
- }
-}
diff --git a/core/java/android/bluetooth/BluetoothLeScanner.aidl b/core/java/android/bluetooth/BluetoothLeScanner.aidl
deleted file mode 100644
index 8cecdd7..0000000
--- a/core/java/android/bluetooth/BluetoothLeScanner.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.bluetooth;
-
-parcelable BluetoothLeScanner.ScanResult;
-parcelable BluetoothLeScanner.Settings;
diff --git a/core/java/android/bluetooth/BluetoothLeScanner.java b/core/java/android/bluetooth/BluetoothLeScanner.java
deleted file mode 100644
index ed3188b..0000000
--- a/core/java/android/bluetooth/BluetoothLeScanner.java
+++ /dev/null
@@ -1,759 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package android.bluetooth;
-
-import android.annotation.Nullable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.ParcelUuid;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class provides methods to perform scan related operations for Bluetooth LE devices. An
- * application can scan for a particular type of BLE devices using {@link BluetoothLeScanFilter}. It
- * can also request different types of callbacks for delivering the result.
- * <p>
- * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
- * {@link BluetoothLeScanner}.
- * <p>
- * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
- * @see BluetoothLeScanFilter
- */
-public class BluetoothLeScanner {
-
- private static final String TAG = "BluetoothLeScanner";
- private static final boolean DBG = true;
-
- /**
- * Settings for Bluetooth LE scan.
- */
- public static final class Settings implements Parcelable {
- /**
- * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes
- * the least power.
- */
- public static final int SCAN_MODE_LOW_POWER = 0;
- /**
- * Perform Bluetooth LE scan in balanced power mode.
- */
- public static final int SCAN_MODE_BALANCED = 1;
- /**
- * Scan using highest duty cycle. It's recommended only using this mode when the application
- * is running in foreground.
- */
- public static final int SCAN_MODE_LOW_LATENCY = 2;
-
- /**
- * Callback each time when a bluetooth advertisement is found.
- */
- public static final int CALLBACK_TYPE_ON_UPDATE = 0;
- /**
- * Callback when a bluetooth advertisement is found for the first time.
- */
- public static final int CALLBACK_TYPE_ON_FOUND = 1;
- /**
- * Callback when a bluetooth advertisement is found for the first time, then lost.
- */
- public static final int CALLBACK_TYPE_ON_LOST = 2;
-
- /**
- * Full scan result which contains device mac address, rssi, advertising and scan response
- * and scan timestamp.
- */
- public static final int SCAN_RESULT_TYPE_FULL = 0;
- /**
- * Truncated scan result which contains device mac address, rssi and scan timestamp. Note
- * it's possible for an app to get more scan results that it asks if there are multiple apps
- * using this type. TODO: decide whether we could unhide this setting.
- *
- * @hide
- */
- public static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
-
- // Bluetooth LE scan mode.
- private int mScanMode;
-
- // Bluetooth LE scan callback type
- private int mCallbackType;
-
- // Bluetooth LE scan result type
- private int mScanResultType;
-
- // Time of delay for reporting the scan result
- private long mReportDelayMicros;
-
- public int getScanMode() {
- return mScanMode;
- }
-
- public int getCallbackType() {
- return mCallbackType;
- }
-
- public int getScanResultType() {
- return mScanResultType;
- }
-
- /**
- * Returns report delay timestamp based on the device clock.
- */
- public long getReportDelayMicros() {
- return mReportDelayMicros;
- }
-
- /**
- * Creates a new {@link Builder} to build {@link Settings} object.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- private Settings(int scanMode, int callbackType, int scanResultType,
- long reportDelayMicros) {
- mScanMode = scanMode;
- mCallbackType = callbackType;
- mScanResultType = scanResultType;
- mReportDelayMicros = reportDelayMicros;
- }
-
- private Settings(Parcel in) {
- mScanMode = in.readInt();
- mCallbackType = in.readInt();
- mScanResultType = in.readInt();
- mReportDelayMicros = in.readLong();
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mScanMode);
- dest.writeInt(mCallbackType);
- dest.writeInt(mScanResultType);
- dest.writeLong(mReportDelayMicros);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public static final Parcelable.Creator<Settings> CREATOR = new Creator<Settings>() {
- @Override
- public Settings[] newArray(int size) {
- return new Settings[size];
- }
-
- @Override
- public Settings createFromParcel(Parcel in) {
- return new Settings(in);
- }
- };
-
- /**
- * Builder for {@link BluetoothLeScanner.Settings}.
- */
- public static class Builder {
- private int mScanMode = SCAN_MODE_LOW_POWER;
- private int mCallbackType = CALLBACK_TYPE_ON_UPDATE;
- private int mScanResultType = SCAN_RESULT_TYPE_FULL;
- private long mReportDelayMicros = 0;
-
- // Hidden constructor.
- private Builder() {
- }
-
- /**
- * Set scan mode for Bluetooth LE scan.
- *
- * @param scanMode The scan mode can be one of {@link Settings#SCAN_MODE_LOW_POWER},
- * {@link Settings#SCAN_MODE_BALANCED} or
- * {@link Settings#SCAN_MODE_LOW_LATENCY}.
- * @throws IllegalArgumentException If the {@code scanMode} is invalid.
- */
- public Builder scanMode(int scanMode) {
- if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) {
- throw new IllegalArgumentException("invalid scan mode " + scanMode);
- }
- mScanMode = scanMode;
- return this;
- }
-
- /**
- * Set callback type for Bluetooth LE scan.
- *
- * @param callbackType The callback type for the scan. Can be either one of
- * {@link Settings#CALLBACK_TYPE_ON_UPDATE},
- * {@link Settings#CALLBACK_TYPE_ON_FOUND} or
- * {@link Settings#CALLBACK_TYPE_ON_LOST}.
- * @throws IllegalArgumentException If the {@code callbackType} is invalid.
- */
- public Builder callbackType(int callbackType) {
- if (callbackType < CALLBACK_TYPE_ON_UPDATE
- || callbackType > CALLBACK_TYPE_ON_LOST) {
- throw new IllegalArgumentException("invalid callback type - " + callbackType);
- }
- mCallbackType = callbackType;
- return this;
- }
-
- /**
- * Set scan result type for Bluetooth LE scan.
- *
- * @param scanResultType Type for scan result, could be either
- * {@link Settings#SCAN_RESULT_TYPE_FULL} or
- * {@link Settings#SCAN_RESULT_TYPE_TRUNCATED}.
- * @throws IllegalArgumentException If the {@code scanResultType} is invalid.
- * @hide
- */
- public Builder scanResultType(int scanResultType) {
- if (scanResultType < SCAN_RESULT_TYPE_FULL
- || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) {
- throw new IllegalArgumentException(
- "invalid scanResultType - " + scanResultType);
- }
- mScanResultType = scanResultType;
- return this;
- }
-
- /**
- * Set report delay timestamp for Bluetooth LE scan.
- */
- public Builder reportDelayMicros(long reportDelayMicros) {
- mReportDelayMicros = reportDelayMicros;
- return this;
- }
-
- /**
- * Build {@link Settings}.
- */
- public Settings build() {
- return new Settings(mScanMode, mCallbackType, mScanResultType, mReportDelayMicros);
- }
- }
- }
-
- /**
- * ScanResult for Bluetooth LE scan.
- */
- public static final class ScanResult implements Parcelable {
- // Remote bluetooth device.
- private BluetoothDevice mDevice;
-
- // Scan record, including advertising data and scan response data.
- private byte[] mScanRecord;
-
- // Received signal strength.
- private int mRssi;
-
- // Device timestamp when the result was last seen.
- private long mTimestampMicros;
-
- // Constructor of scan result.
- public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi, long timestampMicros) {
- mDevice = device;
- mScanRecord = scanRecord;
- mRssi = rssi;
- mTimestampMicros = timestampMicros;
- }
-
- private ScanResult(Parcel in) {
- readFromParcel(in);
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- if (mDevice != null) {
- dest.writeInt(1);
- mDevice.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
- }
- if (mScanRecord != null) {
- dest.writeInt(1);
- dest.writeByteArray(mScanRecord);
- } else {
- dest.writeInt(0);
- }
- dest.writeInt(mRssi);
- dest.writeLong(mTimestampMicros);
- }
-
- private void readFromParcel(Parcel in) {
- if (in.readInt() == 1) {
- mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
- }
- if (in.readInt() == 1) {
- mScanRecord = in.createByteArray();
- }
- mRssi = in.readInt();
- mTimestampMicros = in.readLong();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * Returns the remote bluetooth device identified by the bluetooth device address.
- */
- @Nullable
- public BluetoothDevice getDevice() {
- return mDevice;
- }
-
- @Nullable /**
- * Returns the scan record, which can be a combination of advertisement and scan response.
- */
- public byte[] getScanRecord() {
- return mScanRecord;
- }
-
- /**
- * Returns the received signal strength in dBm. The valid range is [-127, 127].
- */
- public int getRssi() {
- return mRssi;
- }
-
- /**
- * Returns timestamp since boot when the scan record was observed.
- */
- public long getTimestampMicros() {
- return mTimestampMicros;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampMicros);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- ScanResult other = (ScanResult) obj;
- return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) &&
- Objects.deepEquals(mScanRecord, other.mScanRecord)
- && (mTimestampMicros == other.mTimestampMicros);
- }
-
- @Override
- public String toString() {
- return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord="
- + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampMicros="
- + mTimestampMicros + '}';
- }
-
- public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
- @Override
- public ScanResult createFromParcel(Parcel source) {
- return new ScanResult(source);
- }
-
- @Override
- public ScanResult[] newArray(int size) {
- return new ScanResult[size];
- }
- };
-
- }
-
- /**
- * Callback of Bluetooth LE scans. The results of the scans will be delivered through the
- * callbacks.
- */
- public interface ScanCallback {
- /**
- * Callback when any BLE beacon is found.
- *
- * @param result A Bluetooth LE scan result.
- */
- public void onDeviceUpdate(ScanResult result);
-
- /**
- * Callback when the BLE beacon is found for the first time.
- *
- * @param result The Bluetooth LE scan result when the onFound event is triggered.
- */
- public void onDeviceFound(ScanResult result);
-
- /**
- * Callback when the BLE device was lost. Note a device has to be "found" before it's lost.
- *
- * @param device The Bluetooth device that is lost.
- */
- public void onDeviceLost(BluetoothDevice device);
-
- /**
- * Callback when batch results are delivered.
- *
- * @param results List of scan results that are previously scanned.
- */
- public void onBatchScanResults(List<ScanResult> results);
-
- /**
- * Fails to start scan as BLE scan with the same settings is already started by the app.
- */
- public static final int SCAN_ALREADY_STARTED = 1;
- /**
- * Fails to start scan as app cannot be registered.
- */
- public static final int APPLICATION_REGISTRATION_FAILED = 2;
- /**
- * Fails to start scan due to gatt service failure.
- */
- public static final int GATT_SERVICE_FAILURE = 3;
- /**
- * Fails to start scan due to controller failure.
- */
- public static final int CONTROLLER_FAILURE = 4;
-
- /**
- * Callback when scan failed.
- */
- public void onScanFailed(int errorCode);
- }
-
- private final IBluetoothGatt mBluetoothGatt;
- private final Handler mHandler;
- private final Map<Settings, BleScanCallbackWrapper> mLeScanClients;
-
- BluetoothLeScanner(IBluetoothGatt bluetoothGatt) {
- mBluetoothGatt = bluetoothGatt;
- mHandler = new Handler(Looper.getMainLooper());
- mLeScanClients = new HashMap<Settings, BleScanCallbackWrapper>();
- }
-
- /**
- * Bluetooth GATT interface callbacks
- */
- private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub {
- private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5;
-
- private final ScanCallback mScanCallback;
- private final List<BluetoothLeScanFilter> mFilters;
- private Settings mSettings;
- private IBluetoothGatt mBluetoothGatt;
-
- // mLeHandle 0: not registered
- // -1: scan stopped
- // > 0: registered and scan started
- private int mLeHandle;
-
- public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
- List<BluetoothLeScanFilter> filters, Settings settings, ScanCallback scanCallback) {
- mBluetoothGatt = bluetoothGatt;
- mFilters = filters;
- mSettings = settings;
- mScanCallback = scanCallback;
- mLeHandle = 0;
- }
-
- public boolean scanStarted() {
- synchronized (this) {
- if (mLeHandle == -1) {
- return false;
- }
- try {
- wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "Callback reg wait interrupted: " + e);
- }
- }
- return mLeHandle > 0;
- }
-
- public void stopLeScan() {
- synchronized (this) {
- if (mLeHandle <= 0) {
- Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
- return;
- }
- try {
- mBluetoothGatt.stopScan(mLeHandle, false);
- mBluetoothGatt.unregisterClient(mLeHandle);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to stop scan and unregister" + e);
- }
- mLeHandle = -1;
- notifyAll();
- }
- }
-
- /**
- * Application interface registered - app is ready to go
- */
- @Override
- public void onClientRegistered(int status, int clientIf) {
- Log.d(TAG, "onClientRegistered() - status=" + status +
- " clientIf=" + clientIf);
-
- synchronized (this) {
- if (mLeHandle == -1) {
- if (DBG)
- Log.d(TAG, "onClientRegistered LE scan canceled");
- }
-
- if (status == BluetoothGatt.GATT_SUCCESS) {
- mLeHandle = clientIf;
- try {
- mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters);
- } catch (RemoteException e) {
- Log.e(TAG, "fail to start le scan: " + e);
- mLeHandle = -1;
- }
- } else {
- // registration failed
- mLeHandle = -1;
- }
- notifyAll();
- }
- }
-
- @Override
- public void onClientConnectionState(int status, int clientIf,
- boolean connected, String address) {
- // no op
- }
-
- /**
- * Callback reporting an LE scan result.
- *
- * @hide
- */
- @Override
- public void onScanResult(String address, int rssi, byte[] advData) {
- if (DBG)
- Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi);
-
- // Check null in case the scan has been stopped
- synchronized (this) {
- if (mLeHandle <= 0)
- return;
- }
- BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
- address);
- long scanMicros = TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos());
- ScanResult result = new ScanResult(device, advData, rssi,
- scanMicros);
- mScanCallback.onDeviceUpdate(result);
- }
-
- @Override
- public void onGetService(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid) {
- // no op
- }
-
- @Override
- public void onGetIncludedService(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int inclSrvcType, int inclSrvcInstId,
- ParcelUuid inclSrvcUuid) {
- // no op
- }
-
- @Override
- public void onGetCharacteristic(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int charProps) {
- // no op
- }
-
- @Override
- public void onGetDescriptor(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descInstId, ParcelUuid descUuid) {
- // no op
- }
-
- @Override
- public void onSearchComplete(String address, int status) {
- // no op
- }
-
- @Override
- public void onCharacteristicRead(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid, byte[] value) {
- // no op
- }
-
- @Override
- public void onCharacteristicWrite(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid) {
- // no op
- }
-
- @Override
- public void onNotify(String address, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- byte[] value) {
- // no op
- }
-
- @Override
- public void onDescriptorRead(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descInstId, ParcelUuid descrUuid, byte[] value) {
- // no op
- }
-
- @Override
- public void onDescriptorWrite(String address, int status, int srvcType,
- int srvcInstId, ParcelUuid srvcUuid,
- int charInstId, ParcelUuid charUuid,
- int descInstId, ParcelUuid descrUuid) {
- // no op
- }
-
- @Override
- public void onExecuteWrite(String address, int status) {
- // no op
- }
-
- @Override
- public void onReadRemoteRssi(String address, int rssi, int status) {
- // no op
- }
-
- @Override
- public void onAdvertiseStateChange(int advertiseState, int status) {
- // no op
- }
-
- @Override
- public void onMultiAdvertiseCallback(int status) {
- // no op
- }
-
- @Override
- public void onConfigureMTU(String address, int mtu, int status) {
- // no op
- }
- }
-
- /**
- * Scan Bluetooth LE scan. The scan results will be delivered through {@code callback}.
- *
- * @param filters {@link BluetoothLeScanFilter}s for finding exact BLE devices.
- * @param settings Settings for ble scan.
- * @param callback Callback when scan results are delivered.
- * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
- */
- public void startScan(List<BluetoothLeScanFilter> filters, Settings settings,
- final ScanCallback callback) {
- if (settings == null || callback == null) {
- throw new IllegalArgumentException("settings or callback is null");
- }
- synchronized (mLeScanClients) {
- if (mLeScanClients.get(settings) != null) {
- postCallbackError(callback, ScanCallback.SCAN_ALREADY_STARTED);
- return;
- }
- BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters,
- settings, callback);
- try {
- UUID uuid = UUID.randomUUID();
- mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
- if (wrapper.scanStarted()) {
- mLeScanClients.put(settings, wrapper);
- } else {
- postCallbackError(callback, ScanCallback.APPLICATION_REGISTRATION_FAILED);
- return;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "GATT service exception when starting scan", e);
- postCallbackError(callback, ScanCallback.GATT_SERVICE_FAILURE);
- }
- }
- }
-
- private void postCallbackError(final ScanCallback callback, final int errorCode) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- callback.onScanFailed(errorCode);
- }
- });
- }
-
- /**
- * Stop Bluetooth LE scan.
- *
- * @param settings The same settings as used in {@link #startScan}, which is used to identify
- * the BLE scan.
- */
- public void stopScan(Settings settings) {
- synchronized (mLeScanClients) {
- BleScanCallbackWrapper wrapper = mLeScanClients.remove(settings);
- if (wrapper == null) {
- return;
- }
- wrapper.stopLeScan();
- }
- }
-
- /**
- * Returns available storage size for batch scan results. It's recommended not to use batch scan
- * if available storage size is small (less than 1k bytes, for instance).
- *
- * @hide TODO: unhide when batching is supported in stack.
- */
- public int getAvailableBatchStorageSizeBytes() {
- throw new UnsupportedOperationException("not impelemented");
- }
-
- /**
- * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results
- * batched on bluetooth controller.
- *
- * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
- * used to start scan.
- * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will
- * get batch scan callback if the batch scan buffer is flushed.
- * @return Batch Scan results.
- * @hide TODO: unhide when batching is supported in stack.
- */
- public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) {
- throw new UnsupportedOperationException("not impelemented");
- }
-
-}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 1574090..d898060 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -104,6 +104,12 @@ public interface BluetoothProfile {
public static final int MAP = 9;
/**
+ * A2DP Sink Profile
+ * @hide
+ */
+ public static final int A2DP_SINK = 10;
+
+ /**
* Default priority for devices that we try to auto-connect to and
* and allow incoming connections for the profile
* @hide
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index ceed52b..00a0750 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -17,10 +17,10 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeAdvertiseScanData;
-import android.bluetooth.BluetoothLeAdvertiser;
-import android.bluetooth.BluetoothLeScanFilter;
-import android.bluetooth.BluetoothLeScanner;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisementData;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanSettings;
import android.os.ParcelUuid;
import android.bluetooth.IBluetoothGattCallback;
@@ -38,13 +38,12 @@ interface IBluetoothGatt {
void startScanWithUuidsScanParam(in int appIf, in boolean isServer,
in ParcelUuid[] ids, int scanWindow, int scanInterval);
void startScanWithFilters(in int appIf, in boolean isServer,
- in BluetoothLeScanner.Settings settings,
- in List<BluetoothLeScanFilter> filters);
+ in ScanSettings settings, in List<ScanFilter> filters);
void stopScan(in int appIf, in boolean isServer);
void startMultiAdvertising(in int appIf,
- in BluetoothLeAdvertiseScanData.AdvertisementData advertiseData,
- in BluetoothLeAdvertiseScanData.AdvertisementData scanResponse,
- in BluetoothLeAdvertiser.Settings settings);
+ in AdvertisementData advertiseData,
+ in AdvertisementData scanResponse,
+ in AdvertiseSettings settings);
void stopMultiAdvertising(in int appIf);
void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
void unregisterClient(in int clientIf);
diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java
new file mode 100644
index 0000000..f1334c2
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertiseCallback.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+/**
+ * Callback of Bluetooth LE advertising, which is used to deliver advertising operation status.
+ */
+public abstract class AdvertiseCallback {
+
+ /**
+ * The operation is success.
+ *
+ * @hide
+ */
+ public static final int SUCCESS = 0;
+ /**
+ * Fails to start advertising as the advertisement data contains services that are not added to
+ * the local bluetooth GATT server.
+ */
+ public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1;
+ /**
+ * Fails to start advertising as system runs out of quota for advertisers.
+ */
+ public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
+
+ /**
+ * Fails to start advertising as the advertising is already started.
+ */
+ public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
+ /**
+ * Fails to stop advertising as the advertising is not started.
+ */
+ public static final int ADVERTISE_FAILED_NOT_STARTED = 4;
+
+ /**
+ * Operation fails due to bluetooth controller failure.
+ */
+ public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5;
+
+ /**
+ * Callback when advertising operation succeeds.
+ *
+ * @param settingsInEffect The actual settings used for advertising, which may be different from
+ * what the app asks.
+ */
+ public abstract void onSuccess(AdvertiseSettings settingsInEffect);
+
+ /**
+ * Callback when advertising operation fails.
+ *
+ * @param errorCode Error code for failures.
+ */
+ public abstract void onFailure(int errorCode);
+}
diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl b/core/java/android/bluetooth/le/AdvertiseSettings.aidl
index 86ee06d..9f47d74 100644
--- a/core/java/android/bluetooth/BluetoothLeScanFilter.aidl
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
-parcelable BluetoothLeScanFilter;
+parcelable AdvertiseSettings; \ No newline at end of file
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java
new file mode 100644
index 0000000..87d0346
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each
+ * individual advertisement. Use {@link AdvertiseSettings.Builder} to create an instance.
+ */
+public final class AdvertiseSettings implements Parcelable {
+ /**
+ * Perform Bluetooth LE advertising in low power mode. This is the default and preferred
+ * advertising mode as it consumes the least power.
+ */
+ public static final int ADVERTISE_MODE_LOW_POWER = 0;
+ /**
+ * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising
+ * frequency and power consumption.
+ */
+ public static final int ADVERTISE_MODE_BALANCED = 1;
+ /**
+ * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power
+ * consumption and should not be used for background continuous advertising.
+ */
+ public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
+
+ /**
+ * Advertise using the lowest transmission(tx) power level. An app can use low transmission
+ * power to restrict the visibility range of its advertising packet.
+ */
+ public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
+ /**
+ * Advertise using low tx power level.
+ */
+ public static final int ADVERTISE_TX_POWER_LOW = 1;
+ /**
+ * Advertise using medium tx power level.
+ */
+ public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
+ /**
+ * Advertise using high tx power level. This is corresponding to largest visibility range of the
+ * advertising packet.
+ */
+ public static final int ADVERTISE_TX_POWER_HIGH = 3;
+
+ /**
+ * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.1
+ * vol6, part B, section 4.4.2 - Advertising state.
+ */
+ public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0;
+ /**
+ * Scannable undirected advertise type, as defined in same spec mentioned above. This event type
+ * allows a scanner to send a scan request asking additional information about the advertiser.
+ */
+ public static final int ADVERTISE_TYPE_SCANNABLE = 1;
+ /**
+ * Connectable undirected advertising type, as defined in same spec mentioned above. This event
+ * type allows a scanner to send scan request asking additional information about the
+ * advertiser. It also allows an initiator to send a connect request for connection.
+ */
+ public static final int ADVERTISE_TYPE_CONNECTABLE = 2;
+
+ private final int mAdvertiseMode;
+ private final int mAdvertiseTxPowerLevel;
+ private final int mAdvertiseEventType;
+
+ private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
+ int advertiseEventType) {
+ mAdvertiseMode = advertiseMode;
+ mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
+ mAdvertiseEventType = advertiseEventType;
+ }
+
+ private AdvertiseSettings(Parcel in) {
+ mAdvertiseMode = in.readInt();
+ mAdvertiseTxPowerLevel = in.readInt();
+ mAdvertiseEventType = in.readInt();
+ }
+
+ /**
+ * Returns the advertise mode.
+ */
+ public int getMode() {
+ return mAdvertiseMode;
+ }
+
+ /**
+ * Returns the tx power level for advertising.
+ */
+ public int getTxPowerLevel() {
+ return mAdvertiseTxPowerLevel;
+ }
+
+ /**
+ * Returns the advertise event type.
+ */
+ public int getType() {
+ return mAdvertiseEventType;
+ }
+
+ @Override
+ public String toString() {
+ return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel="
+ + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mAdvertiseMode);
+ dest.writeInt(mAdvertiseTxPowerLevel);
+ dest.writeInt(mAdvertiseEventType);
+ }
+
+ public static final Parcelable.Creator<AdvertiseSettings> CREATOR =
+ new Creator<AdvertiseSettings>() {
+ @Override
+ public AdvertiseSettings[] newArray(int size) {
+ return new AdvertiseSettings[size];
+ }
+
+ @Override
+ public AdvertiseSettings createFromParcel(Parcel in) {
+ return new AdvertiseSettings(in);
+ }
+ };
+
+ /**
+ * Builder class for {@link AdvertiseSettings}.
+ */
+ public static final class Builder {
+ private int mMode = ADVERTISE_MODE_LOW_POWER;
+ private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
+ private int mType = ADVERTISE_TYPE_NON_CONNECTABLE;
+
+ /**
+ * Set advertise mode to control the advertising power and latency.
+ *
+ * @param advertiseMode Bluetooth LE Advertising mode, can only be one of
+ * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_POWER},
+ * {@link AdvertiseSettings#ADVERTISE_MODE_BALANCED}, or
+ * {@link AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY}.
+ * @throws IllegalArgumentException If the advertiseMode is invalid.
+ */
+ public Builder setAdvertiseMode(int advertiseMode) {
+ if (advertiseMode < ADVERTISE_MODE_LOW_POWER
+ || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) {
+ throw new IllegalArgumentException("unknown mode " + advertiseMode);
+ }
+ mMode = advertiseMode;
+ return this;
+ }
+
+ /**
+ * Set advertise tx power level to control the transmission power level for the advertising.
+ *
+ * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW},
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_LOW},
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM} or
+ * {@link AdvertiseSettings#ADVERTISE_TX_POWER_HIGH}.
+ * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
+ */
+ public Builder setTxPowerLevel(int txPowerLevel) {
+ if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW
+ || txPowerLevel > ADVERTISE_TX_POWER_HIGH) {
+ throw new IllegalArgumentException("unknown tx power level " + txPowerLevel);
+ }
+ mTxPowerLevel = txPowerLevel;
+ return this;
+ }
+
+ /**
+ * Set advertise type to control the event type of advertising.
+ *
+ * @param type Bluetooth LE Advertising type, can be either
+ * {@link AdvertiseSettings#ADVERTISE_TYPE_NON_CONNECTABLE},
+ * {@link AdvertiseSettings#ADVERTISE_TYPE_SCANNABLE} or
+ * {@link AdvertiseSettings#ADVERTISE_TYPE_CONNECTABLE}.
+ * @throws IllegalArgumentException If the {@code type} is invalid.
+ */
+ public Builder setType(int type) {
+ if (type < ADVERTISE_TYPE_NON_CONNECTABLE
+ || type > ADVERTISE_TYPE_CONNECTABLE) {
+ throw new IllegalArgumentException("unknown advertise type " + type);
+ }
+ mType = type;
+ return this;
+ }
+
+ /**
+ * Build the {@link AdvertiseSettings} object.
+ */
+ public AdvertiseSettings build() {
+ return new AdvertiseSettings(mMode, mTxPowerLevel, mType);
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl b/core/java/android/bluetooth/le/AdvertisementData.aidl
index 3108610..3da1321 100644
--- a/core/java/android/bluetooth/BluetoothLeAdvertiser.aidl
+++ b/core/java/android/bluetooth/le/AdvertisementData.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
-parcelable BluetoothLeAdvertiser.Settings; \ No newline at end of file
+parcelable AdvertisementData; \ No newline at end of file
diff --git a/core/java/android/bluetooth/le/AdvertisementData.java b/core/java/android/bluetooth/le/AdvertisementData.java
new file mode 100644
index 0000000..c587204
--- /dev/null
+++ b/core/java/android/bluetooth/le/AdvertisementData.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothUuid;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Advertisement data packet for Bluetooth LE advertising. This represents the data to be
+ * broadcasted in Bluetooth LE advertising as well as the scan response for active scan.
+ * <p>
+ * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to be
+ * advertised.
+ *
+ * @see BluetoothLeAdvertiser
+ * @see ScanRecord
+ */
+public final class AdvertisementData implements Parcelable {
+
+ @Nullable
+ private final List<ParcelUuid> mServiceUuids;
+
+ private final int mManufacturerId;
+ @Nullable
+ private final byte[] mManufacturerSpecificData;
+
+ @Nullable
+ private final ParcelUuid mServiceDataUuid;
+ @Nullable
+ private final byte[] mServiceData;
+
+ private boolean mIncludeTxPowerLevel;
+
+ private AdvertisementData(List<ParcelUuid> serviceUuids,
+ ParcelUuid serviceDataUuid, byte[] serviceData,
+ int manufacturerId,
+ byte[] manufacturerSpecificData, boolean includeTxPowerLevel) {
+ mServiceUuids = serviceUuids;
+ mManufacturerId = manufacturerId;
+ mManufacturerSpecificData = manufacturerSpecificData;
+ mServiceDataUuid = serviceDataUuid;
+ mServiceData = serviceData;
+ mIncludeTxPowerLevel = includeTxPowerLevel;
+ }
+
+ /**
+ * Returns a list of service uuids within the advertisement that are used to identify the
+ * bluetooth GATT services.
+ */
+ public List<ParcelUuid> getServiceUuids() {
+ return mServiceUuids;
+ }
+
+ /**
+ * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth
+ * SIG.
+ */
+ public int getManufacturerId() {
+ return mManufacturerId;
+ }
+
+ /**
+ * Returns the manufacturer specific data which is the content of manufacturer specific data
+ * field. The first 2 bytes of the data contain the company id.
+ */
+ public byte[] getManufacturerSpecificData() {
+ return mManufacturerSpecificData;
+ }
+
+ /**
+ * Returns a 16 bit uuid of the service that the service data is associated with.
+ */
+ public ParcelUuid getServiceDataUuid() {
+ return mServiceDataUuid;
+ }
+
+ /**
+ * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
+ * service data.
+ */
+ public byte[] getServiceData() {
+ return mServiceData;
+ }
+
+ /**
+ * Whether the transmission power level will be included in the advertisement packet.
+ */
+ public boolean getIncludeTxPowerLevel() {
+ return mIncludeTxPowerLevel;
+ }
+
+ @Override
+ public String toString() {
+ return "AdvertisementData [mServiceUuids=" + mServiceUuids + ", mManufacturerId="
+ + mManufacturerId + ", mManufacturerSpecificData="
+ + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
+ + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
+ + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + "]";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mServiceUuids == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mServiceUuids.size());
+ dest.writeList(mServiceUuids);
+ }
+
+ dest.writeInt(mManufacturerId);
+ if (mManufacturerSpecificData == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mManufacturerSpecificData.length);
+ dest.writeByteArray(mManufacturerSpecificData);
+ }
+
+ if (mServiceDataUuid == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeParcelable(mServiceDataUuid, flags);
+ if (mServiceData == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mServiceData.length);
+ dest.writeByteArray(mServiceData);
+ }
+ }
+ dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
+ }
+
+ public static final Parcelable.Creator<AdvertisementData> CREATOR =
+ new Creator<AdvertisementData>() {
+ @Override
+ public AdvertisementData[] newArray(int size) {
+ return new AdvertisementData[size];
+ }
+
+ @Override
+ public AdvertisementData createFromParcel(Parcel in) {
+ Builder builder = new Builder();
+ if (in.readInt() > 0) {
+ List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
+ in.readList(uuids, ParcelUuid.class.getClassLoader());
+ builder.setServiceUuids(uuids);
+ }
+ int manufacturerId = in.readInt();
+ int manufacturerDataLength = in.readInt();
+ if (manufacturerDataLength > 0) {
+ byte[] manufacturerData = new byte[manufacturerDataLength];
+ in.readByteArray(manufacturerData);
+ builder.setManufacturerData(manufacturerId, manufacturerData);
+ }
+ if (in.readInt() == 1) {
+ ParcelUuid serviceDataUuid = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ int serviceDataLength = in.readInt();
+ if (serviceDataLength > 0) {
+ byte[] serviceData = new byte[serviceDataLength];
+ in.readByteArray(serviceData);
+ builder.setServiceData(serviceDataUuid, serviceData);
+ }
+ }
+ builder.setIncludeTxPowerLevel(in.readByte() == 1);
+ return builder.build();
+ }
+ };
+
+ /**
+ * Builder for {@link AdvertisementData}.
+ */
+ public static final class Builder {
+ private static final int MAX_ADVERTISING_DATA_BYTES = 31;
+ // Each fields need one byte for field length and another byte for field type.
+ private static final int OVERHEAD_BYTES_PER_FIELD = 2;
+ // Flags field will be set by system.
+ private static final int FLAGS_FIELD_BYTES = 3;
+
+ @Nullable
+ private List<ParcelUuid> mServiceUuids;
+ private boolean mIncludeTxPowerLevel;
+ private int mManufacturerId;
+ @Nullable
+ private byte[] mManufacturerSpecificData;
+ @Nullable
+ private ParcelUuid mServiceDataUuid;
+ @Nullable
+ private byte[] mServiceData;
+
+ /**
+ * Set the service uuids. Note the corresponding bluetooth Gatt services need to be already
+ * added on the device before start BLE advertising.
+ *
+ * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or 128-bit
+ * uuids.
+ * @throws IllegalArgumentException If the {@code serviceUuids} are null.
+ */
+ public Builder setServiceUuids(List<ParcelUuid> serviceUuids) {
+ if (serviceUuids == null) {
+ throw new IllegalArgumentException("serivceUuids are null");
+ }
+ mServiceUuids = serviceUuids;
+ return this;
+ }
+
+ /**
+ * Add service data to advertisement.
+ *
+ * @param serviceDataUuid A 16 bit uuid of the service data
+ * @param serviceData Service data - the first two bytes of the service data are the service
+ * data uuid.
+ * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
+ * empty.
+ */
+ public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
+ if (serviceDataUuid == null || serviceData == null) {
+ throw new IllegalArgumentException(
+ "serviceDataUuid or serviceDataUuid is null");
+ }
+ mServiceDataUuid = serviceDataUuid;
+ mServiceData = serviceData;
+ return this;
+ }
+
+ /**
+ * Set manufacturer id and data. See <a
+ * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned
+ * manufacturer identifies</a> for the existing company identifiers.
+ *
+ * @param manufacturerId Manufacturer id assigned by Bluetooth SIG.
+ * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of the
+ * manufacturer specific data are the manufacturer id.
+ * @throws IllegalArgumentException If the {@code manufacturerId} is negative or
+ * {@code manufacturerSpecificData} is null.
+ */
+ public Builder setManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
+ if (manufacturerId < 0) {
+ throw new IllegalArgumentException(
+ "invalid manufacturerId - " + manufacturerId);
+ }
+ if (manufacturerSpecificData == null) {
+ throw new IllegalArgumentException("manufacturerSpecificData is null");
+ }
+ mManufacturerId = manufacturerId;
+ mManufacturerSpecificData = manufacturerSpecificData;
+ return this;
+ }
+
+ /**
+ * Whether the transmission power level should be included in the advertising packet.
+ */
+ public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) {
+ mIncludeTxPowerLevel = includeTxPowerLevel;
+ return this;
+ }
+
+ /**
+ * Build the {@link AdvertisementData}.
+ *
+ * @throws IllegalArgumentException If the data size is larger than 31 bytes.
+ */
+ public AdvertisementData build() {
+ if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) {
+ throw new IllegalArgumentException(
+ "advertisement data size is larger than 31 bytes");
+ }
+ return new AdvertisementData(mServiceUuids,
+ mServiceDataUuid,
+ mServiceData, mManufacturerId, mManufacturerSpecificData,
+ mIncludeTxPowerLevel);
+ }
+
+ // Compute the size of the advertisement data.
+ private int totalBytes() {
+ int size = FLAGS_FIELD_BYTES; // flags field is always set.
+ if (mServiceUuids != null) {
+ int num16BitUuids = 0;
+ int num32BitUuids = 0;
+ int num128BitUuids = 0;
+ for (ParcelUuid uuid : mServiceUuids) {
+ if (BluetoothUuid.is16BitUuid(uuid)) {
+ ++num16BitUuids;
+ } else if (BluetoothUuid.is32BitUuid(uuid)) {
+ ++num32BitUuids;
+ } else {
+ ++num128BitUuids;
+ }
+ }
+ // 16 bit service uuids are grouped into one field when doing advertising.
+ if (num16BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD +
+ num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
+ }
+ // 32 bit service uuids are grouped into one field when doing advertising.
+ if (num32BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD +
+ num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
+ }
+ // 128 bit service uuids are grouped into one field when doing advertising.
+ if (num128BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD +
+ num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
+ }
+ }
+ if (mServiceData != null) {
+ size += OVERHEAD_BYTES_PER_FIELD + mServiceData.length;
+ }
+ if (mManufacturerSpecificData != null) {
+ size += OVERHEAD_BYTES_PER_FIELD + mManufacturerSpecificData.length;
+ }
+ if (mIncludeTxPowerLevel) {
+ size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
+ }
+ return size;
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
new file mode 100644
index 0000000..ed43407
--- /dev/null
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothGattCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop
+ * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by
+ * {@link AdvertisementData}.
+ * <p>
+ * To get an instance of {@link BluetoothLeAdvertiser}, call the
+ * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
+ * <p>
+ * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see AdvertisementData
+ */
+public final class BluetoothLeAdvertiser {
+
+ private static final String TAG = "BluetoothLeAdvertiser";
+
+ private final IBluetoothGatt mBluetoothGatt;
+ private final Handler mHandler;
+ private final Map<AdvertiseCallback, AdvertiseCallbackWrapper>
+ mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>();
+
+ /**
+ * Use BluetoothAdapter.getLeAdvertiser() instead.
+ *
+ * @param bluetoothGatt
+ * @hide
+ */
+ public BluetoothLeAdvertiser(IBluetoothGatt bluetoothGatt) {
+ mBluetoothGatt = bluetoothGatt;
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ /**
+ * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
+ * operation succeeds. Returns immediately, the operation status are delivered through
+ * {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param settings Settings for Bluetooth LE advertising.
+ * @param advertiseData Advertisement data to be broadcasted.
+ * @param callback Callback for advertising status.
+ */
+ public void startAdvertising(AdvertiseSettings settings,
+ AdvertisementData advertiseData, final AdvertiseCallback callback) {
+ startAdvertising(settings, advertiseData, null, callback);
+ }
+
+ /**
+ * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
+ * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends
+ * active scan request. Method returns immediately, the operation status are delivered through
+ * {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ *
+ * @param settings Settings for Bluetooth LE advertising.
+ * @param advertiseData Advertisement data to be advertised in advertisement packet.
+ * @param scanResponse Scan response associated with the advertisement data.
+ * @param callback Callback for advertising status.
+ */
+ public void startAdvertising(AdvertiseSettings settings,
+ AdvertisementData advertiseData, AdvertisementData scanResponse,
+ final AdvertiseCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (mLeAdvertisers.containsKey(callback)) {
+ postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
+ return;
+ }
+ AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
+ scanResponse, settings, mBluetoothGatt);
+ UUID uuid = UUID.randomUUID();
+ try {
+ mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
+ if (wrapper.advertiseStarted()) {
+ mLeAdvertisers.put(callback, wrapper);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to stop advertising", e);
+ }
+ }
+
+ /**
+ * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
+ * {@link BluetoothLeAdvertiser#startAdvertising}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param callback {@link AdvertiseCallback} for delivering stopping advertising status.
+ */
+ public void stopAdvertising(final AdvertiseCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
+ if (wrapper == null) {
+ postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED);
+ return;
+ }
+ try {
+ mBluetoothGatt.stopMultiAdvertising(wrapper.mLeHandle);
+ if (wrapper.advertiseStopped()) {
+ mLeAdvertisers.remove(callback);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to stop advertising", e);
+ }
+ }
+
+ /**
+ * Bluetooth GATT interface callbacks for advertising.
+ */
+ private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
+ private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
+ private final AdvertiseCallback mAdvertiseCallback;
+ private final AdvertisementData mAdvertisement;
+ private final AdvertisementData mScanResponse;
+ private final AdvertiseSettings mSettings;
+ private final IBluetoothGatt mBluetoothGatt;
+
+ // mLeHandle 0: not registered
+ // -1: scan stopped
+ // >0: registered and scan started
+ private int mLeHandle;
+ private boolean isAdvertising = false;
+
+ public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
+ AdvertisementData advertiseData, AdvertisementData scanResponse,
+ AdvertiseSettings settings,
+ IBluetoothGatt bluetoothGatt) {
+ mAdvertiseCallback = advertiseCallback;
+ mAdvertisement = advertiseData;
+ mScanResponse = scanResponse;
+ mSettings = settings;
+ mBluetoothGatt = bluetoothGatt;
+ mLeHandle = 0;
+ }
+
+ public boolean advertiseStarted() {
+ boolean started = false;
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ return false;
+ }
+ try {
+ wait(LE_CALLBACK_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: ", e);
+ }
+ started = (mLeHandle > 0 && isAdvertising);
+ }
+ return started;
+ }
+
+ public boolean advertiseStopped() {
+ synchronized (this) {
+ try {
+ wait(LE_CALLBACK_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ return !isAdvertising;
+ }
+ }
+
+ /**
+ * Application interface registered - app is ready to go
+ */
+ @Override
+ public void onClientRegistered(int status, int clientIf) {
+ Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf);
+ synchronized (this) {
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mLeHandle = clientIf;
+ try {
+ mBluetoothGatt.startMultiAdvertising(mLeHandle, mAdvertisement,
+ mScanResponse, mSettings);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to start le advertise: " + e);
+ mLeHandle = -1;
+ notifyAll();
+ } catch (Exception e) {
+ Log.e(TAG, "fail to start advertise: " + e.getStackTrace());
+ }
+ } else {
+ // registration failed
+ mLeHandle = -1;
+ notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ // no op
+ }
+
+ @Override
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ // no op
+ }
+
+ @Override
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ // no op
+ }
+
+ @Override
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descUuid) {
+ // no op
+ }
+
+ @Override
+ public void onSearchComplete(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ // no op
+ }
+
+ @Override
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid) {
+ // no op
+ }
+
+ @Override
+ public void onExecuteWrite(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ // no op
+ }
+
+ @Override
+ public void onAdvertiseStateChange(int advertiseState, int status) {
+ // no op
+ }
+
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ synchronized (this) {
+ if (status == 0) {
+ isAdvertising = !isAdvertising;
+ if (!isAdvertising) {
+ try {
+ mBluetoothGatt.unregisterClient(mLeHandle);
+ mLeHandle = -1;
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote exception when unregistering", e);
+ }
+ }
+ mAdvertiseCallback.onSuccess(null);
+ } else {
+ mAdvertiseCallback.onFailure(status);
+ }
+ notifyAll();
+ }
+
+ }
+
+ /**
+ * Callback reporting LE ATT MTU.
+ *
+ * @hide
+ */
+ @Override
+ public void onConfigureMTU(String address, int mtu, int status) {
+ // no op
+ }
+ }
+
+ private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onFailure(error);
+ }
+ });
+ }
+}
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
new file mode 100644
index 0000000..4c6346c
--- /dev/null
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.bluetooth.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothGattCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * This class provides methods to perform scan related operations for Bluetooth LE devices. An
+ * application can scan for a particular type of BLE devices using {@link ScanFilter}. It can also
+ * request different types of callbacks for delivering the result.
+ * <p>
+ * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
+ * {@link BluetoothLeScanner}.
+ * <p>
+ * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see ScanFilter
+ */
+public final class BluetoothLeScanner {
+
+ private static final String TAG = "BluetoothLeScanner";
+ private static final boolean DBG = true;
+
+ private final IBluetoothGatt mBluetoothGatt;
+ private final Handler mHandler;
+ private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
+
+ /**
+ * @hide
+ */
+ public BluetoothLeScanner(IBluetoothGatt bluetoothGatt) {
+ mBluetoothGatt = bluetoothGatt;
+ mHandler = new Handler(Looper.getMainLooper());
+ mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
+ }
+
+ /**
+ * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param filters {@link ScanFilter}s for finding exact BLE devices.
+ * @param settings Settings for ble scan.
+ * @param callback Callback when scan results are delivered.
+ * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
+ */
+ public void startScan(List<ScanFilter> filters, ScanSettings settings,
+ final ScanCallback callback) {
+ if (settings == null || callback == null) {
+ throw new IllegalArgumentException("settings or callback is null");
+ }
+ synchronized (mLeScanClients) {
+ if (mLeScanClients.containsKey(callback)) {
+ postCallbackError(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
+ return;
+ }
+ BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(mBluetoothGatt, filters,
+ settings, callback);
+ try {
+ UUID uuid = UUID.randomUUID();
+ mBluetoothGatt.registerClient(new ParcelUuid(uuid), wrapper);
+ if (wrapper.scanStarted()) {
+ mLeScanClients.put(callback, wrapper);
+ } else {
+ postCallbackError(callback,
+ ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "GATT service exception when starting scan", e);
+ postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE);
+ }
+ }
+ }
+
+ /**
+ * Stops an ongoing Bluetooth LE scan.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param callback
+ */
+ public void stopScan(ScanCallback callback) {
+ synchronized (mLeScanClients) {
+ BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
+ if (wrapper == null) {
+ return;
+ }
+ wrapper.stopLeScan();
+ }
+ }
+
+ /**
+ * Returns available storage size for batch scan results. It's recommended not to use batch scan
+ * if available storage size is small (less than 1k bytes, for instance).
+ *
+ * @hide TODO: unhide when batching is supported in stack.
+ */
+ public int getAvailableBatchStorageSizeBytes() {
+ throw new UnsupportedOperationException("not impelemented");
+ }
+
+ /**
+ * Poll scan results from bluetooth controller. This will return Bluetooth LE scan results
+ * batched on bluetooth controller.
+ *
+ * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
+ * used to start scan.
+ * @param flush Whether to flush the batch scan buffer. Note the other batch scan clients will
+ * get batch scan callback if the batch scan buffer is flushed.
+ * @return Batch Scan results.
+ * @hide TODO: unhide when batching is supported in stack.
+ */
+ public List<ScanResult> getBatchScanResults(ScanCallback callback, boolean flush) {
+ throw new UnsupportedOperationException("not impelemented");
+ }
+
+ /**
+ * Bluetooth GATT interface callbacks
+ */
+ private static class BleScanCallbackWrapper extends IBluetoothGattCallback.Stub {
+ private static final int REGISTRATION_CALLBACK_TIMEOUT_SECONDS = 5;
+
+ private final ScanCallback mScanCallback;
+ private final List<ScanFilter> mFilters;
+ private ScanSettings mSettings;
+ private IBluetoothGatt mBluetoothGatt;
+
+ // mLeHandle 0: not registered
+ // -1: scan stopped
+ // > 0: registered and scan started
+ private int mLeHandle;
+
+ public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
+ List<ScanFilter> filters, ScanSettings settings,
+ ScanCallback scanCallback) {
+ mBluetoothGatt = bluetoothGatt;
+ mFilters = filters;
+ mSettings = settings;
+ mScanCallback = scanCallback;
+ mLeHandle = 0;
+ }
+
+ public boolean scanStarted() {
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ return false;
+ }
+ try {
+ wait(REGISTRATION_CALLBACK_TIMEOUT_SECONDS);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Callback reg wait interrupted: " + e);
+ }
+ }
+ return mLeHandle > 0;
+ }
+
+ public void stopLeScan() {
+ synchronized (this) {
+ if (mLeHandle <= 0) {
+ Log.e(TAG, "Error state, mLeHandle: " + mLeHandle);
+ return;
+ }
+ try {
+ mBluetoothGatt.stopScan(mLeHandle, false);
+ mBluetoothGatt.unregisterClient(mLeHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to stop scan and unregister" + e);
+ }
+ mLeHandle = -1;
+ notifyAll();
+ }
+ }
+
+ /**
+ * Application interface registered - app is ready to go
+ */
+ @Override
+ public void onClientRegistered(int status, int clientIf) {
+ Log.d(TAG, "onClientRegistered() - status=" + status +
+ " clientIf=" + clientIf);
+
+ synchronized (this) {
+ if (mLeHandle == -1) {
+ if (DBG)
+ Log.d(TAG, "onClientRegistered LE scan canceled");
+ }
+
+ if (status == BluetoothGatt.GATT_SUCCESS) {
+ mLeHandle = clientIf;
+ try {
+ mBluetoothGatt.startScanWithFilters(mLeHandle, false, mSettings, mFilters);
+ } catch (RemoteException e) {
+ Log.e(TAG, "fail to start le scan: " + e);
+ mLeHandle = -1;
+ }
+ } else {
+ // registration failed
+ mLeHandle = -1;
+ }
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onClientConnectionState(int status, int clientIf,
+ boolean connected, String address) {
+ // no op
+ }
+
+ /**
+ * Callback reporting an LE scan result.
+ *
+ * @hide
+ */
+ @Override
+ public void onScanResult(String address, int rssi, byte[] advData) {
+ if (DBG)
+ Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" + rssi);
+
+ // Check null in case the scan has been stopped
+ synchronized (this) {
+ if (mLeHandle <= 0)
+ return;
+ }
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+ address);
+ long scanNanos = SystemClock.elapsedRealtimeNanos();
+ ScanResult result = new ScanResult(device, advData, rssi,
+ scanNanos);
+ mScanCallback.onAdvertisementUpdate(result);
+ }
+
+ @Override
+ public void onGetService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetIncludedService(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int inclSrvcType, int inclSrvcInstId,
+ ParcelUuid inclSrvcUuid) {
+ // no op
+ }
+
+ @Override
+ public void onGetCharacteristic(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int charProps) {
+ // no op
+ }
+
+ @Override
+ public void onGetDescriptor(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descUuid) {
+ // no op
+ }
+
+ @Override
+ public void onSearchComplete(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onCharacteristicWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid) {
+ // no op
+ }
+
+ @Override
+ public void onNotify(String address, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorRead(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid, byte[] value) {
+ // no op
+ }
+
+ @Override
+ public void onDescriptorWrite(String address, int status, int srvcType,
+ int srvcInstId, ParcelUuid srvcUuid,
+ int charInstId, ParcelUuid charUuid,
+ int descInstId, ParcelUuid descrUuid) {
+ // no op
+ }
+
+ @Override
+ public void onExecuteWrite(String address, int status) {
+ // no op
+ }
+
+ @Override
+ public void onReadRemoteRssi(String address, int rssi, int status) {
+ // no op
+ }
+
+ @Override
+ public void onAdvertiseStateChange(int advertiseState, int status) {
+ // no op
+ }
+
+ @Override
+ public void onMultiAdvertiseCallback(int status) {
+ // no op
+ }
+
+ @Override
+ public void onConfigureMTU(String address, int mtu, int status) {
+ // no op
+ }
+ }
+
+ private void postCallbackError(final ScanCallback callback, final int errorCode) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onScanFailed(errorCode);
+ }
+ });
+ }
+}
diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java
new file mode 100644
index 0000000..50ebf50
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanCallback.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import java.util.List;
+
+/**
+ * Callback of Bluetooth LE scans. The results of the scans will be delivered through the callbacks.
+ */
+public abstract class ScanCallback {
+
+ /**
+ * Fails to start scan as BLE scan with the same settings is already started by the app.
+ */
+ public static final int SCAN_FAILED_ALREADY_STARTED = 1;
+ /**
+ * Fails to start scan as app cannot be registered.
+ */
+ public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2;
+ /**
+ * Fails to start scan due to gatt service failure.
+ */
+ public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3;
+ /**
+ * Fails to start scan due to controller failure.
+ */
+ public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4;
+
+ /**
+ * Callback when a BLE advertisement is found.
+ *
+ * @param result A Bluetooth LE scan result.
+ */
+ public abstract void onAdvertisementUpdate(ScanResult result);
+
+ /**
+ * Callback when the BLE advertisement is found for the first time.
+ *
+ * @param result The Bluetooth LE scan result when the onFound event is triggered.
+ * @hide
+ */
+ public abstract void onAdvertisementFound(ScanResult result);
+
+ /**
+ * Callback when the BLE advertisement was lost. Note a device has to be "found" before it's
+ * lost.
+ *
+ * @param result The Bluetooth scan result that was last found.
+ * @hide
+ */
+ public abstract void onAdvertisementLost(ScanResult result);
+
+ /**
+ * Callback when batch results are delivered.
+ *
+ * @param results List of scan results that are previously scanned.
+ * @hide
+ */
+ public abstract void onBatchScanResults(List<ScanResult> results);
+
+ /**
+ * Callback when scan failed.
+ */
+ public abstract void onScanFailed(int errorCode);
+}
diff --git a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl b/core/java/android/bluetooth/le/ScanFilter.aidl
index 4aa8881..4cecfe6 100644
--- a/core/java/android/bluetooth/BluetoothLeAdvertiseScanData.aidl
+++ b/core/java/android/bluetooth/le/ScanFilter.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
-parcelable BluetoothLeAdvertiseScanData.AdvertisementData; \ No newline at end of file
+parcelable ScanFilter;
diff --git a/core/java/android/bluetooth/BluetoothLeScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index 2ed85ba..c2e316b 100644
--- a/core/java/android/bluetooth/BluetoothLeScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
import android.annotation.Nullable;
-import android.bluetooth.BluetoothLeAdvertiseScanData.ScanRecord;
-import android.bluetooth.BluetoothLeScanner.ScanResult;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
@@ -29,8 +29,7 @@ import java.util.Objects;
import java.util.UUID;
/**
- * {@link BluetoothLeScanFilter} abstracts different scan filters across Bluetooth Advertisement
- * packet fields.
+ * {@link ScanFilter} abstracts different scan filters across Bluetooth Advertisement packet fields.
* <p>
* Current filtering on the following fields are supported:
* <li>Service UUIDs which identify the bluetooth gatt services running on the device.
@@ -40,10 +39,10 @@ import java.util.UUID;
* <li>Service data which is the data associated with a service.
* <li>Manufacturer specific data which is the data associated with a particular manufacturer.
*
- * @see BluetoothLeAdvertiseScanData.ScanRecord
+ * @see ScanRecord
* @see BluetoothLeScanner
*/
-public final class BluetoothLeScanFilter implements Parcelable {
+public final class ScanFilter implements Parcelable {
@Nullable
private final String mLocalName;
@@ -70,7 +69,7 @@ public final class BluetoothLeScanFilter implements Parcelable {
private final int mMinRssi;
private final int mMaxRssi;
- private BluetoothLeScanFilter(String name, String macAddress, ParcelUuid uuid,
+ private ScanFilter(String name, String macAddress, ParcelUuid uuid,
ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask,
int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
int minRssi, int maxRssi) {
@@ -105,88 +104,93 @@ public final class BluetoothLeScanFilter implements Parcelable {
dest.writeInt(mServiceUuid == null ? 0 : 1);
if (mServiceUuid != null) {
dest.writeParcelable(mServiceUuid, flags);
- }
- dest.writeInt(mServiceUuidMask == null ? 0 : 1);
- if (mServiceUuidMask != null) {
- dest.writeParcelable(mServiceUuidMask, flags);
+ dest.writeInt(mServiceUuidMask == null ? 0 : 1);
+ if (mServiceUuidMask != null) {
+ dest.writeParcelable(mServiceUuidMask, flags);
+ }
}
dest.writeInt(mServiceData == null ? 0 : mServiceData.length);
if (mServiceData != null) {
dest.writeByteArray(mServiceData);
- }
- dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length);
- if (mServiceDataMask != null) {
- dest.writeByteArray(mServiceDataMask);
+ dest.writeInt(mServiceDataMask == null ? 0 : mServiceDataMask.length);
+ if (mServiceDataMask != null) {
+ dest.writeByteArray(mServiceDataMask);
+ }
}
dest.writeInt(mManufacturerId);
dest.writeInt(mManufacturerData == null ? 0 : mManufacturerData.length);
if (mManufacturerData != null) {
dest.writeByteArray(mManufacturerData);
- }
- dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length);
- if (mManufacturerDataMask != null) {
- dest.writeByteArray(mManufacturerDataMask);
+ dest.writeInt(mManufacturerDataMask == null ? 0 : mManufacturerDataMask.length);
+ if (mManufacturerDataMask != null) {
+ dest.writeByteArray(mManufacturerDataMask);
+ }
}
dest.writeInt(mMinRssi);
dest.writeInt(mMaxRssi);
}
/**
- * A {@link android.os.Parcelable.Creator} to create {@link BluetoothLeScanFilter} form parcel.
+ * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} form parcel.
*/
- public static final Creator<BluetoothLeScanFilter>
- CREATOR = new Creator<BluetoothLeScanFilter>() {
+ public static final Creator<ScanFilter>
+ CREATOR = new Creator<ScanFilter>() {
@Override
- public BluetoothLeScanFilter[] newArray(int size) {
- return new BluetoothLeScanFilter[size];
+ public ScanFilter[] newArray(int size) {
+ return new ScanFilter[size];
}
@Override
- public BluetoothLeScanFilter createFromParcel(Parcel in) {
- Builder builder = newBuilder();
+ public ScanFilter createFromParcel(Parcel in) {
+ Builder builder = new Builder();
if (in.readInt() == 1) {
- builder.name(in.readString());
+ builder.setName(in.readString());
}
if (in.readInt() == 1) {
- builder.macAddress(in.readString());
+ builder.setMacAddress(in.readString());
}
if (in.readInt() == 1) {
ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
- builder.serviceUuid(uuid);
- }
- if (in.readInt() == 1) {
- ParcelUuid uuidMask = in.readParcelable(ParcelUuid.class.getClassLoader());
- builder.serviceUuidMask(uuidMask);
+ builder.setServiceUuid(uuid);
+ if (in.readInt() == 1) {
+ ParcelUuid uuidMask = in.readParcelable(
+ ParcelUuid.class.getClassLoader());
+ builder.setServiceUuid(uuid, uuidMask);
+ }
}
+
int serviceDataLength = in.readInt();
if (serviceDataLength > 0) {
byte[] serviceData = new byte[serviceDataLength];
in.readByteArray(serviceData);
- builder.serviceData(serviceData);
- }
- int serviceDataMaskLength = in.readInt();
- if (serviceDataMaskLength > 0) {
- byte[] serviceDataMask = new byte[serviceDataMaskLength];
- in.readByteArray(serviceDataMask);
- builder.serviceDataMask(serviceDataMask);
+ builder.setServiceData(serviceData);
+ int serviceDataMaskLength = in.readInt();
+ if (serviceDataMaskLength > 0) {
+ byte[] serviceDataMask = new byte[serviceDataMaskLength];
+ in.readByteArray(serviceDataMask);
+ builder.setServiceData(serviceData, serviceDataMask);
+ }
}
+
int manufacturerId = in.readInt();
int manufacturerDataLength = in.readInt();
if (manufacturerDataLength > 0) {
byte[] manufacturerData = new byte[manufacturerDataLength];
in.readByteArray(manufacturerData);
- builder.manufacturerData(manufacturerId, manufacturerData);
- }
- int manufacturerDataMaskLength = in.readInt();
- if (manufacturerDataMaskLength > 0) {
- byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
- in.readByteArray(manufacturerDataMask);
- builder.manufacturerDataMask(manufacturerDataMask);
+ builder.setManufacturerData(manufacturerId, manufacturerData);
+ int manufacturerDataMaskLength = in.readInt();
+ if (manufacturerDataMaskLength > 0) {
+ byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
+ in.readByteArray(manufacturerDataMask);
+ builder.setManufacturerData(manufacturerId, manufacturerData,
+ manufacturerDataMask);
+ }
}
+
int minRssi = in.readInt();
int maxRssi = in.readInt();
- builder.rssiRange(minRssi, maxRssi);
+ builder.setRssiRange(minRssi, maxRssi);
return builder.build();
}
};
@@ -199,9 +203,10 @@ public final class BluetoothLeScanFilter implements Parcelable {
return mLocalName;
}
- @Nullable /**
- * Returns the filter set on the service uuid.
- */
+ /**
+ * Returns the filter set on the service uuid.
+ */
+ @Nullable
public ParcelUuid getServiceUuid() {
return mServiceUuid;
}
@@ -277,7 +282,7 @@ public final class BluetoothLeScanFilter implements Parcelable {
}
byte[] scanRecordBytes = scanResult.getScanRecord();
- ScanRecord scanRecord = ScanRecord.getParser().parseFromScanRecord(scanRecordBytes);
+ ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordBytes);
// Scan record is null but there exist filters on it.
if (scanRecord == null
@@ -386,13 +391,13 @@ public final class BluetoothLeScanFilter implements Parcelable {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
- BluetoothLeScanFilter other = (BluetoothLeScanFilter) obj;
+ ScanFilter other = (ScanFilter) obj;
return Objects.equals(mLocalName, other.mLocalName) &&
Objects.equals(mMacAddress, other.mMacAddress) &&
- mManufacturerId == other.mManufacturerId &&
+ mManufacturerId == other.mManufacturerId &&
Objects.deepEquals(mManufacturerData, other.mManufacturerData) &&
Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) &&
- mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi &&
+ mMinRssi == other.mMinRssi && mMaxRssi == other.mMaxRssi &&
Objects.deepEquals(mServiceData, other.mServiceData) &&
Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) &&
Objects.equals(mServiceUuid, other.mServiceUuid) &&
@@ -400,17 +405,9 @@ public final class BluetoothLeScanFilter implements Parcelable {
}
/**
- * Returns the {@link Builder} for {@link BluetoothLeScanFilter}.
+ * Builder class for {@link ScanFilter}.
*/
- public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- * Builder class for {@link BluetoothLeScanFilter}. Use
- * {@link BluetoothLeScanFilter#newBuilder()} to get an instance of the {@link Builder}.
- */
- public static class Builder {
+ public static final class Builder {
private String mLocalName;
private String mMacAddress;
@@ -428,27 +425,23 @@ public final class BluetoothLeScanFilter implements Parcelable {
private int mMinRssi = Integer.MIN_VALUE;
private int mMaxRssi = Integer.MAX_VALUE;
- // Private constructor, use BluetoothLeScanFilter.newBuilder instead.
- private Builder() {
- }
-
/**
- * Set filtering on local name.
+ * Set filter on local name.
*/
- public Builder name(String localName) {
+ public Builder setName(String localName) {
mLocalName = localName;
return this;
}
/**
- * Set filtering on device mac address.
+ * Set filter on device mac address.
*
* @param macAddress The device mac address for the filter. It needs to be in the format of
* "01:02:03:AB:CD:EF". The mac address can be validated using
* {@link BluetoothAdapter#checkBluetoothAddress}.
* @throws IllegalArgumentException If the {@code macAddress} is invalid.
*/
- public Builder macAddress(String macAddress) {
+ public Builder setMacAddress(String macAddress) {
if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) {
throw new IllegalArgumentException("invalid mac address " + macAddress);
}
@@ -457,68 +450,115 @@ public final class BluetoothLeScanFilter implements Parcelable {
}
/**
- * Set filtering on service uuid.
+ * Set filter on service uuid.
*/
- public Builder serviceUuid(ParcelUuid serviceUuid) {
+ public Builder setServiceUuid(ParcelUuid serviceUuid) {
mServiceUuid = serviceUuid;
+ mUuidMask = null; // clear uuid mask
return this;
}
/**
- * Set partial uuid filter. The {@code uuidMask} is the bit mask for the {@code uuid} set
- * through {@link #serviceUuid(ParcelUuid)} method. Set any bit in the mask to 1 to indicate
- * a match is needed for the bit in {@code serviceUuid}, and 0 to ignore that bit.
- * <p>
- * The length of {@code uuidMask} must be the same as {@code serviceUuid}.
+ * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
+ * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
+ * bit in {@code serviceUuid}, and 0 to ignore that bit.
+ *
+ * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but
+ * {@code uuidMask} is not {@code null}.
*/
- public Builder serviceUuidMask(ParcelUuid uuidMask) {
+ public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
+ if (mUuidMask != null && mServiceUuid == null) {
+ throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
+ }
+ mServiceUuid = serviceUuid;
mUuidMask = uuidMask;
return this;
}
/**
- * Set service data filter.
+ * Set filtering on service data.
*/
- public Builder serviceData(byte[] serviceData) {
+ public Builder setServiceData(byte[] serviceData) {
mServiceData = serviceData;
+ mServiceDataMask = null; // clear service data mask
return this;
}
/**
- * Set partial service data filter bit mask. For any bit in the mask, set it to 1 if it
- * needs to match the one in service data, otherwise set it to 0 to ignore that bit.
+ * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
+ * match the one in service data, otherwise set it to 0 to ignore that bit.
* <p>
- * The {@code serviceDataMask} must have the same length of the {@code serviceData} set
- * through {@link #serviceData(byte[])}.
+ * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
+ *
+ * @throws IllegalArgumentException If {@code serviceDataMask} is {@code null} while
+ * {@code serviceData} is not or {@code serviceDataMask} and {@code serviceData}
+ * has different length.
*/
- public Builder serviceDataMask(byte[] serviceDataMask) {
+ public Builder setServiceData(byte[] serviceData, byte[] serviceDataMask) {
+ if (mServiceDataMask != null) {
+ if (mServiceData == null) {
+ throw new IllegalArgumentException(
+ "serviceData is null while serviceDataMask is not null");
+ }
+ // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
+ // byte array need to be the same.
+ if (mServiceData.length != mServiceDataMask.length) {
+ throw new IllegalArgumentException(
+ "size mismatch for service data and service data mask");
+ }
+ }
+ mServiceData = serviceData;
mServiceDataMask = serviceDataMask;
return this;
}
/**
- * Set manufacturerId and manufacturerData. A negative manufacturerId is considered as
- * invalid id.
+ * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
* <p>
* Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
+ *
+ * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
*/
- public Builder manufacturerData(int manufacturerId, byte[] manufacturerData) {
+ public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
if (manufacturerData != null && manufacturerId < 0) {
throw new IllegalArgumentException("invalid manufacture id");
}
mManufacturerId = manufacturerId;
mManufacturerData = manufacturerData;
+ mManufacturerDataMask = null; // clear manufacturer data mask
return this;
}
/**
- * Set partial manufacture data filter bit mask. For any bit in the mask, set it the 1 if it
+ * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it
* needs to match the one in manufacturer data, otherwise set it to 0.
* <p>
- * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}
- * set through {@link #manufacturerData(int, byte[])}.
+ * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
+ *
+ * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or
+ * {@code manufacturerData} is null while {@code manufacturerDataMask} is not,
+ * or {@code manufacturerData} and {@code manufacturerDataMask} have different
+ * length.
*/
- public Builder manufacturerDataMask(byte[] manufacturerDataMask) {
+ public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
+ byte[] manufacturerDataMask) {
+ if (manufacturerData != null && manufacturerId < 0) {
+ throw new IllegalArgumentException("invalid manufacture id");
+ }
+ if (mManufacturerDataMask != null) {
+ if (mManufacturerData == null) {
+ throw new IllegalArgumentException(
+ "manufacturerData is null while manufacturerDataMask is not null");
+ }
+ // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
+ // of the two byte array need to be the same.
+ if (mManufacturerData.length != mManufacturerDataMask.length) {
+ throw new IllegalArgumentException(
+ "size mismatch for manufacturerData and manufacturerDataMask");
+ }
+ }
+ mManufacturerId = manufacturerId;
+ mManufacturerData = manufacturerData;
mManufacturerDataMask = manufacturerDataMask;
return this;
}
@@ -527,48 +567,19 @@ public final class BluetoothLeScanFilter implements Parcelable {
* Set the desired rssi range for the filter. A scan result with rssi in the range of
* [minRssi, maxRssi] will be consider as a match.
*/
- public Builder rssiRange(int minRssi, int maxRssi) {
+ public Builder setRssiRange(int minRssi, int maxRssi) {
mMinRssi = minRssi;
mMaxRssi = maxRssi;
return this;
}
/**
- * Build {@link BluetoothLeScanFilter}.
+ * Build {@link ScanFilter}.
*
* @throws IllegalArgumentException If the filter cannot be built.
*/
- public BluetoothLeScanFilter build() {
- if (mUuidMask != null && mServiceUuid == null) {
- throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
- }
-
- if (mServiceDataMask != null) {
- if (mServiceData == null) {
- throw new IllegalArgumentException(
- "serviceData is null while serviceDataMask is not null");
- }
- // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
- // byte array need to be the same.
- if (mServiceData.length != mServiceDataMask.length) {
- throw new IllegalArgumentException(
- "size mismatch for service data and service data mask");
- }
- }
-
- if (mManufacturerDataMask != null) {
- if (mManufacturerData == null) {
- throw new IllegalArgumentException(
- "manufacturerData is null while manufacturerDataMask is not null");
- }
- // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
- // of the two byte array need to be the same.
- if (mManufacturerData.length != mManufacturerDataMask.length) {
- throw new IllegalArgumentException(
- "size mismatch for manufacturerData and manufacturerDataMask");
- }
- }
- return new BluetoothLeScanFilter(mLocalName, mMacAddress,
+ public ScanFilter build() {
+ return new ScanFilter(mLocalName, mMacAddress,
mServiceUuid, mUuidMask,
mServiceData, mServiceDataMask,
mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi);
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
new file mode 100644
index 0000000..bd7304b
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a scan record from Bluetooth LE scan.
+ */
+public final class ScanRecord {
+
+ private static final String TAG = "ScanRecord";
+
+ // The following data type values are assigned by Bluetooth SIG.
+ // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
+ private static final int DATA_TYPE_FLAGS = 0x01;
+ private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
+ private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
+ private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
+ private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
+ private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
+ private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
+ private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
+ private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
+ private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
+ private static final int DATA_TYPE_SERVICE_DATA = 0x16;
+ private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
+
+ // Flags of the advertising data.
+ private final int mAdvertiseFlags;
+
+ @Nullable
+ private final List<ParcelUuid> mServiceUuids;
+
+ private final int mManufacturerId;
+ @Nullable
+ private final byte[] mManufacturerSpecificData;
+
+ @Nullable
+ private final ParcelUuid mServiceDataUuid;
+ @Nullable
+ private final byte[] mServiceData;
+
+ // Transmission power level(in dB).
+ private final int mTxPowerLevel;
+
+ // Local name of the Bluetooth LE device.
+ private final String mLocalName;
+
+ /**
+ * Returns the advertising flags indicating the discoverable mode and capability of the device.
+ * Returns -1 if the flag field is not set.
+ */
+ public int getAdvertiseFlags() {
+ return mAdvertiseFlags;
+ }
+
+ /**
+ * Returns a list of service uuids within the advertisement that are used to identify the
+ * bluetooth gatt services.
+ */
+ public List<ParcelUuid> getServiceUuids() {
+ return mServiceUuids;
+ }
+
+ /**
+ * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth
+ * SIG.
+ */
+ public int getManufacturerId() {
+ return mManufacturerId;
+ }
+
+ /**
+ * Returns the manufacturer specific data which is the content of manufacturer specific data
+ * field. The first 2 bytes of the data contain the company id.
+ */
+ public byte[] getManufacturerSpecificData() {
+ return mManufacturerSpecificData;
+ }
+
+ /**
+ * Returns a 16 bit uuid of the service that the service data is associated with.
+ */
+ public ParcelUuid getServiceDataUuid() {
+ return mServiceDataUuid;
+ }
+
+ /**
+ * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
+ * service data.
+ */
+ public byte[] getServiceData() {
+ return mServiceData;
+ }
+
+ /**
+ * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
+ * if the field is not set. This value can be used to calculate the path loss of a received
+ * packet using the following equation:
+ * <p>
+ * <code>pathloss = txPowerLevel - rssi</code>
+ */
+ public int getTxPowerLevel() {
+ return mTxPowerLevel;
+ }
+
+ /**
+ * Returns the local name of the BLE device. The is a UTF-8 encoded string.
+ */
+ @Nullable
+ public String getLocalName() {
+ return mLocalName;
+ }
+
+ private ScanRecord(List<ParcelUuid> serviceUuids,
+ ParcelUuid serviceDataUuid, byte[] serviceData,
+ int manufacturerId,
+ byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel,
+ String localName) {
+ mServiceUuids = serviceUuids;
+ mManufacturerId = manufacturerId;
+ mManufacturerSpecificData = manufacturerSpecificData;
+ mServiceDataUuid = serviceDataUuid;
+ mServiceData = serviceData;
+ mLocalName = localName;
+ mAdvertiseFlags = advertiseFlags;
+ mTxPowerLevel = txPowerLevel;
+ }
+
+ @Override
+ public String toString() {
+ return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
+ + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
+ + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
+ + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
+ + ", mTxPowerLevel=" + mTxPowerLevel + ", mLocalName=" + mLocalName + "]";
+ }
+
+ /**
+ * Parse scan record bytes to {@link ScanRecord}.
+ * <p>
+ * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
+ * <p>
+ * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
+ * order.
+ *
+ * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
+ */
+ public static ScanRecord parseFromBytes(byte[] scanRecord) {
+ if (scanRecord == null) {
+ return null;
+ }
+
+ int currentPos = 0;
+ int advertiseFlag = -1;
+ List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
+ String localName = null;
+ int txPowerLevel = Integer.MIN_VALUE;
+ ParcelUuid serviceDataUuid = null;
+ byte[] serviceData = null;
+ int manufacturerId = -1;
+ byte[] manufacturerSpecificData = null;
+
+ try {
+ while (currentPos < scanRecord.length) {
+ // length is unsigned int.
+ int length = scanRecord[currentPos++] & 0xFF;
+ if (length == 0) {
+ break;
+ }
+ // Note the length includes the length of the field type itself.
+ int dataLength = length - 1;
+ // fieldType is unsigned int.
+ int fieldType = scanRecord[currentPos++] & 0xFF;
+ switch (fieldType) {
+ case DATA_TYPE_FLAGS:
+ advertiseFlag = scanRecord[currentPos] & 0xFF;
+ break;
+ case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
+ case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
+ parseServiceUuid(scanRecord, currentPos,
+ dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
+ break;
+ case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
+ case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
+ parseServiceUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
+ break;
+ case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
+ case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
+ parseServiceUuid(scanRecord, currentPos, dataLength,
+ BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
+ break;
+ case DATA_TYPE_LOCAL_NAME_SHORT:
+ case DATA_TYPE_LOCAL_NAME_COMPLETE:
+ localName = new String(
+ extractBytes(scanRecord, currentPos, dataLength));
+ break;
+ case DATA_TYPE_TX_POWER_LEVEL:
+ txPowerLevel = scanRecord[currentPos];
+ break;
+ case DATA_TYPE_SERVICE_DATA:
+ serviceData = extractBytes(scanRecord, currentPos, dataLength);
+ // The first two bytes of the service data are service data uuid.
+ int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
+ byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
+ serviceUuidLength);
+ serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes);
+ break;
+ case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
+ manufacturerSpecificData = extractBytes(scanRecord, currentPos,
+ dataLength);
+ // The first two bytes of the manufacturer specific data are
+ // manufacturer ids in little endian.
+ manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) +
+ (manufacturerSpecificData[0] & 0xFF);
+ break;
+ default:
+ // Just ignore, we don't handle such data type.
+ break;
+ }
+ currentPos += dataLength;
+ }
+
+ if (serviceUuids.isEmpty()) {
+ serviceUuids = null;
+ }
+ return new ScanRecord(serviceUuids, serviceDataUuid, serviceData,
+ manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel,
+ localName);
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
+ return null;
+ }
+ }
+
+ // Parse service uuids.
+ private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
+ int uuidLength, List<ParcelUuid> serviceUuids) {
+ while (dataLength > 0) {
+ byte[] uuidBytes = extractBytes(scanRecord, currentPos,
+ uuidLength);
+ serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
+ dataLength -= uuidLength;
+ currentPos += uuidLength;
+ }
+ return currentPos;
+ }
+
+ // Helper method to extract bytes from byte array.
+ private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
+ byte[] bytes = new byte[length];
+ System.arraycopy(scanRecord, start, bytes, 0, length);
+ return bytes;
+ }
+}
diff --git a/core/java/android/tv/TvInputInfo.aidl b/core/java/android/bluetooth/le/ScanResult.aidl
index abc4b47..3943035 100644
--- a/core/java/android/tv/TvInputInfo.aidl
+++ b/core/java/android/bluetooth/le/ScanResult.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.tv;
+package android.bluetooth.le;
-parcelable TvInputInfo;
+parcelable ScanResult; \ No newline at end of file
diff --git a/core/java/android/bluetooth/le/ScanResult.java b/core/java/android/bluetooth/le/ScanResult.java
new file mode 100644
index 0000000..7e6e8f8
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanResult.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * ScanResult for Bluetooth LE scan.
+ */
+public final class ScanResult implements Parcelable {
+ // Remote bluetooth device.
+ private BluetoothDevice mDevice;
+
+ // Scan record, including advertising data and scan response data.
+ private byte[] mScanRecord;
+
+ // Received signal strength.
+ private int mRssi;
+
+ // Device timestamp when the result was last seen.
+ private long mTimestampNanos;
+
+ /**
+ * Constructor of scan result.
+ *
+ * @hide
+ */
+ public ScanResult(BluetoothDevice device, byte[] scanRecord, int rssi,
+ long timestampNanos) {
+ mDevice = device;
+ mScanRecord = scanRecord;
+ mRssi = rssi;
+ mTimestampNanos = timestampNanos;
+ }
+
+ private ScanResult(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (mDevice != null) {
+ dest.writeInt(1);
+ mDevice.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
+ if (mScanRecord != null) {
+ dest.writeInt(1);
+ dest.writeByteArray(mScanRecord);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeInt(mRssi);
+ dest.writeLong(mTimestampNanos);
+ }
+
+ private void readFromParcel(Parcel in) {
+ if (in.readInt() == 1) {
+ mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() == 1) {
+ mScanRecord = in.createByteArray();
+ }
+ mRssi = in.readInt();
+ mTimestampNanos = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the remote bluetooth device identified by the bluetooth device address.
+ */
+ @Nullable
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
+ * Returns the scan record, which can be a combination of advertisement and scan response.
+ */
+ @Nullable
+ public byte[] getScanRecord() {
+ return mScanRecord;
+ }
+
+ /**
+ * Returns the received signal strength in dBm. The valid range is [-127, 127].
+ */
+ public int getRssi() {
+ return mRssi;
+ }
+
+ /**
+ * Returns timestamp since boot when the scan record was observed.
+ */
+ public long getTimestampNanos() {
+ return mTimestampNanos;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ ScanResult other = (ScanResult) obj;
+ return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi) &&
+ Objects.deepEquals(mScanRecord, other.mScanRecord)
+ && (mTimestampNanos == other.mTimestampNanos);
+ }
+
+ @Override
+ public String toString() {
+ return "ScanResult{" + "mDevice=" + mDevice + ", mScanRecord="
+ + Arrays.toString(mScanRecord) + ", mRssi=" + mRssi + ", mTimestampNanos="
+ + mTimestampNanos + '}';
+ }
+
+ public static final Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
+ @Override
+ public ScanResult createFromParcel(Parcel source) {
+ return new ScanResult(source);
+ }
+
+ @Override
+ public ScanResult[] newArray(int size) {
+ return new ScanResult[size];
+ }
+ };
+
+}
diff --git a/core/java/android/bluetooth/le/ScanSettings.aidl b/core/java/android/bluetooth/le/ScanSettings.aidl
new file mode 100644
index 0000000..eb169c1
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanSettings.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+parcelable ScanSettings;
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
new file mode 100644
index 0000000..0a85675
--- /dev/null
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Settings for Bluetooth LE scan.
+ */
+public final class ScanSettings implements Parcelable {
+ /**
+ * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the
+ * least power.
+ */
+ public static final int SCAN_MODE_LOW_POWER = 0;
+ /**
+ * Perform Bluetooth LE scan in balanced power mode.
+ */
+ public static final int SCAN_MODE_BALANCED = 1;
+ /**
+ * Scan using highest duty cycle. It's recommended only using this mode when the application is
+ * running in foreground.
+ */
+ public static final int SCAN_MODE_LOW_LATENCY = 2;
+
+ /**
+ * Callback each time when a bluetooth advertisement is found.
+ */
+ public static final int CALLBACK_TYPE_ON_UPDATE = 0;
+ /**
+ * Callback when a bluetooth advertisement is found for the first time.
+ *
+ * @hide
+ */
+ public static final int CALLBACK_TYPE_ON_FOUND = 1;
+ /**
+ * Callback when a bluetooth advertisement is found for the first time, then lost.
+ *
+ * @hide
+ */
+ public static final int CALLBACK_TYPE_ON_LOST = 2;
+
+ /**
+ * Full scan result which contains device mac address, rssi, advertising and scan response and
+ * scan timestamp.
+ */
+ public static final int SCAN_RESULT_TYPE_FULL = 0;
+ /**
+ * Truncated scan result which contains device mac address, rssi and scan timestamp. Note it's
+ * possible for an app to get more scan results that it asks if there are multiple apps using
+ * this type. TODO: decide whether we could unhide this setting.
+ *
+ * @hide
+ */
+ public static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
+
+ // Bluetooth LE scan mode.
+ private int mScanMode;
+
+ // Bluetooth LE scan callback type
+ private int mCallbackType;
+
+ // Bluetooth LE scan result type
+ private int mScanResultType;
+
+ // Time of delay for reporting the scan result
+ private long mReportDelayNanos;
+
+ public int getScanMode() {
+ return mScanMode;
+ }
+
+ public int getCallbackType() {
+ return mCallbackType;
+ }
+
+ public int getScanResultType() {
+ return mScanResultType;
+ }
+
+ /**
+ * Returns report delay timestamp based on the device clock.
+ */
+ public long getReportDelayNanos() {
+ return mReportDelayNanos;
+ }
+
+ private ScanSettings(int scanMode, int callbackType, int scanResultType,
+ long reportDelayNanos) {
+ mScanMode = scanMode;
+ mCallbackType = callbackType;
+ mScanResultType = scanResultType;
+ mReportDelayNanos = reportDelayNanos;
+ }
+
+ private ScanSettings(Parcel in) {
+ mScanMode = in.readInt();
+ mCallbackType = in.readInt();
+ mScanResultType = in.readInt();
+ mReportDelayNanos = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mScanMode);
+ dest.writeInt(mCallbackType);
+ dest.writeInt(mScanResultType);
+ dest.writeLong(mReportDelayNanos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<ScanSettings>
+ CREATOR = new Creator<ScanSettings>() {
+ @Override
+ public ScanSettings[] newArray(int size) {
+ return new ScanSettings[size];
+ }
+
+ @Override
+ public ScanSettings createFromParcel(Parcel in) {
+ return new ScanSettings(in);
+ }
+ };
+
+ /**
+ * Builder for {@link ScanSettings}.
+ */
+ public static final class Builder {
+ private int mScanMode = SCAN_MODE_LOW_POWER;
+ private int mCallbackType = CALLBACK_TYPE_ON_UPDATE;
+ private int mScanResultType = SCAN_RESULT_TYPE_FULL;
+ private long mReportDelayNanos = 0;
+
+ /**
+ * Set scan mode for Bluetooth LE scan.
+ *
+ * @param scanMode The scan mode can be one of
+ * {@link ScanSettings#SCAN_MODE_LOW_POWER},
+ * {@link ScanSettings#SCAN_MODE_BALANCED} or
+ * {@link ScanSettings#SCAN_MODE_LOW_LATENCY}.
+ * @throws IllegalArgumentException If the {@code scanMode} is invalid.
+ */
+ public Builder setScanMode(int scanMode) {
+ if (scanMode < SCAN_MODE_LOW_POWER || scanMode > SCAN_MODE_LOW_LATENCY) {
+ throw new IllegalArgumentException("invalid scan mode " + scanMode);
+ }
+ mScanMode = scanMode;
+ return this;
+ }
+
+ /**
+ * Set callback type for Bluetooth LE scan.
+ *
+ * @param callbackType The callback type for the scan. Can only be
+ * {@link ScanSettings#CALLBACK_TYPE_ON_UPDATE}.
+ * @throws IllegalArgumentException If the {@code callbackType} is invalid.
+ */
+ public Builder setCallbackType(int callbackType) {
+ if (callbackType < CALLBACK_TYPE_ON_UPDATE
+ || callbackType > CALLBACK_TYPE_ON_LOST) {
+ throw new IllegalArgumentException("invalid callback type - " + callbackType);
+ }
+ mCallbackType = callbackType;
+ return this;
+ }
+
+ /**
+ * Set scan result type for Bluetooth LE scan.
+ *
+ * @param scanResultType Type for scan result, could be either
+ * {@link ScanSettings#SCAN_RESULT_TYPE_FULL} or
+ * {@link ScanSettings#SCAN_RESULT_TYPE_TRUNCATED}.
+ * @throws IllegalArgumentException If the {@code scanResultType} is invalid.
+ * @hide
+ */
+ public Builder setScanResultType(int scanResultType) {
+ if (scanResultType < SCAN_RESULT_TYPE_FULL
+ || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) {
+ throw new IllegalArgumentException(
+ "invalid scanResultType - " + scanResultType);
+ }
+ mScanResultType = scanResultType;
+ return this;
+ }
+
+ /**
+ * Set report delay timestamp for Bluetooth LE scan.
+ */
+ public Builder setReportDelayNanos(long reportDelayNanos) {
+ mReportDelayNanos = reportDelayNanos;
+ return this;
+ }
+
+ /**
+ * Build {@link ScanSettings}.
+ */
+ public ScanSettings build() {
+ return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
+ mReportDelayNanos);
+ }
+ }
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b0673b5..a040efb 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@ import android.os.Looper;
import android.os.StatFs;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.MediaStore;
import android.util.AttributeSet;
import android.view.DisplayAdjustments;
import android.view.Display;
@@ -929,6 +930,40 @@ public abstract class Context {
public abstract File[] getExternalCacheDirs();
/**
+ * Returns absolute paths to application-specific directories on all
+ * external storage devices where the application can place media files.
+ * These files are scanned and made available to other apps through
+ * {@link MediaStore}.
+ * <p>
+ * This is like {@link #getExternalFilesDirs} in that these files will be
+ * deleted when the application is uninstalled, however there are some
+ * important differences:
+ * <ul>
+ * <li>External files are not always available: they will disappear if the
+ * user mounts the external storage on a computer or removes it.
+ * <li>There is no security enforced with these files.
+ * </ul>
+ * <p>
+ * External storage devices returned here are considered a permanent part of
+ * the device, including both emulated external storage and physical media
+ * slots, such as SD cards in a battery compartment. The returned paths do
+ * not include transient devices, such as USB flash drives.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
+ * example, an app may choose to store large files on the device with the
+ * most available space, as measured by {@link StatFs}.
+ * <p>
+ * No permissions are required to read or write to the returned paths; they
+ * are always accessible to the calling app. Write access outside of these
+ * paths on secondary external storage devices is not available.
+ * <p>
+ * Returned paths may be {@code null} if a storage device is unavailable.
+ *
+ * @see Environment#getExternalStorageState(File)
+ */
+ public abstract File[] getExternalMediaDirs();
+
+ /**
* Returns an array of strings naming the private files associated with
* this Context's application package.
*
@@ -2363,6 +2398,7 @@ public abstract class Context {
*
* @see #getSystemService
* @see android.net.wifi.passpoint.WifiPasspointManager
+ * @hide
*/
public static final String WIFI_PASSPOINT_SERVICE = "wifipasspoint";
@@ -2659,6 +2695,15 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.RestrictionsManager} for retrieving application restrictions
+ * and requesting permissions for restricted operations.
+ * @see #getSystemService
+ * @see android.content.RestrictionsManager
+ */
+ public static final String RESTRICTIONS_SERVICE = "restrictions";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.AppOpsManager} for tracking application operations
* on the device.
*
@@ -2706,11 +2751,11 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
- * {@link android.tv.TvInputManager} for interacting with TV inputs on the
- * device.
+ * {@link android.media.tv.TvInputManager} for interacting with TV inputs
+ * on the device.
*
* @see #getSystemService
- * @see android.tv.TvInputManager
+ * @see android.media.tv.TvInputManager
*/
public static final String TV_INPUT_SERVICE = "tv_input";
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index c66355b..dbf9122 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -237,6 +237,11 @@ public class ContextWrapper extends Context {
}
@Override
+ public File[] getExternalMediaDirs() {
+ return mBase.getExternalMediaDirs();
+ }
+
+ @Override
public File getDir(String name, int mode) {
return mBase.getDir(name, mode);
}
diff --git a/core/java/android/tv/ITvInputHardwareCallback.aidl b/core/java/android/content/IRestrictionsManager.aidl
index 83041be..b1c0a3a 100644
--- a/core/java/android/tv/ITvInputHardwareCallback.aidl
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-package android.tv;
+package android.content;
-import android.tv.TvStreamConfig;
+import android.os.Bundle;
/**
+ * Interface used by the RestrictionsManager
* @hide
*/
-oneway interface ITvInputHardwareCallback {
- void onReleased();
- void onStreamConfigChanged(in TvStreamConfig[] configs);
+interface IRestrictionsManager {
+ Bundle getApplicationRestrictions(in String packageName);
+ boolean hasRestrictionsProvider();
+ void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
+ void notifyPermissionResponse(in String packageName, in Bundle response);
}
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 3ff53bf..62f88a9 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -73,32 +73,38 @@ public class RestrictionEntry implements Parcelable {
*/
public static final int TYPE_MULTI_SELECT = 4;
+ /**
+ * A type of restriction. Use this for storing an integer value. The range of values
+ * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
+ */
+ public static final int TYPE_INTEGER = 5;
+
/** The type of restriction. */
- private int type;
+ private int mType;
/** The unique key that identifies the restriction. */
- private String key;
+ private String mKey;
/** The user-visible title of the restriction. */
- private String title;
+ private String mTitle;
/** The user-visible secondary description of the restriction. */
- private String description;
+ private String mDescription;
/** The user-visible set of choices used for single-select and multi-select lists. */
- private String [] choices;
+ private String [] mChoiceEntries;
/** The values corresponding to the user-visible choices. The value(s) of this entry will
* one or more of these, returned by {@link #getAllSelectedStrings()} and
* {@link #getSelectedString()}.
*/
- private String [] values;
+ private String [] mChoiceValues;
/* The chosen value, whose content depends on the type of the restriction. */
- private String currentValue;
+ private String mCurrentValue;
/* List of selected choices in the multi-select case. */
- private String[] currentValues;
+ private String[] mCurrentValues;
/**
* Constructor for {@link #TYPE_CHOICE} type.
@@ -106,9 +112,9 @@ public class RestrictionEntry implements Parcelable {
* @param selectedString the current value
*/
public RestrictionEntry(String key, String selectedString) {
- this.key = key;
- this.type = TYPE_CHOICE;
- this.currentValue = selectedString;
+ this.mKey = key;
+ this.mType = TYPE_CHOICE;
+ this.mCurrentValue = selectedString;
}
/**
@@ -117,8 +123,8 @@ public class RestrictionEntry implements Parcelable {
* @param selectedState whether this restriction is selected or not
*/
public RestrictionEntry(String key, boolean selectedState) {
- this.key = key;
- this.type = TYPE_BOOLEAN;
+ this.mKey = key;
+ this.mType = TYPE_BOOLEAN;
setSelectedState(selectedState);
}
@@ -128,9 +134,20 @@ public class RestrictionEntry implements Parcelable {
* @param selectedStrings the list of values that are currently selected
*/
public RestrictionEntry(String key, String[] selectedStrings) {
- this.key = key;
- this.type = TYPE_MULTI_SELECT;
- this.currentValues = selectedStrings;
+ this.mKey = key;
+ this.mType = TYPE_MULTI_SELECT;
+ this.mCurrentValues = selectedStrings;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_INTEGER} type.
+ * @param key the unique key for this restriction
+ * @param selectedInt the integer value of the restriction
+ */
+ public RestrictionEntry(String key, int selectedInt) {
+ mKey = key;
+ mType = TYPE_INTEGER;
+ setIntValue(selectedInt);
}
/**
@@ -138,7 +155,7 @@ public class RestrictionEntry implements Parcelable {
* @param type the type for this restriction.
*/
public void setType(int type) {
- this.type = type;
+ this.mType = type;
}
/**
@@ -146,7 +163,7 @@ public class RestrictionEntry implements Parcelable {
* @return the type for this restriction
*/
public int getType() {
- return type;
+ return mType;
}
/**
@@ -155,7 +172,7 @@ public class RestrictionEntry implements Parcelable {
* single string values.
*/
public String getSelectedString() {
- return currentValue;
+ return mCurrentValue;
}
/**
@@ -164,7 +181,7 @@ public class RestrictionEntry implements Parcelable {
* null otherwise.
*/
public String[] getAllSelectedStrings() {
- return currentValues;
+ return mCurrentValues;
}
/**
@@ -172,7 +189,23 @@ public class RestrictionEntry implements Parcelable {
* @return the current selected state of the entry.
*/
public boolean getSelectedState() {
- return Boolean.parseBoolean(currentValue);
+ return Boolean.parseBoolean(mCurrentValue);
+ }
+
+ /**
+ * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}.
+ * @return the integer value of the entry.
+ */
+ public int getIntValue() {
+ return Integer.parseInt(mCurrentValue);
+ }
+
+ /**
+ * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}.
+ * @param value the integer value to set.
+ */
+ public void setIntValue(int value) {
+ mCurrentValue = Integer.toString(value);
}
/**
@@ -181,7 +214,7 @@ public class RestrictionEntry implements Parcelable {
* @param selectedString the string value to select.
*/
public void setSelectedString(String selectedString) {
- currentValue = selectedString;
+ mCurrentValue = selectedString;
}
/**
@@ -190,7 +223,7 @@ public class RestrictionEntry implements Parcelable {
* @param state the current selected state
*/
public void setSelectedState(boolean state) {
- currentValue = Boolean.toString(state);
+ mCurrentValue = Boolean.toString(state);
}
/**
@@ -199,7 +232,7 @@ public class RestrictionEntry implements Parcelable {
* @param allSelectedStrings the current list of selected values.
*/
public void setAllSelectedStrings(String[] allSelectedStrings) {
- currentValues = allSelectedStrings;
+ mCurrentValues = allSelectedStrings;
}
/**
@@ -216,7 +249,7 @@ public class RestrictionEntry implements Parcelable {
* @see #getAllSelectedStrings()
*/
public void setChoiceValues(String[] choiceValues) {
- values = choiceValues;
+ mChoiceValues = choiceValues;
}
/**
@@ -227,7 +260,7 @@ public class RestrictionEntry implements Parcelable {
* @see #setChoiceValues(String[])
*/
public void setChoiceValues(Context context, int stringArrayResId) {
- values = context.getResources().getStringArray(stringArrayResId);
+ mChoiceValues = context.getResources().getStringArray(stringArrayResId);
}
/**
@@ -235,7 +268,7 @@ public class RestrictionEntry implements Parcelable {
* @return the list of possible values.
*/
public String[] getChoiceValues() {
- return values;
+ return mChoiceValues;
}
/**
@@ -248,7 +281,7 @@ public class RestrictionEntry implements Parcelable {
* @see #setChoiceValues(String[])
*/
public void setChoiceEntries(String[] choiceEntries) {
- choices = choiceEntries;
+ mChoiceEntries = choiceEntries;
}
/** Sets a list of strings that will be presented as choices to the user. This is similar to
@@ -257,7 +290,7 @@ public class RestrictionEntry implements Parcelable {
* @param stringArrayResId the resource id of a string array containing the possible entries.
*/
public void setChoiceEntries(Context context, int stringArrayResId) {
- choices = context.getResources().getStringArray(stringArrayResId);
+ mChoiceEntries = context.getResources().getStringArray(stringArrayResId);
}
/**
@@ -265,7 +298,7 @@ public class RestrictionEntry implements Parcelable {
* @return the list of choices presented to the user.
*/
public String[] getChoiceEntries() {
- return choices;
+ return mChoiceEntries;
}
/**
@@ -273,7 +306,7 @@ public class RestrictionEntry implements Parcelable {
* @return the user-visible description, null if none was set earlier.
*/
public String getDescription() {
- return description;
+ return mDescription;
}
/**
@@ -283,7 +316,7 @@ public class RestrictionEntry implements Parcelable {
* @param description the user-visible description string.
*/
public void setDescription(String description) {
- this.description = description;
+ this.mDescription = description;
}
/**
@@ -291,7 +324,7 @@ public class RestrictionEntry implements Parcelable {
* @return the key for the restriction.
*/
public String getKey() {
- return key;
+ return mKey;
}
/**
@@ -299,7 +332,7 @@ public class RestrictionEntry implements Parcelable {
* @return the user-visible title for the entry, null if none was set earlier.
*/
public String getTitle() {
- return title;
+ return mTitle;
}
/**
@@ -307,7 +340,7 @@ public class RestrictionEntry implements Parcelable {
* @param title the user-visible title for the entry.
*/
public void setTitle(String title) {
- this.title = title;
+ this.mTitle = title;
}
private boolean equalArrays(String[] one, String[] other) {
@@ -324,23 +357,23 @@ public class RestrictionEntry implements Parcelable {
if (!(o instanceof RestrictionEntry)) return false;
final RestrictionEntry other = (RestrictionEntry) o;
// Make sure that either currentValue matches or currentValues matches.
- return type == other.type && key.equals(other.key)
+ return mType == other.mType && mKey.equals(other.mKey)
&&
- ((currentValues == null && other.currentValues == null
- && currentValue != null && currentValue.equals(other.currentValue))
+ ((mCurrentValues == null && other.mCurrentValues == null
+ && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue))
||
- (currentValue == null && other.currentValue == null
- && currentValues != null && equalArrays(currentValues, other.currentValues)));
+ (mCurrentValue == null && other.mCurrentValue == null
+ && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues)));
}
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + key.hashCode();
- if (currentValue != null) {
- result = 31 * result + currentValue.hashCode();
- } else if (currentValues != null) {
- for (String value : currentValues) {
+ result = 31 * result + mKey.hashCode();
+ if (mCurrentValue != null) {
+ result = 31 * result + mCurrentValue.hashCode();
+ } else if (mCurrentValues != null) {
+ for (String value : mCurrentValues) {
if (value != null) {
result = 31 * result + value.hashCode();
}
@@ -359,14 +392,14 @@ public class RestrictionEntry implements Parcelable {
}
public RestrictionEntry(Parcel in) {
- type = in.readInt();
- key = in.readString();
- title = in.readString();
- description = in.readString();
- choices = readArray(in);
- values = readArray(in);
- currentValue = in.readString();
- currentValues = readArray(in);
+ mType = in.readInt();
+ mKey = in.readString();
+ mTitle = in.readString();
+ mDescription = in.readString();
+ mChoiceEntries = readArray(in);
+ mChoiceValues = readArray(in);
+ mCurrentValue = in.readString();
+ mCurrentValues = readArray(in);
}
@Override
@@ -387,14 +420,14 @@ public class RestrictionEntry implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(type);
- dest.writeString(key);
- dest.writeString(title);
- dest.writeString(description);
- writeArray(dest, choices);
- writeArray(dest, values);
- dest.writeString(currentValue);
- writeArray(dest, currentValues);
+ dest.writeInt(mType);
+ dest.writeString(mKey);
+ dest.writeString(mTitle);
+ dest.writeString(mDescription);
+ writeArray(dest, mChoiceEntries);
+ writeArray(dest, mChoiceValues);
+ dest.writeString(mCurrentValue);
+ writeArray(dest, mCurrentValues);
}
public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
@@ -409,6 +442,6 @@ public class RestrictionEntry implements Parcelable {
@Override
public String toString() {
- return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+ return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}";
}
}
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
new file mode 100644
index 0000000..0dd0edd
--- /dev/null
+++ b/core/java/android/content/RestrictionsManager.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2014 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.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for apps to query restrictions imposed by an entity that
+ * manages the user. Apps can also send permission requests to a local or remote
+ * device administrator to override default app-specific restrictions or any other
+ * operation that needs explicit authorization from the administrator.
+ * <p>
+ * Apps can expose a set of restrictions via a runtime receiver mechanism or via
+ * static meta data in the manifest.
+ * <p>
+ * If the user has an active restrictions provider, dynamic requests can be made in
+ * addition to the statically imposed restrictions. Dynamic requests are app-specific
+ * and can be expressed via a predefined set of templates.
+ * <p>
+ * The RestrictionsManager forwards the dynamic requests to the active
+ * restrictions provider. The restrictions provider can respond back to requests by calling
+ * {@link #notifyPermissionResponse(String, Bundle)}, when
+ * a response is received from the administrator of the device or user
+ * The response is relayed back to the application via a protected broadcast,
+ * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
+ * <p>
+ * Static restrictions are specified by an XML file referenced by a meta-data attribute
+ * in the manifest. This enables applications as well as any web administration consoles
+ * to be able to read the template from the apk.
+ * <p>
+ * The syntax of the XML format is as follows:
+ * <pre>
+ * &lt;restrictions&gt;
+ * &lt;restriction
+ * android:key="&lt;key&gt;"
+ * android:restrictionType="boolean|string|integer|multi-select|null"
+ * ... /&gt;
+ * &lt;restriction ... /&gt;
+ * &lt;/restrictions&gt;
+ * </pre>
+ * <p>
+ * The attributes for each restriction depend on the restriction type.
+ *
+ * @see RestrictionEntry
+ */
+public class RestrictionsManager {
+
+ /**
+ * Broadcast intent delivered when a response is received for a permission
+ * request. The response is not available for later query, so the receiver
+ * must persist and/or immediately act upon the response. The application
+ * should not interrupt the user by coming to the foreground if it isn't
+ * currently in the foreground. It can post a notification instead, informing
+ * the user of a change in state.
+ * <p>
+ * For instance, if the user requested permission to make an in-app purchase,
+ * the app can post a notification that the request had been granted or denied,
+ * and allow the purchase to go through.
+ * <p>
+ * The broadcast Intent carries the following extra:
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ */
+ public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
+ "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+
+ /**
+ * Protected broadcast intent sent to the active restrictions provider. The intent
+ * contains the following extras:<p>
+ * <ul>
+ * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting
+ * permission.</li>
+ * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li>
+ * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values
+ * for the request.
+ * </ul>
+ * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
+ * @see #requestPermission(String, String, Bundle)
+ */
+ public static final String ACTION_REQUEST_PERMISSION =
+ "android.intent.action.REQUEST_PERMISSION";
+
+ /**
+ * The package name of the application making the request.
+ */
+ public static final String EXTRA_PACKAGE_NAME = "package_name";
+
+ /**
+ * The template id that specifies what kind of a request it is and may indicate
+ * how the request is to be presented to the administrator. Must be either one of
+ * the predefined templates or a custom one specified by the application that the
+ * restrictions provider is familiar with.
+ */
+ public static final String EXTRA_TEMPLATE_ID = "template_id";
+
+ /**
+ * A bundle containing the details about the request. The contents depend on the
+ * template id.
+ * @see #EXTRA_TEMPLATE_ID
+ */
+ public static final String EXTRA_REQUEST_BUNDLE = "request_bundle";
+
+ /**
+ * Contains a response from the administrator for specific request.
+ * The bundle contains the following information, at least:
+ * <ul>
+ * <li>{@link #REQUEST_KEY_ID}: The request id.</li>
+ * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li>
+ * </ul>
+ * <p>
+ * And depending on what the request template was, the bundle will contain the actual
+ * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in
+ * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator
+ * approved the request, false otherwise.
+ */
+ public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle";
+
+
+ /**
+ * Request template that presents a simple question, with a possible title and icon.
+ * <p>
+ * Required keys are
+ * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}.
+ * <p>
+ * Optional keys are
+ * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
+ * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
+ */
+ public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple";
+
+ /**
+ * Key for request ID contained in the request bundle.
+ * <p>
+ * App-generated request id to identify the specific request when receiving
+ * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_ID = "android.req_template.req_id";
+
+ /**
+ * Key for request data contained in the request bundle.
+ * <p>
+ * Optional, typically used to identify the specific data that is being referred to,
+ * such as the unique identifier for a movie or book. This is not used for display
+ * purposes and is more like a cookie. This value is returned in the
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DATA = "android.req_template.data";
+
+ /**
+ * Key for request title contained in the request bundle.
+ * <p>
+ * Optional, typically used as the title of any notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_TITLE = "android.req_template.title";
+
+ /**
+ * Key for request message contained in the request bundle.
+ * <p>
+ * Required, shown as the actual message in a notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+
+ /**
+ * Key for request icon contained in the request bundle.
+ * <p>
+ * Optional, shown alongside the request message presented to the administrator
+ * who approves the request.
+ * <p>
+ * Type: Bitmap
+ */
+ public static final String REQUEST_KEY_ICON = "android.req_template.icon";
+
+ /**
+ * Key for request approval button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the positive button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+
+ /**
+ * Key for request rejection button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the negative button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+
+ /**
+ * Key for requestor's name contained in the request bundle. This value is not specified by
+ * the application. It is automatically inserted into the Bundle by the Restrictions Provider
+ * before it is sent to the administrator.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+
+ /**
+ * Key for requestor's device name contained in the request bundle. This value is not specified
+ * by the application. It is automatically inserted into the Bundle by the Restrictions Provider
+ * before it is sent to the administrator.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+
+ /**
+ * Key for the response in the response bundle sent to the application, for a permission
+ * request.
+ * <p>
+ * Type: boolean
+ */
+ public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+
+ private static final String TAG = "RestrictionsManager";
+
+ private final Context mContext;
+ private final IRestrictionsManager mService;
+
+ /**
+ * @hide
+ */
+ public RestrictionsManager(Context context, IRestrictionsManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns any available set of application-specific restrictions applicable
+ * to this application.
+ * @return
+ */
+ public Bundle getApplicationRestrictions() {
+ try {
+ if (mService != null) {
+ return mService.getApplicationRestrictions(mContext.getPackageName());
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ return null;
+ }
+
+ /**
+ * Called by an application to check if permission requests can be made. If false,
+ * there is no need to request permission for an operation, unless a static
+ * restriction applies to that operation.
+ * @return
+ */
+ public boolean hasRestrictionsProvider() {
+ try {
+ if (mService != null) {
+ return mService.hasRestrictionsProvider();
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ return false;
+ }
+
+ /**
+ * Called by an application to request permission for an operation. The contents of the
+ * request are passed in a Bundle that contains several pieces of data depending on the
+ * chosen request template.
+ *
+ * @param requestTemplate The request template to use. The template could be one of the
+ * predefined templates specified in this class or a custom template that the specific
+ * Restrictions Provider might understand. For custom templates, the template name should be
+ * namespaced to avoid collisions with predefined templates and templates specified by
+ * other Restrictions Provider vendors.
+ * @param requestData A Bundle containing the data corresponding to the specified request
+ * template. The keys for the data in the bundle depend on the kind of template chosen.
+ */
+ public void requestPermission(String requestTemplate, Bundle requestData) {
+ try {
+ if (mService != null) {
+ mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData);
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ }
+
+ /**
+ * Called by the Restrictions Provider when a response is available to be
+ * delivered to an application.
+ * @param packageName
+ * @param response
+ */
+ public void notifyPermissionResponse(String packageName, Bundle response) {
+ try {
+ if (mService != null) {
+ mService.notifyPermissionResponse(packageName, response);
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ }
+
+ /**
+ * Parse and return the list of restrictions defined in the manifest for the specified
+ * package, if any.
+ * @param packageName The application for which to fetch the restrictions list.
+ * @return The list of RestrictionEntry objects created from the XML file specified
+ * in the manifest, or null if none was specified.
+ */
+ public List<RestrictionEntry> getManifestRestrictions(String packageName) {
+ // TODO:
+ return null;
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 44a6a5d..70668e1 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -433,6 +433,13 @@ interface IPackageManager {
in VerificationParams verificationParams,
in ContainerEncryptionParams encryptionParams);
+ void installPackageWithVerificationEncryptionAndAbiOverrideEtc(in Uri packageURI,
+ in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2,
+ int flags, in String installerPackageName,
+ in VerificationParams verificationParams,
+ in ContainerEncryptionParams encryptionParams,
+ in String packageAbiOverride);
+
int installExistingPackageAsUser(String packageName, int userId);
void verifyPendingInstall(int id, int verificationCode);
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 9087338..5d48868 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.DisplayMetrics;
import android.util.Log;
/**
@@ -47,21 +48,22 @@ public class LauncherActivityInfo {
private ActivityInfo mActivityInfo;
private ComponentName mComponentName;
private UserHandle mUser;
- // TODO: Fetch this value from PM
private long mFirstInstallTime;
/**
* Create a launchable activity object for a given ResolveInfo and user.
- *
+ *
* @param context The context for fetching resources.
* @param info ResolveInfo from which to create the LauncherActivityInfo.
* @param user The UserHandle of the profile to which this activity belongs.
*/
- LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user) {
+ LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user,
+ long firstInstallTime) {
this(context);
- this.mActivityInfo = info.activityInfo;
- this.mComponentName = LauncherApps.getComponentName(info);
- this.mUser = user;
+ mActivityInfo = info.activityInfo;
+ mComponentName = LauncherApps.getComponentName(info);
+ mUser = user;
+ mFirstInstallTime = firstInstallTime;
}
LauncherActivityInfo(Context context) {
@@ -79,7 +81,13 @@ public class LauncherActivityInfo {
}
/**
- * Returns the user handle of the user profile that this activity belongs to.
+ * Returns the user handle of the user profile that this activity belongs to. In order to
+ * persist the identity of the profile, do not store the UserHandle. Instead retrieve its
+ * serial number from UserManager. You can convert the serial number back to a UserHandle
+ * for later use.
+ *
+ * @see UserManager#getSerialNumberForUser(UserHandle)
+ * @see UserManager#getUserForSerialNumber(long)
*
* @return The UserHandle of the profile.
*/
@@ -89,7 +97,7 @@ public class LauncherActivityInfo {
/**
* Retrieves the label for the activity.
- *
+ *
* @return The label for the activity.
*/
public CharSequence getLabel() {
@@ -98,8 +106,10 @@ public class LauncherActivityInfo {
/**
* Returns the icon for this activity, without any badging for the profile.
- * @param density The preferred density of the icon, zero for default density.
+ * @param density The preferred density of the icon, zero for default density. Use
+ * density DPI values from {@link DisplayMetrics}.
* @see #getBadgedIcon(int)
+ * @see DisplayMetrics
* @return The drawable associated with the activity
*/
public Drawable getIcon(int density) {
@@ -109,15 +119,25 @@ public class LauncherActivityInfo {
/**
* Returns the application flags from the ApplicationInfo of the activity.
- *
+ *
* @return Application flags
+ * @hide remove before shipping
*/
public int getApplicationFlags() {
return mActivityInfo.applicationInfo.flags;
}
/**
+ * Returns the application info for the appliction this activity belongs to.
+ * @return
+ */
+ public ApplicationInfo getApplicationInfo() {
+ return mActivityInfo.applicationInfo;
+ }
+
+ /**
* Returns the time at which the package was first installed.
+ *
* @return The time of installation of the package, in milliseconds.
*/
public long getFirstInstallTime() {
@@ -134,7 +154,9 @@ public class LauncherActivityInfo {
/**
* Returns the activity icon with badging appropriate for the profile.
- * @param density Optional density for the icon, or 0 to use the default density.
+ * @param density Optional density for the icon, or 0 to use the default density. Use
+ * {@link DisplayMetrics} for DPI values.
+ * @see DisplayMetrics
* @return A badged icon for the activity.
*/
public Drawable getBadgedIcon(int density) {
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 8025b60..04c0b9f 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -16,15 +16,18 @@
package android.content.pm;
+import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import java.util.ArrayList;
@@ -36,6 +39,12 @@ import java.util.List;
* managed profiles. This is mainly for use by launchers. Apps can be queried for each user profile.
* Since the PackageManager will not deliver package broadcasts for other profiles, you can register
* for package changes here.
+ * <p>
+ * To watch for managed profiles being added or removed, register for the following broadcasts:
+ * {@link Intent#ACTION_MANAGED_PROFILE_ADDED} and {@link Intent#ACTION_MANAGED_PROFILE_REMOVED}.
+ * <p>
+ * You can retrieve the list of profiles associated with this user with
+ * {@link UserManager#getUserProfiles()}.
*/
public class LauncherApps {
@@ -44,12 +53,13 @@ public class LauncherApps {
private Context mContext;
private ILauncherApps mService;
+ private PackageManager mPm;
private List<OnAppsChangedListener> mListeners
= new ArrayList<OnAppsChangedListener>();
/**
- * Callbacks for changes to this and related managed profiles.
+ * Callbacks for package changes to this and related managed profiles.
*/
public interface OnAppsChangedListener {
/**
@@ -57,6 +67,7 @@ public class LauncherApps {
*
* @param user The UserHandle of the profile that generated the change.
* @param packageName The name of the package that was removed.
+ * @hide remove before ship
*/
void onPackageRemoved(UserHandle user, String packageName);
@@ -65,6 +76,7 @@ public class LauncherApps {
*
* @param user The UserHandle of the profile that generated the change.
* @param packageName The name of the package that was added.
+ * @hide remove before ship
*/
void onPackageAdded(UserHandle user, String packageName);
@@ -73,6 +85,7 @@ public class LauncherApps {
*
* @param user The UserHandle of the profile that generated the change.
* @param packageName The name of the package that has changed.
+ * @hide remove before ship
*/
void onPackageChanged(UserHandle user, String packageName);
@@ -86,6 +99,7 @@ public class LauncherApps {
* available.
* @param replacing Indicates whether these packages are replacing
* existing ones.
+ * @hide remove before ship
*/
void onPackagesAvailable(UserHandle user, String[] packageNames, boolean replacing);
@@ -99,14 +113,66 @@ public class LauncherApps {
* unavailable.
* @param replacing Indicates whether the packages are about to be
* replaced with new versions.
+ * @hide remove before ship
*/
void onPackagesUnavailable(UserHandle user, String[] packageNames, boolean replacing);
+
+ /**
+ * Indicates that a package was removed from the specified profile.
+ *
+ * @param packageName The name of the package that was removed.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ void onPackageRemoved(String packageName, UserHandle user);
+
+ /**
+ * Indicates that a package was added to the specified profile.
+ *
+ * @param packageName The name of the package that was added.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ void onPackageAdded(String packageName, UserHandle user);
+
+ /**
+ * Indicates that a package was modified in the specified profile.
+ *
+ * @param packageName The name of the package that has changed.
+ * @param user The UserHandle of the profile that generated the change.
+ */
+ void onPackageChanged(String packageName, UserHandle user);
+
+ /**
+ * Indicates that one or more packages have become available. For
+ * example, this can happen when a removable storage card has
+ * reappeared.
+ *
+ * @param packageNames The names of the packages that have become
+ * available.
+ * @param user The UserHandle of the profile that generated the change.
+ * @param replacing Indicates whether these packages are replacing
+ * existing ones.
+ */
+ void onPackagesAvailable(String [] packageNames, UserHandle user, boolean replacing);
+
+ /**
+ * Indicates that one or more packages have become unavailable. For
+ * example, this can happen when a removable storage card has been
+ * removed.
+ *
+ * @param packageNames The names of the packages that have become
+ * unavailable.
+ * @param user The UserHandle of the profile that generated the change.
+ * @param replacing Indicates whether the packages are about to be
+ * replaced with new versions.
+ */
+ void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing);
}
/** @hide */
public LauncherApps(Context context, ILauncherApps service) {
mContext = context;
mService = service;
+ mPm = context.getPackageManager();
}
/**
@@ -131,7 +197,15 @@ public class LauncherApps {
final int count = activities.size();
for (int i = 0; i < count; i++) {
ResolveInfo ri = activities.get(i);
- LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user);
+ long firstInstallTime = 0;
+ try {
+ firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
+ } catch (NameNotFoundException nnfe) {
+ // Sorry, can't find package
+ }
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user,
+ firstInstallTime);
if (DEBUG) {
Log.v(TAG, "Returning activity for profile " + user + " : "
+ lai.getComponentName());
@@ -157,7 +231,15 @@ public class LauncherApps {
try {
ResolveInfo ri = mService.resolveActivity(intent, user);
if (ri != null) {
- LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user);
+ long firstInstallTime = 0;
+ try {
+ firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
+ } catch (NameNotFoundException nnfe) {
+ // Sorry, can't find package
+ }
+ LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user,
+ firstInstallTime);
return info;
}
} catch (RemoteException re) {
@@ -173,9 +255,23 @@ public class LauncherApps {
* @param sourceBounds The Rect containing the source bounds of the clicked icon
* @param opts Options to pass to startActivity
* @param user The UserHandle of the profile
+ * @hide remove before ship
*/
public void startActivityForProfile(ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) {
+ startActivityForProfile(component, user, sourceBounds, opts);
+ }
+
+ /**
+ * Starts an activity in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch
+ * @param user The UserHandle of the profile
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon
+ * @param opts Options to pass to startActivity
+ */
+ public void startActivityForProfile(ComponentName component, UserHandle user, Rect sourceBounds,
+ Bundle opts) {
if (DEBUG) {
Log.i(TAG, "StartActivityForProfile " + component + " " + user.getIdentifier());
}
@@ -224,13 +320,15 @@ public class LauncherApps {
*
* @param listener The listener to add.
*/
- public synchronized void addOnAppsChangedListener(OnAppsChangedListener listener) {
- if (listener != null && !mListeners.contains(listener)) {
- mListeners.add(listener);
- if (mListeners.size() == 1) {
- try {
- mService.addOnAppsChangedListener(mAppsChangedListener);
- } catch (RemoteException re) {
+ public void addOnAppsChangedListener(OnAppsChangedListener listener) {
+ synchronized (this) {
+ if (listener != null && !mListeners.contains(listener)) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ try {
+ mService.addOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
}
}
}
@@ -242,12 +340,14 @@ public class LauncherApps {
* @param listener The listener to remove.
* @see #addOnAppsChangedListener(OnAppsChangedListener)
*/
- public synchronized void removeOnAppsChangedListener(OnAppsChangedListener listener) {
- mListeners.remove(listener);
- if (mListeners.size() == 0) {
- try {
- mService.removeOnAppsChangedListener(mAppsChangedListener);
- } catch (RemoteException re) {
+ public void removeOnAppsChangedListener(OnAppsChangedListener listener) {
+ synchronized (this) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ try {
+ mService.removeOnAppsChangedListener(mAppsChangedListener);
+ } catch (RemoteException re) {
+ }
}
}
}
@@ -261,7 +361,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackageRemoved(user, packageName);
+ listener.onPackageRemoved(user, packageName); // TODO: Remove before ship
+ listener.onPackageRemoved(packageName, user);
}
}
}
@@ -273,7 +374,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackageChanged(user, packageName);
+ listener.onPackageChanged(user, packageName); // TODO: Remove before ship
+ listener.onPackageChanged(packageName, user);
}
}
}
@@ -285,7 +387,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackageAdded(user, packageName);
+ listener.onPackageAdded(user, packageName); // TODO: Remove before ship
+ listener.onPackageAdded(packageName, user);
}
}
}
@@ -298,7 +401,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackagesAvailable(user, packageNames, replacing);
+ listener.onPackagesAvailable(user, packageNames, replacing); // TODO: Remove
+ listener.onPackagesAvailable(packageNames, user, replacing);
}
}
}
@@ -311,7 +415,8 @@ public class LauncherApps {
}
synchronized (LauncherApps.this) {
for (OnAppsChangedListener listener : mListeners) {
- listener.onPackagesUnavailable(user, packageNames, replacing);
+ listener.onPackagesUnavailable(user, packageNames, replacing); // TODO: Remove
+ listener.onPackagesUnavailable(packageNames, user, replacing);
}
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1c838c3..ab8bf61 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2174,7 +2174,6 @@ public class PackageParser {
}
final int innerDepth = parser.getDepth();
-
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
@@ -2548,13 +2547,13 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
false)) {
a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
- if (a.info.exported) {
+ if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Activity exported request ignored due to singleUser: "
+ a.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
a.info.exported = false;
+ setExported = true;
}
- setExported = true;
}
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly,
@@ -2907,7 +2906,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
false)) {
p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
- if (p.info.exported) {
+ if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Provider exported request ignored due to singleUser: "
+ p.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
@@ -3181,13 +3180,13 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestService_singleUser,
false)) {
s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
- if (s.info.exported) {
+ if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Service exported request ignored due to singleUser: "
+ s.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
s.info.exported = false;
+ setExported = true;
}
- setExported = true;
}
sa.recycle();
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 3737638..ed3f9aa 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -702,12 +702,17 @@ public class Resources {
* Context.obtainStyledAttributes} with
* an array containing the resource ID of interest to create the TypedArray.</p>
*
+ * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use
+ * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)}
+ * or {@link #getDrawable(int, Theme)} passing the desired theme.</p>
+ *
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
* @return Drawable An object that can be used to draw this resource.
* @throws NotFoundException Throws NotFoundException if the given ID does
* not exist.
+ * @see #getDrawable(int, Theme)
*/
public Drawable getDrawable(int id) throws NotFoundException {
return getDrawable(id, null);
@@ -715,7 +720,9 @@ public class Resources {
/**
* Return a drawable object associated with a particular resource ID and
- * styled for the specified theme.
+ * styled for the specified theme. Various types of objects will be
+ * returned depending on the underlying resource -- for example, a solid
+ * color, PNG image, scalable image, etc.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
@@ -755,6 +762,11 @@ public class Resources {
* image, scalable image, etc. The Drawable API hides these implementation
* details.
*
+ * <p class="note"><strong>Note:</strong> To obtain a themed drawable, use
+ * {@link android.content.Context#getDrawable(int) Context.getDrawable(int)}
+ * or {@link #getDrawableForDensity(int, int, Theme)} passing the desired
+ * theme.</p>
+ *
* @param id The desired resource identifier, as generated by the aapt tool.
* This integer encodes the package, type, and resource entry.
* The value 0 is an invalid identifier.
@@ -2340,12 +2352,12 @@ public class Resources {
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
- dr = Drawable.createFromXmlThemed(this, rp, theme);
+ dr = Drawable.createFromXml(this, rp, theme);
rp.close();
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- dr = Drawable.createFromResourceStreamThemed(this, value, is, file, null, theme);
+ dr = Drawable.createFromResourceStream(this, value, is, file, null);
is.close();
}
} catch (Exception e) {
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index c593e9e..c8de2f1 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -142,9 +142,10 @@ public final class Sensor {
public static final String STRING_TYPE_TEMPERATURE = "android.sensor.temperature";
/**
- * A constant describing a proximity sensor type.
+ * A constant describing a proximity sensor type. This is a wake up sensor.
* <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
+ * @see #isWakeUpSensor()
*/
public static final int TYPE_PROXIMITY = 8;
@@ -307,8 +308,10 @@ public final class Sensor {
* itself. The sensor continues to operate while the device is asleep
* and will automatically wake the device to notify when significant
* motion is detected. The application does not need to hold any wake
- * locks for this sensor to trigger.
+ * locks for this sensor to trigger. This is a wake up sensor.
* <p>See {@link TriggerEvent} for more details.
+ *
+ * @see #isWakeUpSensor()
*/
public static final int TYPE_SIGNIFICANT_MOTION = 17;
@@ -381,11 +384,17 @@ public final class Sensor {
/**
* A constant describing a heart rate monitor.
* <p>
- * A sensor that measures the heart rate in beats per minute.
+ * The reported value is the heart rate in beats per minute.
+ * <p>
+ * The reported accuracy represents the status of the monitor during the reading. See the
+ * {@code SENSOR_STATUS_*} constants in {@link android.hardware.SensorManager SensorManager}
+ * for more details on accuracy/status values. In particular, when the accuracy is
+ * {@code SENSOR_STATUS_UNRELIABLE} or {@code SENSOR_STATUS_NO_CONTACT}, the heart rate
+ * value should be discarded.
* <p>
- * value[0] represents the beats per minute when the measurement was taken.
- * value[0] is 0 if the heart rate monitor could not measure the rate or the
- * rate is 0 beat per minute.
+ * This sensor requires permission {@code android.permission.BODY_SENSORS}.
+ * It will not be returned by {@code SensorManager.getSensorsList} nor
+ * {@code SensorManager.getDefaultSensor} if the application doesn't have this permission.
*/
public static final int TYPE_HEART_RATE = 21;
@@ -397,6 +406,321 @@ public final class Sensor {
public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate";
/**
+ * A non-wake up variant of proximity sensor.
+ *
+ * @see #TYPE_PROXIMITY
+ */
+ public static final int TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = 22;
+
+ /**
+ * A constant string describing a non-wake up proximity sensor type.
+ *
+ * @see #TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+ */
+ public static final String SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR =
+ "android.sensor.non_wake_up_proximity_sensor";
+
+ /**
+ * A constant describing a wake up variant of accelerometer sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ACCELEROMETER
+ */
+ public static final int TYPE_WAKE_UP_ACCELEROMETER = 23;
+
+ /**
+ * A constant string describing a wake up accelerometer.
+ *
+ * @see #TYPE_WAKE_UP_ACCELEROMETER
+ */
+ public static final String STRING_TYPE_WAKE_UP_ACCELEROMETER =
+ "android.sensor.wake_up_accelerometer";
+
+ /**
+ * A constant describing a wake up variant of a magnetic field sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_MAGNETIC_FIELD
+ */
+ public static final int TYPE_WAKE_UP_MAGNETIC_FIELD = 24;
+
+ /**
+ * A constant string describing a wake up magnetic field sensor.
+ *
+ * @see #TYPE_WAKE_UP_MAGNETIC_FIELD
+ */
+ public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD =
+ "android.sensor.wake_up_magnetic_field";
+
+ /**
+ * A constant describing a wake up variant of an orientation sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ORIENTATION
+ */
+ public static final int TYPE_WAKE_UP_ORIENTATION = 25;
+
+ /**
+ * A constant string describing a wake up orientation sensor.
+ *
+ * @see #TYPE_WAKE_UP_ORIENTATION
+ */
+ public static final String STRING_TYPE_WAKE_UP_ORIENTATION =
+ "android.sensor.wake_up_orientation";
+
+ /**
+ * A constant describing a wake up variant of a gyroscope sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GYROSCOPE
+ */
+ public static final int TYPE_WAKE_UP_GYROSCOPE = 26;
+
+ /**
+ * A constant string describing a wake up gyroscope sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GYROSCOPE
+ */
+ public static final String STRING_TYPE_WAKE_UP_GYROSCOPE = "android.sensor.wake_up_gyroscope";
+
+ /**
+ * A constant describing a wake up variant of a light sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_LIGHT
+ */
+ public static final int TYPE_WAKE_UP_LIGHT = 27;
+
+ /**
+ * A constant string describing a wake up light sensor type.
+ *
+ * @see #TYPE_WAKE_UP_LIGHT
+ */
+ public static final String STRING_TYPE_WAKE_UP_LIGHT = "android.sensor.wake_up_light";
+
+ /**
+ * A constant describing a wake up variant of a pressure sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_PRESSURE
+ */
+ public static final int TYPE_WAKE_UP_PRESSURE = 28;
+
+ /**
+ * A constant string describing a wake up pressure sensor type.
+ *
+ * @see #TYPE_WAKE_UP_PRESSURE
+ */
+ public static final String STRING_TYPE_WAKE_UP_PRESSURE = "android.sensor.wake_up_pressure";
+
+ /**
+ * A constant describing a wake up variant of a gravity sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GRAVITY
+ */
+ public static final int TYPE_WAKE_UP_GRAVITY = 29;
+
+ /**
+ * A constant string describing a wake up gravity sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GRAVITY
+ */
+ public static final String STRING_TYPE_WAKE_UP_GRAVITY = "android.sensor.wake_up_gravity";
+
+ /**
+ * A constant describing a wake up variant of a linear acceleration sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_LINEAR_ACCELERATION
+ */
+ public static final int TYPE_WAKE_UP_LINEAR_ACCELERATION = 30;
+
+ /**
+ * A constant string describing a wake up linear acceleration sensor type.
+ *
+ * @see #TYPE_WAKE_UP_LINEAR_ACCELERATION
+ */
+ public static final String STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION =
+ "android.sensor.wake_up_linear_acceleration";
+
+ /**
+ * A constant describing a wake up variant of a rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_ROTATION_VECTOR = 31;
+
+ /**
+ * A constant string describing a wake up rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_ROTATION_VECTOR =
+ "android.sensor.wake_up_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of a relative humidity sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_RELATIVE_HUMIDITY
+ */
+ public static final int TYPE_WAKE_UP_RELATIVE_HUMIDITY = 32;
+
+ /**
+ * A constant string describing a wake up relative humidity sensor type.
+ *
+ * @see #TYPE_WAKE_UP_RELATIVE_HUMIDITY
+ */
+ public static final String STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY =
+ "android.sensor.wake_up_relative_humidity";
+
+ /**
+ * A constant describing a wake up variant of an ambient temperature sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_AMBIENT_TEMPERATURE
+ */
+ public static final int TYPE_WAKE_UP_AMBIENT_TEMPERATURE = 33;
+
+ /**
+ * A constant string describing a wake up ambient temperature sensor type.
+ *
+ * @see #TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+ */
+ public static final String STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE =
+ "android.sensor.wake_up_ambient_temperature";
+
+ /**
+ * A constant describing a wake up variant of an uncalibrated magnetic field sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_MAGNETIC_FIELD_UNCALIBRATED
+ */
+ public static final int TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = 34;
+
+ /**
+ * A constant string describing a wake up uncalibrated magnetic field sensor type.
+ *
+ * @see #TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+ */
+ public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED =
+ "android.sensor.wake_up_magnetic_field_uncalibrated";
+
+ /**
+ * A constant describing a wake up variant of a game rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GAME_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_GAME_ROTATION_VECTOR = 35;
+
+ /**
+ * A constant string describing a wake up game rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR =
+ "android.sensor.wake_up_game_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of an uncalibrated gyroscope sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GYROSCOPE_UNCALIBRATED
+ */
+ public static final int TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = 36;
+
+ /**
+ * A constant string describing a wake up uncalibrated gyroscope sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+ */
+ public static final String STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED =
+ "android.sensor.wake_up_gyroscope_uncalibrated";
+
+ /**
+ * A constant describing a wake up variant of a step detector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_STEP_DETECTOR
+ */
+ public static final int TYPE_WAKE_UP_STEP_DETECTOR = 37;
+
+ /**
+ * A constant string describing a wake up step detector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_STEP_DETECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_STEP_DETECTOR =
+ "android.sensor.wake_up_step_detector";
+
+ /**
+ * A constant describing a wake up variant of a step counter sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_STEP_COUNTER
+ */
+ public static final int TYPE_WAKE_UP_STEP_COUNTER = 38;
+
+ /**
+ * A constant string describing a wake up step counter sensor type.
+ *
+ * @see #TYPE_WAKE_UP_STEP_COUNTER
+ */
+ public static final String STRING_TYPE_WAKE_UP_STEP_COUNTER =
+ "android.sensor.wake_up_step_counter";
+
+ /**
+ * A constant describing a wake up variant of a geomagnetic rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GEOMAGNETIC_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = 39;
+
+ /**
+ * A constant string describing a wake up geomagnetic rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR =
+ "android.sensor.wake_up_geomagnetic_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of a heart rate sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_HEART_RATE
+ */
+ public static final int TYPE_WAKE_UP_HEART_RATE = 40;
+
+ /**
+ * A constant string describing a wake up heart rate sensor type.
+ *
+ * @see #TYPE_WAKE_UP_HEART_RATE
+ */
+ public static final String STRING_TYPE_WAKE_UP_HEART_RATE = "android.sensor.wake_up_heart_rate";
+
+ /**
+ * A sensor of this type generates an event each time a tilt event is detected. A tilt event
+ * is generated if the direction of the 2-seconds window average gravity changed by at
+ * least 35 degrees since the activation of the sensor. It is a wake up sensor.
+ *
+ * @see #isWakeUpSensor()
+ */
+ public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41;
+
+ /**
+ * A constant string describing a wake up tilt detector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_TILT_DETECTOR
+ */
+ public static final String SENSOR_STRING_TYPE_WAKE_UP_TILT_DETECTOR =
+ "android.sensor.wake_up_tilt_detector";
+
+ /**
* A constant describing a wake gesture sensor.
* <p>
* Wake gesture sensors enable waking up the device based on a device specific motion.
@@ -410,6 +734,7 @@ public final class Sensor {
* the device. This sensor must be low power, as it is likely to be activated 24/7.
* Values of events created by this sensors should not be used.
*
+ * @see #isWakeUpSensor()
* @hide This sensor is expected to only be used by the power manager
*/
public static final int TYPE_WAKE_GESTURE = 42;
@@ -467,7 +792,29 @@ public final class Sensor {
REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_DETECTOR
REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_COUNTER
REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR
- REPORTING_MODE_ON_CHANGE, 1 // SENSOR_TYPE_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+ // wake up variants of base sensors
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ACCELEROMETER
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ORIENTATION
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GYROSCOPE
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_LIGHT
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_PRESSURE
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GRAVITY
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION
+ REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+ REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+ REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+ REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_DETECTOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_COUNTER
+ REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR
+ REPORTING_MODE_ONE_SHOT, 1, // SENSOR_TYPE_WAKE_GESTURE
};
static int getReportingMode(Sensor sensor) {
@@ -525,6 +872,8 @@ public final class Sensor {
private int mFifoMaxEventCount;
private String mStringType;
private String mRequiredPermission;
+ private int mMaxDelay;
+ private boolean mWakeUpSensor;
Sensor() {
}
@@ -625,6 +974,51 @@ public final class Sensor {
return mHandle;
}
+ /**
+ * This value is defined only for continuous mode sensors. It is the delay between two
+ * sensor events corresponding to the lowest frequency that this sensor supports. When
+ * lower frequencies are requested through registerListener() the events will be generated
+ * at this frequency instead. It can be used to estimate when the batch FIFO may be full.
+ * Older devices may set this value to zero. Ignore this value in case it is negative
+ * or zero.
+ *
+ * @return The max delay for this sensor in microseconds.
+ */
+ public int getMaxDelay() {
+ return mMaxDelay;
+ }
+
+ /**
+ * Returns whether this sensor is a wake-up sensor.
+ * <p>
+ * Wake up sensors wake the application processor up when they have events to deliver. When a
+ * wake up sensor is registered to without batching enabled, each event will wake the
+ * application processor up.
+ * <p>
+ * When a wake up sensor is registered to with batching enabled, it
+ * wakes the application processor up when maxReportingLatency has elapsed or when the hardware
+ * FIFO storing the events from wake up sensors is getting full.
+ * <p>
+ * Non-wake up sensors never wake the application processor up. Their events are only reported
+ * when the application processor is awake, for example because the application holds a wake
+ * lock, or another source woke the application processor up.
+ * <p>
+ * When a non-wake up sensor is registered to without batching enabled, the measurements made
+ * while the application processor is asleep might be lost and never returned.
+ * <p>
+ * When a non-wake up sensor is registered to with batching enabled, the measurements made while
+ * the application processor is asleep are stored in the hardware FIFO for non-wake up sensors.
+ * When this FIFO gets full, new events start overwriting older events. When the application
+ * then wakes up, the latest events are returned, and some old events might be lost. The number
+ * of events actually returned depends on the hardware FIFO size, as well as on what other
+ * sensors are activated. If losing sensor events is not acceptable during batching, you must
+ * use the wake-up version of the sensor.
+ * @return true if this is a wake up sensor, false otherwise.
+ */
+ public boolean isWakeUpSensor() {
+ return mWakeUpSensor;
+ }
+
void setRange(float max, float res) {
mMaxRange = max;
mResolution = res;
diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java
index 677d244..0d859fb 100644
--- a/core/java/android/hardware/SensorEventListener.java
+++ b/core/java/android/hardware/SensorEventListener.java
@@ -39,11 +39,13 @@ public interface SensorEventListener {
public void onSensorChanged(SensorEvent event);
/**
- * Called when the accuracy of a sensor has changed.
- * <p>See {@link android.hardware.SensorManager SensorManager}
- * for details.
+ * Called when the accuracy of the registered sensor has changed.
+ *
+ * <p>See the SENSOR_STATUS_* constants in
+ * {@link android.hardware.SensorManager SensorManager} for details.
*
- * @param accuracy The new accuracy of this sensor
+ * @param accuracy The new accuracy of this sensor, one of
+ * {@code SensorManager.SENSOR_STATUS_*}
*/
- public void onAccuracyChanged(Sensor sensor, int accuracy);
+ public void onAccuracyChanged(Sensor sensor, int accuracy);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 5f2b5f0..25c7630 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -321,6 +321,13 @@ public abstract class SensorManager {
/**
+ * The values returned by this sensor cannot be trusted because the sensor
+ * had no contact with what it was measuring (for example, the heart rate
+ * monitor is not in contact with the user).
+ */
+ public static final int SENSOR_STATUS_NO_CONTACT = -1;
+
+ /**
* The values returned by this sensor cannot be trusted, calibration is
* needed or the environment doesn't allow readings
*/
@@ -421,9 +428,10 @@ public abstract class SensorManager {
* {@link SensorManager#getSensorList(int) getSensorList}.
*
* @param type
- * of sensors requested
+ * of sensors requested
*
- * @return the default sensors matching the asked type.
+ * @return the default sensor matching the requested type if one exists and the application
+ * has the necessary permissions, or null otherwise.
*
* @see #getSensorList(int)
* @see Sensor
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 8684a04..b66ec86 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -395,25 +395,12 @@ public class SystemSensorManager extends SensorManager {
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);
- mListener.onAccuracyChanged(t.sensor, t.accuracy);
- }
- break;
- default:
- // For other sensors, just report the accuracy once
- if (mFirstEvent.get(handle) == false) {
- mFirstEvent.put(handle, true);
- mListener.onAccuracyChanged(
- t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
- }
- break;
+
+ // 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);
+ mListener.onAccuracyChanged(t.sensor, t.accuracy);
}
mListener.onSensorChanged(t);
}
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 8391209..7738d2d 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -508,14 +508,62 @@ public abstract class CameraCaptureSession implements AutoCloseable {
}
/**
- * This method is called when an image capture has completed and the
+ * This method is called when an image capture makes partial forward progress; some
+ * (but not all) results from an image capture are available.
+ *
+ * <p>The result provided here will contain some subset of the fields of
+ * a full result. Multiple {@link #onCaptureProgressed} calls may happen per
+ * capture; a given result field will only be present in one partial
+ * capture at most. The final {@link #onCaptureCompleted} call will always
+ * contain all the fields (in particular, the union of all the fields of all
+ * the partial results composing the total result).</p>
+ *
+ * <p>For each request, some result data might be available earlier than others. The typical
+ * delay between each partial result (per request) is a single frame interval.
+ * For performance-oriented use-cases, applications should query the metadata they need
+ * to make forward progress from the partial results and avoid waiting for the completed
+ * result.</p>
+ *
+ * <p>Each request will generate at least {@code 1} partial results, and at most
+ * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p>
+ *
+ * <p>Depending on the request settings, the number of partial results per request
+ * will vary, although typically the partial count could be the same as long as the
+ * camera device subsystems enabled stay the same.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera The CameraDevice sending the callback.
+ * @param request The request that was given to the CameraDevice
+ * @param partialResult The partial output metadata from the capture, which
+ * includes a subset of the {@link TotalCaptureResult} fields.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public void onCaptureProgressed(CameraDevice camera,
+ CaptureRequest request, CaptureResult partialResult) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when an image capture has fully completed and all the
* result metadata is available.
*
+ * <p>This callback will always fire after the last {@link #onCaptureProgressed};
+ * in other words, no more partial results will be delivered once the completed result
+ * is available.</p>
+ *
+ * <p>For performance-intensive use-cases where latency is a factor, consider
+ * using {@link #onCaptureProgressed} instead.</p>
+ *
* <p>The default implementation of this method does nothing.</p>
*
* @param camera The CameraDevice sending the callback.
* @param request The request that was given to the CameraDevice
- * @param result The output metadata from the capture, including the
+ * @param result The total output metadata from the capture, including the
* final capture parameters and the state of the camera system during
* capture.
*
@@ -525,7 +573,7 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* @see #setRepeatingBurst
*/
public void onCaptureCompleted(CameraDevice camera,
- CaptureRequest request, CaptureResult result) {
+ CaptureRequest request, TotalCaptureResult result) {
// default empty implementation
}
@@ -563,24 +611,57 @@ public abstract class CameraCaptureSession implements AutoCloseable {
* when a capture sequence finishes and all {@link CaptureResult}
* or {@link CaptureFailure} for it have been returned via this listener.
*
+ * <p>In total, there will be at least one result/failure returned by this listener
+ * before this callback is invoked. If the capture sequence is aborted before any
+ * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
* @param camera
* The CameraDevice sending the callback.
* @param sequenceId
* A sequence ID returned by the {@link #capture} family of functions.
- * @param lastFrameNumber
+ * @param frameNumber
* The last frame number (returned by {@link CaptureResult#getFrameNumber}
* or {@link CaptureFailure#getFrameNumber}) in the capture sequence.
- * The last frame number may be equal to NO_FRAMES_CAPTURED if no images
- * were captured for this sequence. This can happen, for example, when a
- * repeating request or burst is cleared right after being set.
*
* @see CaptureResult#getFrameNumber()
* @see CaptureFailure#getFrameNumber()
* @see CaptureResult#getSequenceId()
* @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceAborted
*/
public void onCaptureSequenceCompleted(CameraDevice camera,
- int sequenceId, int lastFrameNumber) {
+ int sequenceId, long frameNumber) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called independently of the others in CaptureListener,
+ * when a capture sequence aborts before any {@link CaptureResult}
+ * or {@link CaptureFailure} for it have been returned via this listener.
+ *
+ * <p>Due to the asynchronous nature of the camera device, not all submitted captures
+ * are immediately processed. It is possible to clear out the pending requests
+ * by a variety of operations such as {@link CameraDevice#stopRepeating} or
+ * {@link CameraDevice#flush}. When such an event happens,
+ * {@link #onCaptureSequenceCompleted} will not be called.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
+ * @param camera
+ * The CameraDevice sending the callback.
+ * @param sequenceId
+ * A sequence ID returned by the {@link #capture} family of functions.
+ *
+ * @see CaptureResult#getFrameNumber()
+ * @see CaptureFailure#getFrameNumber()
+ * @see CaptureResult#getSequenceId()
+ * @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceCompleted
+ */
+ public void onCaptureSequenceAborted(CameraDevice camera,
+ int sequenceId) {
// default empty implementation
}
}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index c08424a..2f5b4fe 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -296,8 +296,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* valid anti-banding modes that the application may request
* for this camera device; they must include AUTO.</p>
*/
- public static final Key<byte[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES =
- new Key<byte[]>("android.control.aeAvailableAntibandingModes", byte[].class);
+ public static final Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES =
+ new Key<int[]>("android.control.aeAvailableAntibandingModes", int[].class);
/**
* <p>The set of auto-exposure modes that are supported by this
@@ -315,15 +315,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CaptureRequest#CONTROL_AE_MODE
*/
- public static final Key<byte[]> CONTROL_AE_AVAILABLE_MODES =
- new Key<byte[]>("android.control.aeAvailableModes", byte[].class);
+ public static final Key<int[]> CONTROL_AE_AVAILABLE_MODES =
+ new Key<int[]>("android.control.aeAvailableModes", int[].class);
/**
* <p>List of frame rate ranges supported by the
* AE algorithm/hardware</p>
*/
- public static final Key<int[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES =
- new Key<int[]>("android.control.aeAvailableTargetFpsRanges", int[].class);
+ public static final Key<android.util.Range<Integer>[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES =
+ new Key<android.util.Range<Integer>[]>("android.control.aeAvailableTargetFpsRanges", new TypeReference<android.util.Range<Integer>[]>() {{ }});
/**
* <p>Maximum and minimum exposure compensation
@@ -332,8 +332,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CameraCharacteristics#CONTROL_AE_COMPENSATION_STEP
*/
- public static final Key<int[]> CONTROL_AE_COMPENSATION_RANGE =
- new Key<int[]>("android.control.aeCompensationRange", int[].class);
+ public static final Key<android.util.Range<Integer>> CONTROL_AE_COMPENSATION_RANGE =
+ new Key<android.util.Range<Integer>>("android.control.aeCompensationRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Smallest step by which exposure compensation
@@ -355,8 +355,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @see CaptureRequest#CONTROL_AF_MODE
* @see CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE
*/
- public static final Key<byte[]> CONTROL_AF_AVAILABLE_MODES =
- new Key<byte[]>("android.control.afAvailableModes", byte[].class);
+ public static final Key<int[]> CONTROL_AF_AVAILABLE_MODES =
+ new Key<int[]>("android.control.afAvailableModes", int[].class);
/**
* <p>List containing the subset of color effects
@@ -374,8 +374,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @see CaptureRequest#CONTROL_EFFECT_MODE
* @see CaptureRequest#CONTROL_MODE
*/
- public static final Key<byte[]> CONTROL_AVAILABLE_EFFECTS =
- new Key<byte[]>("android.control.availableEffects", byte[].class);
+ public static final Key<int[]> CONTROL_AVAILABLE_EFFECTS =
+ new Key<int[]>("android.control.availableEffects", int[].class);
/**
* <p>List containing a subset of scene modes
@@ -388,15 +388,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CaptureRequest#CONTROL_SCENE_MODE
*/
- public static final Key<byte[]> CONTROL_AVAILABLE_SCENE_MODES =
- new Key<byte[]>("android.control.availableSceneModes", byte[].class);
+ public static final Key<int[]> CONTROL_AVAILABLE_SCENE_MODES =
+ new Key<int[]>("android.control.availableSceneModes", int[].class);
/**
* <p>List of video stabilization modes that can
* be supported</p>
*/
- public static final Key<byte[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES =
- new Key<byte[]>("android.control.availableVideoStabilizationModes", byte[].class);
+ public static final Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES =
+ new Key<int[]>("android.control.availableVideoStabilizationModes", int[].class);
/**
* <p>The set of auto-white-balance modes ({@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode})
@@ -414,8 +414,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
* @see CaptureRequest#CONTROL_AWB_MODE
*/
- public static final Key<byte[]> CONTROL_AWB_AVAILABLE_MODES =
- new Key<byte[]>("android.control.awbAvailableModes", byte[].class);
+ public static final Key<int[]> CONTROL_AWB_AVAILABLE_MODES =
+ new Key<int[]>("android.control.awbAvailableModes", int[].class);
/**
* <p>List of the maximum number of regions that can be used for metering in
@@ -427,19 +427,53 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @see CaptureRequest#CONTROL_AE_REGIONS
* @see CaptureRequest#CONTROL_AF_REGIONS
* @see CaptureRequest#CONTROL_AWB_REGIONS
+ * @hide
*/
public static final Key<int[]> CONTROL_MAX_REGIONS =
new Key<int[]>("android.control.maxRegions", int[].class);
/**
+ * <p>List of the maximum number of regions that can be used for metering in
+ * auto-exposure (AE);
+ * this corresponds to the the maximum number of elements in
+ * {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_REGIONS
+ */
+ public static final Key<Integer> CONTROL_MAX_REGIONS_AE =
+ new Key<Integer>("android.control.maxRegionsAe", int.class);
+
+ /**
+ * <p>List of the maximum number of regions that can be used for metering in
+ * auto-white balance (AWB);
+ * this corresponds to the the maximum number of elements in
+ * {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AWB_REGIONS
+ */
+ public static final Key<Integer> CONTROL_MAX_REGIONS_AWB =
+ new Key<Integer>("android.control.maxRegionsAwb", int.class);
+
+ /**
+ * <p>List of the maximum number of regions that can be used for metering in
+ * auto-focus (AF);
+ * this corresponds to the the maximum number of elements in
+ * {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AF_REGIONS
+ */
+ public static final Key<Integer> CONTROL_MAX_REGIONS_AF =
+ new Key<Integer>("android.control.maxRegionsAf", int.class);
+
+ /**
* <p>The set of edge enhancement modes supported by this camera device.</p>
* <p>This tag lists the valid modes for {@link CaptureRequest#EDGE_MODE android.edge.mode}.</p>
* <p>Full-capability camera devices must always support OFF and FAST.</p>
*
* @see CaptureRequest#EDGE_MODE
*/
- public static final Key<byte[]> EDGE_AVAILABLE_EDGE_MODES =
- new Key<byte[]>("android.edge.availableEdgeModes", byte[].class);
+ public static final Key<int[]> EDGE_AVAILABLE_EDGE_MODES =
+ new Key<int[]>("android.edge.availableEdgeModes", int[].class);
/**
* <p>Whether this camera device has a
@@ -458,8 +492,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CaptureRequest#HOT_PIXEL_MODE
*/
- public static final Key<byte[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES =
- new Key<byte[]>("android.hotPixel.availableHotPixelModes", byte[].class);
+ public static final Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES =
+ new Key<int[]>("android.hotPixel.availableHotPixelModes", int[].class);
/**
* <p>Supported resolutions for the JPEG thumbnail</p>
@@ -526,8 +560,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CaptureRequest#LENS_OPTICAL_STABILIZATION_MODE
*/
- public static final Key<byte[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION =
- new Key<byte[]>("android.lens.info.availableOpticalStabilization", byte[].class);
+ public static final Key<int[]> LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION =
+ new Key<int[]>("android.lens.info.availableOpticalStabilization", int[].class);
/**
* <p>Optional. Hyperfocal distance for this lens.</p>
@@ -553,6 +587,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>Dimensions of lens shading map.</p>
* <p>The map should be on the order of 30-40 rows and columns, and
* must be smaller than 64x64.</p>
+ * @hide
*/
public static final Key<android.util.Size> LENS_INFO_SHADING_MAP_SIZE =
new Key<android.util.Size>("android.lens.info.shadingMapSize", android.util.Size.class);
@@ -591,8 +626,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CaptureRequest#NOISE_REDUCTION_MODE
*/
- public static final Key<byte[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES =
- new Key<byte[]>("android.noiseReduction.availableNoiseReductionModes", byte[].class);
+ public static final Key<int[]> NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES =
+ new Key<int[]>("android.noiseReduction.availableNoiseReductionModes", int[].class);
/**
* <p>If set to 1, the HAL will always split result
@@ -621,7 +656,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* number is 3, and max JPEG stream number is 2, then this tuple should be <code>(1, 3, 2)</code>.</p>
* <p>This lists the upper bound of the number of output streams supported by
* the camera device. Using more streams simultaneously may require more hardware and
- * CPU resources that will consume more power. The image format for a output stream can
+ * CPU resources that will consume more power. The image format for an output stream can
* be any supported format provided by android.scaler.availableStreamConfigurations.
* The formats defined in android.scaler.availableStreamConfigurations can be catergorized
* into the 3 stream types as below:</p>
@@ -632,11 +667,79 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <li>Processed (but not-stalling): any non-RAW format without a stall duration.
* Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li>
* </ul>
+ * @hide
*/
public static final Key<int[]> REQUEST_MAX_NUM_OUTPUT_STREAMS =
new Key<int[]>("android.request.maxNumOutputStreams", int[].class);
/**
+ * <p>The maximum numbers of different types of output streams
+ * that can be configured and used simultaneously by a camera device
+ * for any <code>RAW</code> formats.</p>
+ * <p>This value contains the max number of output simultaneous
+ * streams from the raw sensor.</p>
+ * <p>This lists the upper bound of the number of output streams supported by
+ * the camera device. Using more streams simultaneously may require more hardware and
+ * CPU resources that will consume more power. The image format for this kind of an output stream can
+ * be any <code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p>
+ * <p>In particular, a <code>RAW</code> format is typically one of:</p>
+ * <ul>
+ * <li>ImageFormat#RAW_SENSOR</li>
+ * <li>Opaque <code>RAW</code></li>
+ * </ul>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ */
+ public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_RAW =
+ new Key<Integer>("android.request.maxNumOutputRaw", int.class);
+
+ /**
+ * <p>The maximum numbers of different types of output streams
+ * that can be configured and used simultaneously by a camera device
+ * for any processed (but not-stalling) formats.</p>
+ * <p>This value contains the max number of output simultaneous
+ * streams for any processed (but not-stalling) formats.</p>
+ * <p>This lists the upper bound of the number of output streams supported by
+ * the camera device. Using more streams simultaneously may require more hardware and
+ * CPU resources that will consume more power. The image format for this kind of an output stream can
+ * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p>
+ * <p>Processed (but not-stalling) is defined as any non-RAW format without a stall duration.
+ * Typically:</p>
+ * <ul>
+ * <li>ImageFormat#YUV_420_888</li>
+ * <li>ImageFormat#NV21</li>
+ * <li>ImageFormat#YV12</li>
+ * <li>Implementation-defined formats, i.e. StreamConfiguration#isOutputSupportedFor(Class)</li>
+ * </ul>
+ * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with
+ * a processed format -- it will return 0 for a non-stalling stream.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ */
+ public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC =
+ new Key<Integer>("android.request.maxNumOutputProc", int.class);
+
+ /**
+ * <p>The maximum numbers of different types of output streams
+ * that can be configured and used simultaneously by a camera device
+ * for any processed (and stalling) formats.</p>
+ * <p>This value contains the max number of output simultaneous
+ * streams for any processed (but not-stalling) formats.</p>
+ * <p>This lists the upper bound of the number of output streams supported by
+ * the camera device. Using more streams simultaneously may require more hardware and
+ * CPU resources that will consume more power. The image format for this kind of an output stream can
+ * be any non-<code>RAW</code> and supported format provided by {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}.</p>
+ * <p>A processed and stalling format is defined as any non-RAW format with a stallDurations &gt; 0.
+ * Typically only the <code>JPEG</code> format (ImageFormat#JPEG)</p>
+ * <p>For full guarantees, query StreamConfigurationMap#getOutputStallDuration with
+ * a processed format -- it will return a non-0 value for a stalling stream.</p>
+ *
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
+ */
+ public static final Key<Integer> REQUEST_MAX_NUM_OUTPUT_PROC_STALLING =
+ new Key<Integer>("android.request.maxNumOutputProcStalling", int.class);
+
+ /**
* <p>The maximum numbers of any type of input streams
* that can be configured and used simultaneously by a camera device.</p>
* <p>When set to 0, it means no input stream is supported.</p>
@@ -707,7 +810,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> FULL devices:</p>
* <ul>
* <li>MANUAL_SENSOR</li>
- * <li>ZSL</li>
* </ul>
* <p>Other capabilities may be available on either FULL or LIMITED
* devices, but the app. should query this field to be sure.</p>
@@ -716,12 +818,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @see #REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE
* @see #REQUEST_AVAILABLE_CAPABILITIES_OPTIONAL
* @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR
- * @see #REQUEST_AVAILABLE_CAPABILITIES_GCAM
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING
* @see #REQUEST_AVAILABLE_CAPABILITIES_ZSL
* @see #REQUEST_AVAILABLE_CAPABILITIES_DNG
*/
- public static final Key<Integer> REQUEST_AVAILABLE_CAPABILITIES =
- new Key<Integer>("android.request.availableCapabilities", int.class);
+ public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES =
+ new Key<int[]>("android.request.availableCapabilities", int[].class);
/**
* <p>A list of all keys that the camera device has available
@@ -750,7 +852,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* value.</p>
* <p>The following keys may return <code>null</code> unless they are enabled:</p>
* <ul>
- * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li>
+ * <li>android.statistics.lensShadingMap (non-null iff {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON)</li>
* </ul>
* <p>(Those sometimes-null keys should nevertheless be listed here
* if they are available.)</p>
@@ -761,7 +863,6 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>TODO: This should be used by #getAvailableCaptureResultKeys.</p>
*
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @hide
*/
@@ -1229,8 +1330,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* <p>Range of valid sensitivities</p>
*/
- public static final Key<int[]> SENSOR_INFO_SENSITIVITY_RANGE =
- new Key<int[]>("android.sensor.info.sensitivityRange", int[].class);
+ public static final Key<android.util.Range<Integer>> SENSOR_INFO_SENSITIVITY_RANGE =
+ new Key<android.util.Range<Integer>>("android.sensor.info.sensitivityRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Arrangement of color filters on sensor;
@@ -1251,8 +1352,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
- public static final Key<long[]> SENSOR_INFO_EXPOSURE_TIME_RANGE =
- new Key<long[]>("android.sensor.info.exposureTimeRange", long[].class);
+ public static final Key<android.util.Range<Long>> SENSOR_INFO_EXPOSURE_TIME_RANGE =
+ new Key<android.util.Range<Long>>("android.sensor.info.exposureTimeRange", new TypeReference<android.util.Range<Long>>() {{ }});
/**
* <p>Maximum possible frame duration (minimum frame
@@ -1276,8 +1377,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* array</p>
* <p>Needed for FOV calculation for old API</p>
*/
- public static final Key<float[]> SENSOR_INFO_PHYSICAL_SIZE =
- new Key<float[]>("android.sensor.info.physicalSize", float[].class);
+ public static final Key<android.util.SizeF> SENSOR_INFO_PHYSICAL_SIZE =
+ new Key<android.util.SizeF>("android.sensor.info.physicalSize", android.util.SizeF.class);
/**
* <p>Dimensions of full pixel array, possibly
@@ -1383,8 +1484,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
*/
- public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM1 =
- new Key<Rational[]>("android.sensor.calibrationTransform1", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM1 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform1", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A per-device calibration transform matrix that maps from the
@@ -1404,8 +1505,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
*/
- public static final Key<Rational[]> SENSOR_CALIBRATION_TRANSFORM2 =
- new Key<Rational[]>("android.sensor.calibrationTransform2", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_CALIBRATION_TRANSFORM2 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.calibrationTransform2", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms color values from CIE XYZ color space to
@@ -1426,8 +1527,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
*/
- public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM1 =
- new Key<Rational[]>("android.sensor.colorTransform1", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM1 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform1", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms color values from CIE XYZ color space to
@@ -1450,8 +1551,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
*/
- public static final Key<Rational[]> SENSOR_COLOR_TRANSFORM2 =
- new Key<Rational[]>("android.sensor.colorTransform2", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_COLOR_TRANSFORM2 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.colorTransform2", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms white balanced camera colors from the reference
@@ -1470,8 +1571,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT1
*/
- public static final Key<Rational[]> SENSOR_FORWARD_MATRIX1 =
- new Key<Rational[]>("android.sensor.forwardMatrix1", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX1 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix1", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A matrix that transforms white balanced camera colors from the reference
@@ -1492,8 +1593,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CameraCharacteristics#SENSOR_REFERENCE_ILLUMINANT2
*/
- public static final Key<Rational[]> SENSOR_FORWARD_MATRIX2 =
- new Key<Rational[]>("android.sensor.forwardMatrix2", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> SENSOR_FORWARD_MATRIX2 =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.sensor.forwardMatrix2", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>A fixed black level offset for each of the color filter arrangement
@@ -1560,8 +1661,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* android.statistics.faceIds and
* android.statistics.faceLandmarks outputs.</p>
*/
- public static final Key<byte[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES =
- new Key<byte[]>("android.statistics.info.availableFaceDetectModes", byte[].class);
+ public static final Key<int[]> STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES =
+ new Key<int[]>("android.statistics.info.availableFaceDetectModes", int[].class);
/**
* <p>Maximum number of simultaneously detectable
@@ -1584,19 +1685,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* <p>Maximum number of supported points in the
- * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, or
- * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, or {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.</p>
+ * tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p>
* <p>If the actual number of points provided by the application (in
- * android.tonemap.curve*) is less than max, the camera device will
+ * {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}*) is less than max, the camera device will
* resample the curve to its internal representation, using linear
* interpolation.</p>
* <p>The output curves in the result metadata may have a different number
* of points than the input curves, and will represent the actual
* hardware curves used as closely as possible when linearly interpolated.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_BLUE
- * @see CaptureRequest#TONEMAP_CURVE_GREEN
- * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_CURVE
*/
public static final Key<Integer> TONEMAP_MAX_CURVE_POINTS =
new Key<Integer>("android.tonemap.maxCurvePoints", int.class);
@@ -1609,8 +1707,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
*
* @see CaptureRequest#TONEMAP_MODE
*/
- public static final Key<byte[]> TONEMAP_AVAILABLE_TONE_MAP_MODES =
- new Key<byte[]>("android.tonemap.availableToneMapModes", byte[].class);
+ public static final Key<int[]> TONEMAP_AVAILABLE_TONE_MAP_MODES =
+ new Key<int[]>("android.tonemap.availableToneMapModes", int[].class);
/**
* <p>A list of camera LEDs that are available on this system.</p>
@@ -1626,15 +1724,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>A FULL device has the most support possible and will enable the
* widest range of use cases such as:</p>
* <ul>
- * <li>30 FPS at maximum resolution (== sensor resolution)</li>
- * <li>Per frame control</li>
- * <li>Manual sensor control</li>
- * <li>Zero Shutter Lag (ZSL)</li>
+ * <li>30fps at maximum resolution (== sensor resolution) is preferred, more than 20fps is required.</li>
+ * <li>Per frame control ({@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} <code>==</code> PER_FRAME_CONTROL)</li>
+ * <li>Manual sensor control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_SENSOR)</li>
+ * <li>Manual post-processing control ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains MANUAL_POST_PROCESSING)</li>
* </ul>
* <p>A LIMITED device may have some or none of the above characteristics.
* To find out more refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}.</p>
*
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraCharacteristics#SYNC_MAX_LATENCY
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
* @see #INFO_SUPPORTED_HARDWARE_LEVEL_FULL
*/
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 77640d1..6f5099b 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -766,14 +766,62 @@ public interface CameraDevice extends AutoCloseable {
}
/**
- * This method is called when an image capture has completed and the
+ * This method is called when an image capture makes partial forward progress; some
+ * (but not all) results from an image capture are available.
+ *
+ * <p>The result provided here will contain some subset of the fields of
+ * a full result. Multiple {@link #onCaptureProgressed} calls may happen per
+ * capture; a given result field will only be present in one partial
+ * capture at most. The final {@link #onCaptureCompleted} call will always
+ * contain all the fields (in particular, the union of all the fields of all
+ * the partial results composing the total result).</p>
+ *
+ * <p>For each request, some result data might be available earlier than others. The typical
+ * delay between each partial result (per request) is a single frame interval.
+ * For performance-oriented use-cases, applications should query the metadata they need
+ * to make forward progress from the partial results and avoid waiting for the completed
+ * result.</p>
+ *
+ * <p>Each request will generate at least {@code 1} partial results, and at most
+ * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT} partial results.</p>
+ *
+ * <p>Depending on the request settings, the number of partial results per request
+ * will vary, although typically the partial count could be the same as long as the
+ * camera device subsystems enabled stay the same.</p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param camera The CameraDevice sending the callback.
+ * @param request The request that was given to the CameraDevice
+ * @param partialResult The partial output metadata from the capture, which
+ * includes a subset of the {@link TotalCaptureResult} fields.
+ *
+ * @see #capture
+ * @see #captureBurst
+ * @see #setRepeatingRequest
+ * @see #setRepeatingBurst
+ */
+ public void onCaptureProgressed(CameraDevice camera,
+ CaptureRequest request, CaptureResult partialResult) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called when an image capture has fully completed and all the
* result metadata is available.
*
+ * <p>This callback will always fire after the last {@link #onCaptureProgressed};
+ * in other words, no more partial results will be delivered once the completed result
+ * is available.</p>
+ *
+ * <p>For performance-intensive use-cases where latency is a factor, consider
+ * using {@link #onCaptureProgressed} instead.</p>
+ *
* <p>The default implementation of this method does nothing.</p>
*
* @param camera The CameraDevice sending the callback.
* @param request The request that was given to the CameraDevice
- * @param result The output metadata from the capture, including the
+ * @param result The total output metadata from the capture, including the
* final capture parameters and the state of the camera system during
* capture.
*
@@ -783,7 +831,7 @@ public interface CameraDevice extends AutoCloseable {
* @see #setRepeatingBurst
*/
public void onCaptureCompleted(CameraDevice camera,
- CaptureRequest request, CaptureResult result) {
+ CaptureRequest request, TotalCaptureResult result) {
// default empty implementation
}
@@ -796,6 +844,10 @@ public interface CameraDevice extends AutoCloseable {
* the capture may have been pushed to their respective output
* streams.</p>
*
+ * <p>Some partial results may have been delivered before the capture fails;
+ * however after this callback fires, no more partial results will be delivered by
+ * {@link #onCaptureProgressed}.</p>
+ *
* <p>The default implementation of this method does nothing.</p>
*
* @param camera
@@ -821,24 +873,57 @@ public interface CameraDevice extends AutoCloseable {
* when a capture sequence finishes and all {@link CaptureResult}
* or {@link CaptureFailure} for it have been returned via this listener.
*
+ * <p>In total, there will be at least one result/failure returned by this listener
+ * before this callback is invoked. If the capture sequence is aborted before any
+ * requests have been processed, {@link #onCaptureSequenceAborted} is invoked instead.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
* @param camera
* The CameraDevice sending the callback.
* @param sequenceId
* A sequence ID returned by the {@link #capture} family of functions.
- * @param lastFrameNumber
+ * @param frameNumber
* The last frame number (returned by {@link CaptureResult#getFrameNumber}
* or {@link CaptureFailure#getFrameNumber}) in the capture sequence.
- * The last frame number may be equal to NO_FRAMES_CAPTURED if no images
- * were captured for this sequence. This can happen, for example, when a
- * repeating request or burst is cleared right after being set.
*
* @see CaptureResult#getFrameNumber()
* @see CaptureFailure#getFrameNumber()
* @see CaptureResult#getSequenceId()
* @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceAborted
*/
public void onCaptureSequenceCompleted(CameraDevice camera,
- int sequenceId, int lastFrameNumber) {
+ int sequenceId, long frameNumber) {
+ // default empty implementation
+ }
+
+ /**
+ * This method is called independently of the others in CaptureListener,
+ * when a capture sequence aborts before any {@link CaptureResult}
+ * or {@link CaptureFailure} for it have been returned via this listener.
+ *
+ * <p>Due to the asynchronous nature of the camera device, not all submitted captures
+ * are immediately processed. It is possible to clear out the pending requests
+ * by a variety of operations such as {@link CameraDevice#stopRepeating} or
+ * {@link CameraDevice#flush}. When such an event happens,
+ * {@link #onCaptureSequenceCompleted} will not be called.</p>
+ *
+ * <p>The default implementation does nothing.</p>
+ *
+ * @param camera
+ * The CameraDevice sending the callback.
+ * @param sequenceId
+ * A sequence ID returned by the {@link #capture} family of functions.
+ *
+ * @see CaptureResult#getFrameNumber()
+ * @see CaptureFailure#getFrameNumber()
+ * @see CaptureResult#getSequenceId()
+ * @see CaptureFailure#getSequenceId()
+ * @see #onCaptureSequenceCompleted
+ */
+ public void onCaptureSequenceAborted(CameraDevice camera,
+ int sequenceId) {
// default empty implementation
}
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index accceb1..4a89fe7 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -219,6 +219,7 @@ public final class CameraManager {
private CameraDevice openCameraDeviceUserAsync(String cameraId,
CameraDevice.StateListener listener, Handler handler)
throws CameraAccessException {
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
try {
@@ -230,7 +231,8 @@ public final class CameraManager {
new android.hardware.camera2.impl.CameraDevice(
cameraId,
listener,
- handler);
+ handler,
+ characteristics);
BinderHolder holder = new BinderHolder();
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 4cde601..b3e165e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -236,9 +236,17 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>The camera device can be manually controlled (3A algorithms such
- * as auto exposure, and auto focus can be
- * bypassed), this includes but is not limited to:</p>
+ * as auto exposure, and auto focus can be bypassed).
+ * The camera device supports basic manual control of the sensor image
+ * acquisition related stages. This means the following controls are
+ * guaranteed to be supported:</p>
* <ul>
+ * <li>Manual frame duration control<ul>
+ * <li>{@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration}</li>
+ * <li>{@link CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION android.sensor.info.maxFrameDuration}</li>
+ * <li>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP android.scaler.streamConfigurationMap}</li>
+ * </ul>
+ * </li>
* <li>Manual exposure control<ul>
* <li>{@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}</li>
* <li>{@link CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE android.sensor.info.exposureTimeRange}</li>
@@ -265,10 +273,15 @@ public abstract class CameraMetadata<TKey> {
* <p>If any of the above 3A algorithms are enabled, then the camera
* device will accurately report the values applied by 3A in the
* result.</p>
+ * <p>A given camera device may also support additional manual sensor controls,
+ * but this capability only covers the above list of controls.</p>
*
* @see CaptureRequest#BLACK_LEVEL_LOCK
+ * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
* @see CameraCharacteristics#SENSOR_INFO_EXPOSURE_TIME_RANGE
+ * @see CameraCharacteristics#SENSOR_INFO_MAX_FRAME_DURATION
* @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE
* @see CaptureRequest#SENSOR_SENSITIVITY
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
@@ -276,12 +289,12 @@ public abstract class CameraMetadata<TKey> {
public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 2;
/**
- * <p>TODO: This should be @hide</p>
+ * <p>The camera device post-processing stages can be manually controlled.
+ * The camera device supports basic manual control of the image post-processing
+ * stages. This means the following controls are guaranteed to be supported:</p>
* <ul>
* <li>Manual tonemap control<ul>
- * <li>{@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}</li>
- * <li>{@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}</li>
- * <li>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}</li>
+ * <li>{@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}</li>
* <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li>
* <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li>
* </ul>
@@ -292,8 +305,8 @@ public abstract class CameraMetadata<TKey> {
* </ul>
* </li>
* <li>Lens shading map information<ul>
- * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}</li>
- * <li>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}</li>
+ * <li>android.statistics.lensShadingMap</li>
+ * <li>android.lens.info.shadingMapSize</li>
* </ul>
* </li>
* </ul>
@@ -301,19 +314,17 @@ public abstract class CameraMetadata<TKey> {
* will accurately report the values applied by AWB in the result.</p>
* <p>The camera device will also support everything in MANUAL_SENSOR
* except manual lens control and manual flash control.</p>
+ * <p>A given camera device may also support additional post-processing
+ * controls, but this capability only covers the above list of controls.</p>
*
* @see CaptureRequest#COLOR_CORRECTION_GAINS
* @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
- * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
- * @see CaptureRequest#TONEMAP_CURVE_BLUE
- * @see CaptureRequest#TONEMAP_CURVE_GREEN
- * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_CURVE
* @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
* @see CaptureRequest#TONEMAP_MODE
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
- public static final int REQUEST_AVAILABLE_CAPABILITIES_GCAM = 3;
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 3;
/**
* <p>The camera device supports the Zero Shutter Lag use case.</p>
@@ -1548,17 +1559,14 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>Use the tone mapping curve specified in
- * the android.tonemap.curve* entries.</p>
+ * the {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}* entries.</p>
* <p>All color enhancement and tonemapping must be disabled, except
* for applying the tonemapping curve specified by
- * {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed}, {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}, or
- * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}.</p>
+ * {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p>
* <p>Must not slow down frame rate relative to raw
* sensor output.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_BLUE
- * @see CaptureRequest#TONEMAP_CURVE_GREEN
- * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_CURVE
* @see CaptureRequest#TONEMAP_MODE
*/
public static final int TONEMAP_MODE_CONTRAST_CURVE = 0;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index a4aa296..d4dfdd5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -537,30 +537,24 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
- public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM =
- new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>Gains applying to Bayer raw color channels for
* white-balance.</p>
- * <p>The 4-channel white-balance gains are defined in
- * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain
- * for green pixels on even rows of the output, and <code>G_odd</code>
- * is the gain for green pixels on the odd rows. if a HAL
- * does not support a separate gain for even/odd green channels,
- * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to
- * <code>G_even</code> in the output result metadata.</p>
- * <p>This array is either set by the camera device when the request
- * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or
- * directly by the application in the request when the
- * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p>
- * <p>The output should be the gains actually applied by the camera device to
- * the current frame.</p>
+ * <p>These per-channel gains are either set by the camera device
+ * when the request {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not
+ * TRANSFORM_MATRIX, or directly by the application in the
+ * request when the {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is
+ * TRANSFORM_MATRIX.</p>
+ * <p>The gains in the result metadata are the gains actually
+ * applied by the camera device to the current frame.</p>
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
- public static final Key<float[]> COLOR_CORRECTION_GAINS =
- new Key<float[]>("android.colorCorrection.gains", float[].class);
+ public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS =
+ new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class);
/**
* <p>The desired setting for the camera device's auto-exposure
@@ -693,15 +687,16 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>List of areas to use for
* metering.</p>
- * <p>Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined to be inclusive of the
- * specified coordinates.</p>
* <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
* ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
- * bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.</p>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</p>
* <p>If all regions have 0 weight, then no specific metering area
* needs to be used by the camera device. If the metering region is
* outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
@@ -711,8 +706,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
- public static final Key<int[]> CONTROL_AE_REGIONS =
- new Key<int[]>("android.control.aeRegions", int[].class);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Range over which fps can be adjusted to
@@ -722,8 +717,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
- public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE =
- new Key<int[]>("android.control.aeTargetFpsRange", int[].class);
+ public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE =
+ new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Whether the camera device will trigger a precapture
@@ -768,26 +763,27 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>List of areas to use for focus
* estimation.</p>
- * <p>Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined to be inclusive of the
- * specified coordinates.</p>
* <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
* ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
- * bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.</p>
- * <p>If all regions have 0 weight, then no specific focus area
- * needs to be used by the camera device. If the focusing region is
- * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture
- * result metadata, the camera device will ignore the sections outside
- * the region and output the used sections in the result metadata.</p>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</p>
+ * <p>If all regions have 0 weight, then no specific metering area
+ * needs to be used by the camera device. If the metering region is
+ * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result metadata.</p>
*
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
- public static final Key<int[]> CONTROL_AF_REGIONS =
- new Key<int[]>("android.control.afRegions", int[].class);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Whether the camera device will trigger autofocus for this request.</p>
@@ -854,27 +850,27 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>List of areas to use for illuminant
* estimation.</p>
- * <p>Only used in AUTO mode.</p>
- * <p>Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined to be inclusive of the
- * specified coordinates.</p>
* <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
* ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
- * bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.</p>
- * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area
- * needs to be used by the camera device. If the AWB region is
- * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</p>
+ * <p>If all regions have 0 weight, then no specific metering area
+ * needs to be used by the camera device. If the metering region is
+ * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
* the camera device will ignore the sections outside the region and output the
* used sections in the result metadata.</p>
*
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
- public static final Key<int[]> CONTROL_AWB_REGIONS =
- new Key<int[]>("android.control.awbRegions", int[].class);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Information to the camera device 3A (auto-exposure,
@@ -1068,8 +1064,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
new Key<Integer>("android.hotPixel.mode", int.class);
/**
+ * <p>A location object to use when generating image GPS metadata.</p>
+ */
+ public static final Key<android.location.Location> JPEG_GPS_LOCATION =
+ new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class);
+
+ /**
* <p>GPS coordinates to include in output JPEG
* EXIF</p>
+ * @hide
*/
public static final Key<double[]> JPEG_GPS_COORDINATES =
new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
@@ -1077,6 +1080,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>32 characters describing GPS algorithm to
* include in EXIF</p>
+ * @hide
*/
public static final Key<String> JPEG_GPS_PROCESSING_METHOD =
new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
@@ -1084,6 +1088,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>Time GPS fix was made to include in
* EXIF</p>
+ * @hide
*/
public static final Key<Long> JPEG_GPS_TIMESTAMP =
new Key<Long>("android.jpeg.gpsTimestamp", long.class);
@@ -1116,6 +1121,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* but the captured JPEG will still be a valid image.</p>
* <p>When a jpeg image capture is issued, the thumbnail size selected should have
* the same aspect ratio as the jpeg image.</p>
+ * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect
+ * ratio, the camera device creates the thumbnail by cropping it from the primary image.
+ * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has
+ * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to
+ * generate the thumbnail image. The thumbnail image will always have a smaller Field
+ * Of View (FOV) than the primary image when aspect ratios differ.</p>
*/
public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE =
new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class);
@@ -1432,8 +1443,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>When set to OFF mode, no lens shading correction will be applied by the
* camera device, and an identity lens shading map data will be provided
* if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens
- * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>,
- * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map
+ * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>,
+ * the output android.statistics.lensShadingMap for this case will be an identity map
* shown below:</p>
* <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
* 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
@@ -1445,8 +1456,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>When set to other modes, lens shading correction will be applied by the
* camera device. Applications can request lens shading map data by setting
* {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide
- * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified
- * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}; the returned shading map data will be the one
+ * lens shading map data in android.statistics.lensShadingMap, with size specified
+ * by android.lens.info.shadingMapSize; the returned shading map data will be the one
* applied by the camera device for this capture request.</p>
* <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability
* of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in
@@ -1456,8 +1467,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AWB_MODE
- * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
* @see #SHADING_MODE_FAST
@@ -1498,10 +1507,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>Whether the camera device will output the lens
* shading map in output result metadata.</p>
* <p>When set to ON,
- * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in
+ * android.statistics.lensShadingMap must be provided in
* the output result metadata.</p>
- *
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
* @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF
* @see #STATISTICS_LENS_SHADING_MAP_MODE_ON
*/
@@ -1512,10 +1519,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>Tonemapping / contrast / gamma curve for the blue
* channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* CONTRAST_CURVE.</p>
- * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_BLUE =
new Key<float[]>("android.tonemap.curveBlue", float[].class);
@@ -1524,10 +1531,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>Tonemapping / contrast / gamma curve for the green
* channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* CONTRAST_CURVE.</p>
- * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_GREEN =
new Key<float[]>("android.tonemap.curveGreen", float[].class);
@@ -1537,7 +1544,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* CONTRAST_CURVE.</p>
* <p>Each channel's curve is defined by an array of control points:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} =
+ * <pre><code>android.tonemap.curveRed =
* [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ]
* 2 &lt;= N &lt;= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre>
* <p>These are sorted in order of increasing <code>Pin</code>; it is always
@@ -1553,15 +1560,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* only specify the red channel and the precision is limited to 4
* digits, for conciseness.</p>
* <p>Linear mapping:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ]
+ * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ]
* </code></pre>
* <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
* <p>Invert mapping:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ]
+ * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ]
* </code></pre>
* <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
* <p>Gamma 1/2.2 mapping, with 16 control points:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812,
* 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072,
* 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685,
@@ -1569,7 +1576,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* </code></pre>
* <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
* <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845,
* 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130,
* 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721,
@@ -1577,14 +1584,67 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* </code></pre>
* <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_RED =
new Key<float[]>("android.tonemap.curveRed", float[].class);
/**
+ * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}
+ * is CONTRAST_CURVE.</p>
+ * <p>The tonemapCurve consist of three curves for each of red, green, and blue
+ * channels respectively. The following example uses the red channel as an
+ * example. The same logic applies to green and blue channel.
+ * Each channel's curve is defined by an array of control points:</p>
+ * <pre><code>curveRed =
+ * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ]
+ * 2 &lt;= N &lt;= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre>
+ * <p>These are sorted in order of increasing <code>Pin</code>; it is always
+ * guaranteed that input values 0.0 and 1.0 are included in the list to
+ * define a complete mapping. For input values between control points,
+ * the camera device must linearly interpolate between the control
+ * points.</p>
+ * <p>Each curve can have an independent number of points, and the number
+ * of points can be less than max (that is, the request doesn't have to
+ * always provide a curve with number of points equivalent to
+ * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p>
+ * <p>A few examples, and their corresponding graphical mappings; these
+ * only specify the red channel and the precision is limited to 4
+ * digits, for conciseness.</p>
+ * <p>Linear mapping:</p>
+ * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ]
+ * </code></pre>
+ * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p>Invert mapping:</p>
+ * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ]
+ * </code></pre>
+ * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p>Gamma 1/2.2 mapping, with 16 control points:</p>
+ * <pre><code>curveRed = [
+ * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812),
+ * (0.2667, 0.5484), (0.3333, 0.6069), (0.4000, 0.6594), (0.4667, 0.7072),
+ * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685),
+ * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ]
+ * </code></pre>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
+ * <pre><code>curveRed = [
+ * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845),
+ * (0.2667, 0.5532), (0.3333, 0.6125), (0.4000, 0.6652), (0.4667, 0.7130),
+ * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721),
+ * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ]
+ * </code></pre>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ *
+ * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE =
+ new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.class);
+
+ /**
* <p>High-level global contrast/gamma/tonemapping control.</p>
* <p>When switching to an application-defined contrast curve by setting
* {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined
@@ -1600,8 +1660,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>This must be set to a valid mode in
* {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</p>
* <p>When using either FAST or HIGH_QUALITY, the camera device will
- * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed},
- * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.
+ * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.
* These values are always available, and as close as possible to the
* actually used nonlinear/nonglobal transforms.</p>
* <p>If a request is sent with CONTRAST_CURVE with the camera device's
@@ -1609,9 +1668,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* roughly the same.</p>
*
* @see CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES
- * @see CaptureRequest#TONEMAP_CURVE_BLUE
- * @see CaptureRequest#TONEMAP_CURVE_GREEN
- * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_CURVE
* @see CaptureRequest#TONEMAP_MODE
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 61d491b..7d07c92 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -24,9 +24,9 @@ import android.util.Rational;
import java.util.List;
/**
- * <p>The results of a single image capture from the image sensor.</p>
+ * <p>The subset of the results of a single image capture from the image sensor.</p>
*
- * <p>Contains the final configuration for the capture hardware (sensor, lens,
+ * <p>Contains a subset of the final configuration for the capture hardware (sensor, lens,
* flash), the processing pipeline, the control algorithms, and the output
* buffers.</p>
*
@@ -36,10 +36,15 @@ import java.util.List;
* capture. The result also includes additional metadata about the state of the
* camera device during the capture.</p>
*
- * <p>{@link CameraCharacteristics} objects are immutable.</p>
+ * <p>Not all properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()}
+ * are necessarily available. Some results are {@link CaptureResult partial} and will
+ * not have every key set. Only {@link TotalCaptureResult total} results are guaranteed to have
+ * every key available that was enabled by the request.</p>
+ *
+ * <p>{@link CaptureResult} objects are immutable.</p>
*
*/
-public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
+public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
private static final String TAG = "CaptureResult";
private static final boolean VERBOSE = false;
@@ -249,18 +254,19 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* Get the request associated with this result.
*
- * <p>Whenever a request is successfully captured, with
- * {@link CameraDevice.CaptureListener#onCaptureCompleted},
- * the {@code result}'s {@code getRequest()} will return that {@code request}.
+ * <p>Whenever a request has been fully or partially captured, with
+ * {@link CameraDevice.CaptureListener#onCaptureCompleted} or
+ * {@link CameraDevice.CaptureListener#onCaptureProgressed}, the {@code result}'s
+ * {@code getRequest()} will return that {@code request}.
* </p>
*
- * <p>In particular,
+ * <p>For example,
* <code><pre>cameraDevice.capture(someRequest, new CaptureListener() {
* {@literal @}Override
* void onCaptureCompleted(CaptureRequest myRequest, CaptureResult myResult) {
* assert(myResult.getRequest.equals(myRequest) == true);
* }
- * };
+ * }, null);
* </code></pre>
* </p>
*
@@ -297,6 +303,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @return int The ID for the sequence of requests that this capture result is a part of
*
* @see CameraDevice.CaptureListener#onCaptureSequenceCompleted
+ * @see CameraDevice.CaptureListener#onCaptureSequenceAborted
*/
public int getSequenceId() {
return mSequenceId;
@@ -376,30 +383,24 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
- public static final Key<Rational[]> COLOR_CORRECTION_TRANSFORM =
- new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
+ public static final Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM =
+ new Key<android.hardware.camera2.params.ColorSpaceTransform>("android.colorCorrection.transform", android.hardware.camera2.params.ColorSpaceTransform.class);
/**
* <p>Gains applying to Bayer raw color channels for
* white-balance.</p>
- * <p>The 4-channel white-balance gains are defined in
- * the order of <code>[R G_even G_odd B]</code>, where <code>G_even</code> is the gain
- * for green pixels on even rows of the output, and <code>G_odd</code>
- * is the gain for green pixels on the odd rows. if a HAL
- * does not support a separate gain for even/odd green channels,
- * it should use the <code>G_even</code> value, and write <code>G_odd</code> equal to
- * <code>G_even</code> in the output result metadata.</p>
- * <p>This array is either set by the camera device when the request
- * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not TRANSFORM_MATRIX, or
- * directly by the application in the request when the
- * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is TRANSFORM_MATRIX.</p>
- * <p>The output should be the gains actually applied by the camera device to
- * the current frame.</p>
+ * <p>These per-channel gains are either set by the camera device
+ * when the request {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is not
+ * TRANSFORM_MATRIX, or directly by the application in the
+ * request when the {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is
+ * TRANSFORM_MATRIX.</p>
+ * <p>The gains in the result metadata are the gains actually
+ * applied by the camera device to the current frame.</p>
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
*/
- public static final Key<float[]> COLOR_CORRECTION_GAINS =
- new Key<float[]>("android.colorCorrection.gains", float[].class);
+ public static final Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS =
+ new Key<android.hardware.camera2.params.RggbChannelVector>("android.colorCorrection.gains", android.hardware.camera2.params.RggbChannelVector.class);
/**
* <p>The desired setting for the camera device's auto-exposure
@@ -532,15 +533,16 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>List of areas to use for
* metering.</p>
- * <p>Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined to be inclusive of the
- * specified coordinates.</p>
* <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
* ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
- * bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.</p>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</p>
* <p>If all regions have 0 weight, then no specific metering area
* needs to be used by the camera device. If the metering region is
* outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
@@ -550,8 +552,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
- public static final Key<int[]> CONTROL_AE_REGIONS =
- new Key<int[]>("android.control.aeRegions", int[].class);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AE_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.aeRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Range over which fps can be adjusted to
@@ -561,8 +563,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CaptureRequest#SENSOR_EXPOSURE_TIME
*/
- public static final Key<int[]> CONTROL_AE_TARGET_FPS_RANGE =
- new Key<int[]>("android.control.aeTargetFpsRange", int[].class);
+ public static final Key<android.util.Range<Integer>> CONTROL_AE_TARGET_FPS_RANGE =
+ new Key<android.util.Range<Integer>>("android.control.aeTargetFpsRange", new TypeReference<android.util.Range<Integer>>() {{ }});
/**
* <p>Whether the camera device will trigger a precapture
@@ -805,26 +807,27 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>List of areas to use for focus
* estimation.</p>
- * <p>Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined to be inclusive of the
- * specified coordinates.</p>
* <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
* ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
- * bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.</p>
- * <p>If all regions have 0 weight, then no specific focus area
- * needs to be used by the camera device. If the focusing region is
- * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture
- * result metadata, the camera device will ignore the sections outside
- * the region and output the used sections in the result metadata.</p>
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</p>
+ * <p>If all regions have 0 weight, then no specific metering area
+ * needs to be used by the camera device. If the metering region is
+ * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * the camera device will ignore the sections outside the region and output the
+ * used sections in the result metadata.</p>
*
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
- public static final Key<int[]> CONTROL_AF_REGIONS =
- new Key<int[]>("android.control.afRegions", int[].class);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.afRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Whether the camera device will trigger autofocus for this request.</p>
@@ -1288,27 +1291,27 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>List of areas to use for illuminant
* estimation.</p>
- * <p>Only used in AUTO mode.</p>
- * <p>Each area is a rectangle plus weight: xmin, ymin,
- * xmax, ymax, weight. The rectangle is defined to be inclusive of the
- * specified coordinates.</p>
* <p>The coordinate system is based on the active pixel array,
* with (0,0) being the top-left pixel in the active pixel array, and
* ({@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.width - 1,
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}.height - 1) being the
- * bottom-right pixel in the active pixel array. The weight
- * should be nonnegative.</p>
- * <p>If all regions have 0 weight, then no specific auto-white balance (AWB) area
- * needs to be used by the camera device. If the AWB region is
- * outside the the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
+ * bottom-right pixel in the active pixel array.</p>
+ * <p>The weight must range from 0 to 1000, and represents a weight
+ * for every pixel in the area. This means that a large metering area
+ * with the same weight as a smaller area will have more effect in
+ * the metering result. Metering areas can partially overlap and the
+ * camera device will add the weights in the overlap region.</p>
+ * <p>If all regions have 0 weight, then no specific metering area
+ * needs to be used by the camera device. If the metering region is
+ * outside the used {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} returned in capture result metadata,
* the camera device will ignore the sections outside the region and output the
* used sections in the result metadata.</p>
*
* @see CaptureRequest#SCALER_CROP_REGION
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
- public static final Key<int[]> CONTROL_AWB_REGIONS =
- new Key<int[]>("android.control.awbRegions", int[].class);
+ public static final Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS =
+ new Key<android.hardware.camera2.params.MeteringRectangle[]>("android.control.awbRegions", android.hardware.camera2.params.MeteringRectangle[].class);
/**
* <p>Information to the camera device 3A (auto-exposure,
@@ -1649,8 +1652,15 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
new Key<Integer>("android.hotPixel.mode", int.class);
/**
+ * <p>A location object to use when generating image GPS metadata.</p>
+ */
+ public static final Key<android.location.Location> JPEG_GPS_LOCATION =
+ new Key<android.location.Location>("android.jpeg.gpsLocation", android.location.Location.class);
+
+ /**
* <p>GPS coordinates to include in output JPEG
* EXIF</p>
+ * @hide
*/
public static final Key<double[]> JPEG_GPS_COORDINATES =
new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
@@ -1658,6 +1668,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>32 characters describing GPS algorithm to
* include in EXIF</p>
+ * @hide
*/
public static final Key<String> JPEG_GPS_PROCESSING_METHOD =
new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
@@ -1665,6 +1676,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>Time GPS fix was made to include in
* EXIF</p>
+ * @hide
*/
public static final Key<Long> JPEG_GPS_TIMESTAMP =
new Key<Long>("android.jpeg.gpsTimestamp", long.class);
@@ -1697,6 +1709,12 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* but the captured JPEG will still be a valid image.</p>
* <p>When a jpeg image capture is issued, the thumbnail size selected should have
* the same aspect ratio as the jpeg image.</p>
+ * <p>If the thumbnail image aspect ratio differs from the JPEG primary image aspect
+ * ratio, the camera device creates the thumbnail by cropping it from the primary image.
+ * For example, if the primary image has 4:3 aspect ratio, the thumbnail image has
+ * 16:9 aspect ratio, the primary image will be cropped vertically (letterbox) to
+ * generate the thumbnail image. The thumbnail image will always have a smaller Field
+ * Of View (FOV) than the primary image when aspect ratios differ.</p>
*/
public static final Key<android.util.Size> JPEG_THUMBNAIL_SIZE =
new Key<android.util.Size>("android.jpeg.thumbnailSize", android.util.Size.class);
@@ -1786,8 +1804,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>If variable focus not supported, can still report
* fixed depth of field range</p>
*/
- public static final Key<float[]> LENS_FOCUS_RANGE =
- new Key<float[]>("android.lens.focusRange", float[].class);
+ public static final Key<android.util.Pair<Float,Float>> LENS_FOCUS_RANGE =
+ new Key<android.util.Pair<Float,Float>>("android.lens.focusRange", new TypeReference<android.util.Pair<Float,Float>>() {{ }});
/**
* <p>Sets whether the camera device uses optical image stabilization (OIS)
@@ -2154,8 +2172,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>When set to OFF mode, no lens shading correction will be applied by the
* camera device, and an identity lens shading map data will be provided
* if <code>{@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} == ON</code>. For example, for lens
- * shading map with size specified as <code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]</code>,
- * the output {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} for this case will be an identity map
+ * shading map with size specified as <code>android.lens.info.shadingMapSize = [ 4, 3 ]</code>,
+ * the output android.statistics.lensShadingMap for this case will be an identity map
* shown below:</p>
* <pre><code>[ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
* 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
@@ -2167,8 +2185,8 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>When set to other modes, lens shading correction will be applied by the
* camera device. Applications can request lens shading map data by setting
* {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} to ON, and then the camera device will provide
- * lens shading map data in {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap}, with size specified
- * by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}; the returned shading map data will be the one
+ * lens shading map data in android.statistics.lensShadingMap, with size specified
+ * by android.lens.info.shadingMapSize; the returned shading map data will be the one
* applied by the camera device for this capture request.</p>
* <p>The shading map data may depend on the AE and AWB statistics, therefore the reliability
* of the map data may be affected by the AE and AWB algorithms. When AE and AWB are in
@@ -2178,8 +2196,6 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AWB_MODE
- * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
* @see #SHADING_MODE_FAST
@@ -2269,13 +2285,59 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* The map is assumed to be bilinearly interpolated between the sample points.</p>
* <p>The channel order is [R, Geven, Godd, B], where Geven is the green
* channel for the even rows of a Bayer pattern, and Godd is the odd rows.
+ * The shading map is stored in a fully interleaved format.</p>
+ * <p>The shading map should have on the order of 30-40 rows and columns,
+ * and must be smaller than 64x64.</p>
+ * <p>As an example, given a very small map defined as:</p>
+ * <pre><code>width,height = [ 4, 3 ]
+ * values =
+ * [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2,
+ * 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3,
+ * 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0,
+ * 1.0, 1.0, 1.0, 1.0, 1.2, 1.3, 1.25, 1.2,
+ * 1.3, 1.2, 1.2, 1.3, 1.2, 1.15, 1.1, 1.2,
+ * 1.2, 1.1, 1.0, 1.2, 1.3, 1.15, 1.2, 1.3 ]
+ * </code></pre>
+ * <p>The low-resolution scaling map images for each channel are
+ * (displayed using nearest-neighbor interpolation):</p>
+ * <p><img alt="Red lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/red_shading.png" />
+ * <img alt="Green (even rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_e_shading.png" />
+ * <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
+ * <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
+ * <p>As a visualization only, inverting the full-color map to recover an
+ * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p>
+ * <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
+ *
+ * @see CaptureRequest#COLOR_CORRECTION_MODE
+ */
+ public static final Key<android.hardware.camera2.params.LensShadingMap> STATISTICS_LENS_SHADING_CORRECTION_MAP =
+ new Key<android.hardware.camera2.params.LensShadingMap>("android.statistics.lensShadingCorrectionMap", android.hardware.camera2.params.LensShadingMap.class);
+
+ /**
+ * <p>The shading map is a low-resolution floating-point map
+ * that lists the coefficients used to correct for vignetting, for each
+ * Bayer color channel.</p>
+ * <p>The least shaded section of the image should have a gain factor
+ * of 1; all other sections should have gains above 1.</p>
+ * <p>When {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} = TRANSFORM_MATRIX, the map
+ * must take into account the colorCorrection settings.</p>
+ * <p>The shading map is for the entire active pixel array, and is not
+ * affected by the crop region specified in the request. Each shading map
+ * entry is the value of the shading compensation map over a specific
+ * pixel on the sensor. Specifically, with a (N x M) resolution shading
+ * map, and an active pixel array size (W x H), shading map entry
+ * (x,y) ϵ (0 ... N-1, 0 ... M-1) is the value of the shading map at
+ * pixel ( ((W-1)/(N-1)) * x, ((H-1)/(M-1)) * y) for the four color channels.
+ * The map is assumed to be bilinearly interpolated between the sample points.</p>
+ * <p>The channel order is [R, Geven, Godd, B], where Geven is the green
+ * channel for the even rows of a Bayer pattern, and Godd is the odd rows.
* The shading map is stored in a fully interleaved format, and its size
- * is provided in the camera static metadata by {@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize}.</p>
+ * is provided in the camera static metadata by android.lens.info.shadingMapSize.</p>
* <p>The shading map should have on the order of 30-40 rows and columns,
* and must be smaller than 64x64.</p>
* <p>As an example, given a very small map defined as:</p>
- * <pre><code>{@link CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE android.lens.info.shadingMapSize} = [ 4, 3 ]
- * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} =
+ * <pre><code>android.lens.info.shadingMapSize = [ 4, 3 ]
+ * android.statistics.lensShadingMap =
* [ 1.3, 1.2, 1.15, 1.2, 1.2, 1.2, 1.15, 1.2,
* 1.1, 1.2, 1.2, 1.2, 1.3, 1.2, 1.3, 1.3,
* 1.2, 1.2, 1.25, 1.1, 1.1, 1.1, 1.1, 1.0,
@@ -2294,8 +2356,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
- * @see CameraCharacteristics#LENS_INFO_SHADING_MAP_SIZE
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
+ * @hide
*/
public static final Key<float[]> STATISTICS_LENS_SHADING_MAP =
new Key<float[]>("android.statistics.lensShadingMap", float[].class);
@@ -2395,17 +2456,15 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE
*/
- public static final Key<int[]> STATISTICS_HOT_PIXEL_MAP =
- new Key<int[]>("android.statistics.hotPixelMap", int[].class);
+ public static final Key<android.graphics.Point[]> STATISTICS_HOT_PIXEL_MAP =
+ new Key<android.graphics.Point[]>("android.statistics.hotPixelMap", android.graphics.Point[].class);
/**
* <p>Whether the camera device will output the lens
* shading map in output result metadata.</p>
* <p>When set to ON,
- * {@link CaptureResult#STATISTICS_LENS_SHADING_MAP android.statistics.lensShadingMap} must be provided in
+ * android.statistics.lensShadingMap must be provided in
* the output result metadata.</p>
- *
- * @see CaptureResult#STATISTICS_LENS_SHADING_MAP
* @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF
* @see #STATISTICS_LENS_SHADING_MAP_MODE_ON
*/
@@ -2416,10 +2475,10 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>Tonemapping / contrast / gamma curve for the blue
* channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* CONTRAST_CURVE.</p>
- * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_BLUE =
new Key<float[]>("android.tonemap.curveBlue", float[].class);
@@ -2428,10 +2487,10 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>Tonemapping / contrast / gamma curve for the green
* channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* CONTRAST_CURVE.</p>
- * <p>See {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} for more details.</p>
+ * <p>See android.tonemap.curveRed for more details.</p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_GREEN =
new Key<float[]>("android.tonemap.curveGreen", float[].class);
@@ -2441,7 +2500,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* channel, to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
* CONTRAST_CURVE.</p>
* <p>Each channel's curve is defined by an array of control points:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} =
+ * <pre><code>android.tonemap.curveRed =
* [ P0in, P0out, P1in, P1out, P2in, P2out, P3in, P3out, ..., PNin, PNout ]
* 2 &lt;= N &lt;= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre>
* <p>These are sorted in order of increasing <code>Pin</code>; it is always
@@ -2457,15 +2516,15 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* only specify the red channel and the precision is limited to 4
* digits, for conciseness.</p>
* <p>Linear mapping:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 0, 1.0, 1.0 ]
+ * <pre><code>android.tonemap.curveRed = [ 0, 0, 1.0, 1.0 ]
* </code></pre>
* <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
* <p>Invert mapping:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [ 0, 1.0, 1.0, 0 ]
+ * <pre><code>android.tonemap.curveRed = [ 0, 1.0, 1.0, 0 ]
* </code></pre>
* <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
* <p>Gamma 1/2.2 mapping, with 16 control points:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2920, 0.1333, 0.4002, 0.2000, 0.4812,
* 0.2667, 0.5484, 0.3333, 0.6069, 0.4000, 0.6594, 0.4667, 0.7072,
* 0.5333, 0.7515, 0.6000, 0.7928, 0.6667, 0.8317, 0.7333, 0.8685,
@@ -2473,7 +2532,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* </code></pre>
* <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
* <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
- * <pre><code>{@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed} = [
+ * <pre><code>android.tonemap.curveRed = [
* 0.0000, 0.0000, 0.0667, 0.2864, 0.1333, 0.4007, 0.2000, 0.4845,
* 0.2667, 0.5532, 0.3333, 0.6125, 0.4000, 0.6652, 0.4667, 0.7130,
* 0.5333, 0.7569, 0.6000, 0.7977, 0.6667, 0.8360, 0.7333, 0.8721,
@@ -2481,14 +2540,67 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* </code></pre>
* <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
*
- * @see CaptureRequest#TONEMAP_CURVE_RED
* @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
* @see CaptureRequest#TONEMAP_MODE
+ * @hide
*/
public static final Key<float[]> TONEMAP_CURVE_RED =
new Key<float[]>("android.tonemap.curveRed", float[].class);
/**
+ * <p>Tonemapping / contrast / gamma curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}
+ * is CONTRAST_CURVE.</p>
+ * <p>The tonemapCurve consist of three curves for each of red, green, and blue
+ * channels respectively. The following example uses the red channel as an
+ * example. The same logic applies to green and blue channel.
+ * Each channel's curve is defined by an array of control points:</p>
+ * <pre><code>curveRed =
+ * [ P0(in, out), P1(in, out), P2(in, out), P3(in, out), ..., PN(in, out) ]
+ * 2 &lt;= N &lt;= {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</code></pre>
+ * <p>These are sorted in order of increasing <code>Pin</code>; it is always
+ * guaranteed that input values 0.0 and 1.0 are included in the list to
+ * define a complete mapping. For input values between control points,
+ * the camera device must linearly interpolate between the control
+ * points.</p>
+ * <p>Each curve can have an independent number of points, and the number
+ * of points can be less than max (that is, the request doesn't have to
+ * always provide a curve with number of points equivalent to
+ * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p>
+ * <p>A few examples, and their corresponding graphical mappings; these
+ * only specify the red channel and the precision is limited to 4
+ * digits, for conciseness.</p>
+ * <p>Linear mapping:</p>
+ * <pre><code>curveRed = [ (0, 0), (1.0, 1.0) ]
+ * </code></pre>
+ * <p><img alt="Linear mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/linear_tonemap.png" /></p>
+ * <p>Invert mapping:</p>
+ * <pre><code>curveRed = [ (0, 1.0), (1.0, 0) ]
+ * </code></pre>
+ * <p><img alt="Inverting mapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/inverse_tonemap.png" /></p>
+ * <p>Gamma 1/2.2 mapping, with 16 control points:</p>
+ * <pre><code>curveRed = [
+ * (0.0000, 0.0000), (0.0667, 0.2920), (0.1333, 0.4002), (0.2000, 0.4812),
+ * (0.2667, 0.5484), (0.3333, 0.6069), (0.4000, 0.6594), (0.4667, 0.7072),
+ * (0.5333, 0.7515), (0.6000, 0.7928), (0.6667, 0.8317), (0.7333, 0.8685),
+ * (0.8000, 0.9035), (0.8667, 0.9370), (0.9333, 0.9691), (1.0000, 1.0000) ]
+ * </code></pre>
+ * <p><img alt="Gamma = 1/2.2 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/gamma_tonemap.png" /></p>
+ * <p>Standard sRGB gamma mapping, per IEC 61966-2-1:1999, with 16 control points:</p>
+ * <pre><code>curveRed = [
+ * (0.0000, 0.0000), (0.0667, 0.2864), (0.1333, 0.4007), (0.2000, 0.4845),
+ * (0.2667, 0.5532), (0.3333, 0.6125), (0.4000, 0.6652), (0.4667, 0.7130),
+ * (0.5333, 0.7569), (0.6000, 0.7977), (0.6667, 0.8360), (0.7333, 0.8721),
+ * (0.8000, 0.9063), (0.8667, 0.9389), (0.9333, 0.9701), (1.0000, 1.0000) ]
+ * </code></pre>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ *
+ * @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final Key<android.hardware.camera2.params.TonemapCurve> TONEMAP_CURVE =
+ new Key<android.hardware.camera2.params.TonemapCurve>("android.tonemap.curve", android.hardware.camera2.params.TonemapCurve.class);
+
+ /**
* <p>High-level global contrast/gamma/tonemapping control.</p>
* <p>When switching to an application-defined contrast curve by setting
* {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} to CONTRAST_CURVE, the curve is defined
@@ -2504,8 +2616,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>This must be set to a valid mode in
* {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}.</p>
* <p>When using either FAST or HIGH_QUALITY, the camera device will
- * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE_RED android.tonemap.curveRed},
- * {@link CaptureRequest#TONEMAP_CURVE_GREEN android.tonemap.curveGreen}, and {@link CaptureRequest#TONEMAP_CURVE_BLUE android.tonemap.curveBlue}.
+ * emit its own tonemap curve in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.
* These values are always available, and as close as possible to the
* actually used nonlinear/nonglobal transforms.</p>
* <p>If a request is sent with CONTRAST_CURVE with the camera device's
@@ -2513,9 +2624,7 @@ public final class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* roughly the same.</p>
*
* @see CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES
- * @see CaptureRequest#TONEMAP_CURVE_BLUE
- * @see CaptureRequest#TONEMAP_CURVE_GREEN
- * @see CaptureRequest#TONEMAP_CURVE_RED
+ * @see CaptureRequest#TONEMAP_CURVE
* @see CaptureRequest#TONEMAP_MODE
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
new file mode 100644
index 0000000..2647959
--- /dev/null
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>The total assembled results of a single image capture from the image sensor.</p>
+ *
+ * <p>Contains the final configuration for the capture hardware (sensor, lens,
+ * flash), the processing pipeline, the control algorithms, and the output
+ * buffers.</p>
+ *
+ * <p>A {@code TotalCaptureResult} is produced by a {@link CameraDevice} after processing a
+ * {@link CaptureRequest}. All properties listed for capture requests can also
+ * be queried on the capture result, to determine the final values used for
+ * capture. The result also includes additional metadata about the state of the
+ * camera device during the capture.</p>
+ *
+ * <p>All properties returned by {@link CameraCharacteristics#getAvailableCaptureResultKeys()}
+ * are available (that is {@link CaptureResult#get} will return non-{@code null}, if and only if
+ * that key that was enabled by the request. A few keys such as
+ * {@link CaptureResult#STATISTICS_FACES} are disabled by default unless enabled with a switch (such
+ * as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on
+ * a case-by-case basis.</p>
+ *
+ * <p>{@link TotalCaptureResult} objects are immutable.</p>
+ *
+ * @see CameraDevice.CaptureListener#onCaptureCompleted
+ */
+public final class TotalCaptureResult extends CaptureResult {
+
+ /**
+ * Takes ownership of the passed-in properties object
+ * @hide
+ */
+ public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent, int sequenceId) {
+ super(results, parent, sequenceId);
+ }
+
+ /**
+ * Creates a request-less result.
+ *
+ * <p><strong>For testing only.</strong></p>
+ * @hide
+ */
+ public TotalCaptureResult(CameraMetadataNative results, int sequenceId) {
+ super(results, sequenceId);
+ }
+
+ /**
+ * Get the read-only list of partial results that compose this total result.
+ *
+ * <p>The list is returned is unmodifiable; attempting to modify it will result in a
+ * {@code UnsupportedOperationException} being thrown.</p>
+ *
+ * <p>The list size will be inclusive between {@code 1} and
+ * {@link CameraCharacteristics#REQUEST_PARTIAL_RESULT_COUNT}, in ascending order
+ * of when {@link CameraDevice.CaptureListener#onCaptureProgressed} was invoked.</p>
+ *
+ * @return unmodifiable list of partial results
+ */
+ public List<CaptureResult> getPartialResults() {
+ // TODO
+ return Collections.unmodifiableList(null);
+ }
+}
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index b082a70..9a4c531 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -20,10 +20,13 @@ import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.utils.CameraBinderDecorator;
import android.hardware.camera2.utils.CameraRuntimeException;
import android.hardware.camera2.utils.LongParcelable;
@@ -72,6 +75,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>();
private final String mCameraId;
+ private final CameraCharacteristics mCharacteristics;
/**
* A list tracking request and its expected last frame.
@@ -150,13 +154,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
};
- public CameraDevice(String cameraId, StateListener listener, Handler handler) {
+ public CameraDevice(String cameraId, StateListener listener, Handler handler,
+ CameraCharacteristics characteristics) {
if (cameraId == null || listener == null || handler == null) {
throw new IllegalArgumentException("Null argument given");
}
mCameraId = cameraId;
mDeviceListener = listener;
mDeviceHandler = handler;
+ mCharacteristics = characteristics;
final int MAX_TAG_LEN = 23;
String tag = String.format("CameraDevice-JV-%s", mCameraId);
@@ -359,7 +365,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
holder.getListener().onCaptureSequenceCompleted(
CameraDevice.this,
requestId,
- (int)lastFrameNumber);
+ lastFrameNumber);
}
}
};
@@ -717,7 +723,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
holder.getListener().onCaptureSequenceCompleted(
CameraDevice.this,
requestId,
- (int)lastFrameNumber);
+ lastFrameNumber);
}
}
};
@@ -850,11 +856,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
@Override
public void onResultReceived(CameraMetadataNative result,
CaptureResultExtras resultExtras) throws RemoteException {
+
int requestId = resultExtras.getRequestId();
if (DEBUG) {
Log.v(TAG, "Received result frame " + resultExtras.getFrameNumber() + " for id "
+ requestId);
}
+
+
+ // TODO: Handle CameraCharacteristics access from CaptureResult correctly.
+ result.set(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE,
+ getCharacteristics().get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE));
+
final CaptureListenerHolder holder;
synchronized (mLock) {
holder = CameraDevice.this.mCaptureListenerMap.get(requestId);
@@ -888,12 +901,15 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
final CaptureRequest request = holder.getRequest(resultExtras.getSubsequenceId());
- final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId);
+
Runnable resultDispatch = null;
// Either send a partial result or the final capture completed result
if (quirkIsPartialResult) {
+ final CaptureResult resultAsCapture =
+ new CaptureResult(result, request, requestId);
+
// Partial result
resultDispatch = new Runnable() {
@Override
@@ -907,6 +923,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
}
};
} else {
+ final TotalCaptureResult resultAsCapture =
+ new TotalCaptureResult(result, request, requestId);
+
// Final capture result
resultDispatch = new Runnable() {
@Override
@@ -958,4 +977,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice {
return (mRemoteDevice == null);
}
}
+
+ private CameraCharacteristics getCharacteristics() {
+ return mCharacteristics;
+ }
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index ab2c49a..83aee5d 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -31,6 +31,7 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableColorSpaceTransform
import android.hardware.camera2.marshal.impl.MarshalQueryableEnum;
import android.hardware.camera2.marshal.impl.MarshalQueryableMeteringRectangle;
import android.hardware.camera2.marshal.impl.MarshalQueryableNativeByteToInteger;
+import android.hardware.camera2.marshal.impl.MarshalQueryablePair;
import android.hardware.camera2.marshal.impl.MarshalQueryableParcelable;
import android.hardware.camera2.marshal.impl.MarshalQueryablePrimitive;
import android.hardware.camera2.marshal.impl.MarshalQueryableRange;
@@ -43,13 +44,19 @@ import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfiguration
import android.hardware.camera2.marshal.impl.MarshalQueryableStreamConfigurationDuration;
import android.hardware.camera2.marshal.impl.MarshalQueryableString;
import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
import android.hardware.camera2.params.StreamConfiguration;
import android.hardware.camera2.params.StreamConfigurationDuration;
import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.TonemapCurve;
import android.hardware.camera2.utils.TypeReference;
+import android.location.Location;
+import android.location.LocationManager;
import android.os.Parcelable;
import android.os.Parcel;
import android.util.Log;
+import android.util.Pair;
+import android.util.Size;
import com.android.internal.util.Preconditions;
@@ -57,6 +64,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* Implementation of camera metadata marshal/unmarshal across Binder to
@@ -209,6 +217,37 @@ public class CameraMetadataNative implements Parcelable {
// this should be in sync with HAL_PIXEL_FORMAT_BLOB defined in graphics.h
public static final int NATIVE_JPEG_FORMAT = 0x21;
+ private static final String CELLID_PROCESS = "CELLID";
+ private static final String GPS_PROCESS = "GPS";
+
+ private static String translateLocationProviderToProcess(final String provider) {
+ if (provider == null) {
+ return null;
+ }
+ switch(provider) {
+ case LocationManager.GPS_PROVIDER:
+ return GPS_PROCESS;
+ case LocationManager.NETWORK_PROVIDER:
+ return CELLID_PROCESS;
+ default:
+ return null;
+ }
+ }
+
+ private static String translateProcessToLocationProvider(final String process) {
+ if (process == null) {
+ return null;
+ }
+ switch(process) {
+ case GPS_PROCESS:
+ return LocationManager.GPS_PROVIDER;
+ case CELLID_PROCESS:
+ return LocationManager.NETWORK_PROVIDER;
+ default:
+ return null;
+ }
+ }
+
public CameraMetadataNative() {
super();
mMetadataPtr = nativeAllocate();
@@ -297,9 +336,9 @@ public class CameraMetadataNative implements Parcelable {
public <T> T get(Key<T> key) {
Preconditions.checkNotNull(key, "key must not be null");
- T value = getOverride(key);
- if (value != null) {
- return value;
+ Pair<T, Boolean> override = getOverride(key);
+ if (override.second) {
+ return override.first;
}
return getBase(key);
@@ -409,23 +448,44 @@ public class CameraMetadataNative implements Parcelable {
ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
return marshaler.unmarshal(buffer);
}
-
// Need overwrite some metadata that has different definitions between native
// and managed sides.
@SuppressWarnings("unchecked")
- private <T> T getOverride(Key<T> key) {
+ private <T> Pair<T, Boolean> getOverride(Key<T> key) {
+ T value = null;
+ boolean override = true;
+
if (key.equals(CameraCharacteristics.SCALER_AVAILABLE_FORMATS)) {
- return (T) getAvailableFormats();
+ value = (T) getAvailableFormats();
} else if (key.equals(CaptureResult.STATISTICS_FACES)) {
- return (T) getFaces();
+ value = (T) getFaces();
} else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
- return (T) getFaceRectangles();
+ value = (T) getFaceRectangles();
} else if (key.equals(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)) {
- return (T) getStreamConfigurationMap();
+ value = (T) getStreamConfigurationMap();
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) {
+ value = (T) getMaxRegions(key);
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) {
+ value = (T) getMaxRegions(key);
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) {
+ value = (T) getMaxRegions(key);
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) {
+ value = (T) getMaxNumOutputs(key);
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) {
+ value = (T) getMaxNumOutputs(key);
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) {
+ value = (T) getMaxNumOutputs(key);
+ } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) {
+ value = (T) getTonemapCurve();
+ } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) {
+ value = (T) getGpsLocation();
+ } else if (key.equals(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP)) {
+ value = (T) getLensShadingMap();
+ } else {
+ override = false;
}
- // For other keys, get() falls back to getBase()
- return null;
+ return Pair.create(value, override);
}
private int[] getAvailableFormats() {
@@ -541,6 +601,62 @@ public class CameraMetadataNative implements Parcelable {
return fixedFaceRectangles;
}
+ private LensShadingMap getLensShadingMap() {
+ float[] lsmArray = getBase(CaptureResult.STATISTICS_LENS_SHADING_MAP);
+ if (lsmArray == null) {
+ Log.w(TAG, "getLensShadingMap - Lens shading map was null.");
+ return null;
+ }
+ Size s = get(CameraCharacteristics.LENS_INFO_SHADING_MAP_SIZE);
+ LensShadingMap map = new LensShadingMap(lsmArray, s.getHeight(), s.getWidth());
+ return map;
+ }
+
+ private Location getGpsLocation() {
+ String processingMethod = get(CaptureResult.JPEG_GPS_PROCESSING_METHOD);
+ Location l = new Location(translateProcessToLocationProvider(processingMethod));
+
+ double[] coords = get(CaptureResult.JPEG_GPS_COORDINATES);
+ Long timeStamp = get(CaptureResult.JPEG_GPS_TIMESTAMP);
+
+ if (timeStamp != null) {
+ l.setTime(timeStamp);
+ } else {
+ Log.w(TAG, "getGpsLocation - No timestamp for GPS location.");
+ }
+
+ if (coords != null) {
+ l.setLatitude(coords[0]);
+ l.setLongitude(coords[1]);
+ l.setAltitude(coords[2]);
+ } else {
+ Log.w(TAG, "getGpsLocation - No coordinates for GPS location");
+ }
+
+ return l;
+ }
+
+ private boolean setGpsLocation(Location l) {
+ if (l == null) {
+ return false;
+ }
+
+ double[] coords = { l.getLatitude(), l.getLongitude(), l.getAltitude() };
+ String processMethod = translateLocationProviderToProcess(l.getProvider());
+ long timestamp = l.getTime();
+
+ set(CaptureRequest.JPEG_GPS_TIMESTAMP, timestamp);
+ set(CaptureRequest.JPEG_GPS_COORDINATES, coords);
+
+ if (processMethod == null) {
+ Log.w(TAG, "setGpsLocation - No process method, Location is not from a GPS or NETWORK" +
+ "provider");
+ } else {
+ setBase(CaptureRequest.JPEG_GPS_PROCESSING_METHOD, processMethod);
+ }
+ return true;
+ }
+
private StreamConfigurationMap getStreamConfigurationMap() {
StreamConfiguration[] configurations = getBase(
CameraCharacteristics.SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
@@ -552,6 +668,63 @@ public class CameraMetadataNative implements Parcelable {
return new StreamConfigurationMap(configurations, minFrameDurations, stallDurations);
}
+ private <T> Integer getMaxRegions(Key<T> key) {
+ final int AE = 0;
+ final int AWB = 1;
+ final int AF = 2;
+
+ // The order of the elements is: (AE, AWB, AF)
+ int[] maxRegions = getBase(CameraCharacteristics.CONTROL_MAX_REGIONS);
+
+ if (maxRegions == null) {
+ return null;
+ }
+
+ if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AE)) {
+ return maxRegions[AE];
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AWB)) {
+ return maxRegions[AWB];
+ } else if (key.equals(CameraCharacteristics.CONTROL_MAX_REGIONS_AF)) {
+ return maxRegions[AF];
+ } else {
+ throw new AssertionError("Invalid key " + key);
+ }
+ }
+
+ private <T> Integer getMaxNumOutputs(Key<T> key) {
+ final int RAW = 0;
+ final int PROC = 1;
+ final int PROC_STALLING = 2;
+
+ // The order of the elements is: (raw, proc+nonstalling, proc+stalling)
+ int[] maxNumOutputs = getBase(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_STREAMS);
+
+ if (maxNumOutputs == null) {
+ return null;
+ }
+
+ if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_RAW)) {
+ return maxNumOutputs[RAW];
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC)) {
+ return maxNumOutputs[PROC];
+ } else if (key.equals(CameraCharacteristics.REQUEST_MAX_NUM_OUTPUT_PROC_STALLING)) {
+ return maxNumOutputs[PROC_STALLING];
+ } else {
+ throw new AssertionError("Invalid key " + key);
+ }
+ }
+
+ private <T> TonemapCurve getTonemapCurve() {
+ float[] red = getBase(CaptureRequest.TONEMAP_CURVE_RED);
+ float[] green = getBase(CaptureRequest.TONEMAP_CURVE_GREEN);
+ float[] blue = getBase(CaptureRequest.TONEMAP_CURVE_BLUE);
+ if (red == null || green == null || blue == null) {
+ return null;
+ }
+ TonemapCurve tc = new TonemapCurve(red, green, blue);
+ return tc;
+ }
+
private <T> void setBase(CameraCharacteristics.Key<T> key, T value) {
setBase(key.getNativeKey(), value);
}
@@ -591,8 +764,11 @@ public class CameraMetadataNative implements Parcelable {
return setAvailableFormats((int[]) value);
} else if (key.equals(CaptureResult.STATISTICS_FACE_RECTANGLES)) {
return setFaceRectangles((Rect[]) value);
+ } else if (key.equals(CaptureRequest.TONEMAP_CURVE)) {
+ return setTonemapCurve((TonemapCurve) value);
+ } else if (key.equals(CaptureResult.JPEG_GPS_LOCATION)) {
+ return setGpsLocation((Location) value);
}
-
// For other keys, set() falls back to setBase().
return false;
}
@@ -646,6 +822,24 @@ public class CameraMetadataNative implements Parcelable {
return true;
}
+ private <T> boolean setTonemapCurve(TonemapCurve tc) {
+ if (tc == null) {
+ return false;
+ }
+
+ float[][] curve = new float[3][];
+ for (int i = TonemapCurve.CHANNEL_RED; i <= TonemapCurve.CHANNEL_BLUE; i++) {
+ int pointCount = tc.getPointCount(i);
+ curve[i] = new float[pointCount * TonemapCurve.POINT_SIZE];
+ tc.copyColorCurve(i, curve[i], 0);
+ }
+ setBase(CaptureRequest.TONEMAP_CURVE_RED, curve[0]);
+ setBase(CaptureRequest.TONEMAP_CURVE_GREEN, curve[1]);
+ setBase(CaptureRequest.TONEMAP_CURVE_BLUE, curve[2]);
+
+ return true;
+ }
+
private long mMetadataPtr; // native CameraMetadata*
private native long nativeAllocate();
@@ -813,6 +1007,7 @@ public class CameraMetadataNative implements Parcelable {
new MarshalQueryableString(),
new MarshalQueryableReprocessFormatsMap(),
new MarshalQueryableRange(),
+ new MarshalQueryablePair(),
new MarshalQueryableMeteringRectangle(),
new MarshalQueryableColorSpaceTransform(),
new MarshalQueryableStreamConfiguration(),
diff --git a/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java
new file mode 100644
index 0000000..0a9935d
--- /dev/null
+++ b/core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.camera2.marshal.impl;
+
+import android.hardware.camera2.marshal.Marshaler;
+import android.hardware.camera2.marshal.MarshalQueryable;
+import android.hardware.camera2.marshal.MarshalRegistry;
+import android.hardware.camera2.utils.TypeReference;
+import android.util.Pair;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.ByteBuffer;
+
+/**
+ * Marshal {@link Pair} to/from any native type
+ */
+public class MarshalQueryablePair<T1, T2>
+ implements MarshalQueryable<Pair<T1, T2>> {
+
+ private class MarshalerPair extends Marshaler<Pair<T1, T2>> {
+ private final Class<? super Pair<T1, T2>> mClass;
+ private final Constructor<Pair<T1, T2>> mConstructor;
+ /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */
+ private final Marshaler<T1> mNestedTypeMarshalerFirst;
+ /** Marshal the {@code T1} inside of {@code Pair<T1, T2>} */
+ private final Marshaler<T2> mNestedTypeMarshalerSecond;
+
+ @SuppressWarnings("unchecked")
+ protected MarshalerPair(TypeReference<Pair<T1, T2>> typeReference,
+ int nativeType) {
+ super(MarshalQueryablePair.this, typeReference, nativeType);
+
+ mClass = typeReference.getRawType();
+
+ /*
+ * Lookup the actual type arguments, e.g. Pair<Integer, Float> --> [Integer, Float]
+ * and then get the marshalers for that managed type.
+ */
+ ParameterizedType paramType;
+ try {
+ paramType = (ParameterizedType) typeReference.getType();
+ } catch (ClassCastException e) {
+ throw new AssertionError("Raw use of Pair is not supported", e);
+ }
+
+ // Get type marshaler for T1
+ {
+ Type actualTypeArgument = paramType.getActualTypeArguments()[0];
+
+ TypeReference<?> actualTypeArgToken =
+ TypeReference.createSpecializedTypeReference(actualTypeArgument);
+
+ mNestedTypeMarshalerFirst = (Marshaler<T1>)MarshalRegistry.getMarshaler(
+ actualTypeArgToken, mNativeType);
+ }
+ // Get type marshaler for T2
+ {
+ Type actualTypeArgument = paramType.getActualTypeArguments()[1];
+
+ TypeReference<?> actualTypeArgToken =
+ TypeReference.createSpecializedTypeReference(actualTypeArgument);
+
+ mNestedTypeMarshalerSecond = (Marshaler<T2>)MarshalRegistry.getMarshaler(
+ actualTypeArgToken, mNativeType);
+ }
+ try {
+ mConstructor = (Constructor<Pair<T1, T2>>)mClass.getConstructor(
+ Object.class, Object.class);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public void marshal(Pair<T1, T2> value, ByteBuffer buffer) {
+ if (value.first == null) {
+ throw new UnsupportedOperationException("Pair#first must not be null");
+ } else if (value.second == null) {
+ throw new UnsupportedOperationException("Pair#second must not be null");
+ }
+
+ mNestedTypeMarshalerFirst.marshal(value.first, buffer);
+ mNestedTypeMarshalerSecond.marshal(value.second, buffer);
+ }
+
+ @Override
+ public Pair<T1, T2> unmarshal(ByteBuffer buffer) {
+ T1 first = mNestedTypeMarshalerFirst.unmarshal(buffer);
+ T2 second = mNestedTypeMarshalerSecond.unmarshal(buffer);
+
+ try {
+ return mConstructor.newInstance(first, second);
+ } catch (InstantiationException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (IllegalArgumentException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public int getNativeSize() {
+ int firstSize = mNestedTypeMarshalerFirst.getNativeSize();
+ int secondSize = mNestedTypeMarshalerSecond.getNativeSize();
+
+ if (firstSize != NATIVE_SIZE_DYNAMIC && secondSize != NATIVE_SIZE_DYNAMIC) {
+ return firstSize + secondSize;
+ } else {
+ return NATIVE_SIZE_DYNAMIC;
+ }
+ }
+
+ @Override
+ public int calculateMarshalSize(Pair<T1, T2> value) {
+ int nativeSize = getNativeSize();
+
+ if (nativeSize != NATIVE_SIZE_DYNAMIC) {
+ return nativeSize;
+ } else {
+ int firstSize = mNestedTypeMarshalerFirst.calculateMarshalSize(value.first);
+ int secondSize = mNestedTypeMarshalerSecond.calculateMarshalSize(value.second);
+
+ return firstSize + secondSize;
+ }
+ }
+ }
+
+ @Override
+ public Marshaler<Pair<T1, T2>> createMarshaler(TypeReference<Pair<T1, T2>> managedType,
+ int nativeType) {
+ return new MarshalerPair(managedType, nativeType);
+ }
+
+ @Override
+ public boolean isTypeMappingSupported(TypeReference<Pair<T1, T2>> managedType, int nativeType) {
+ return (Pair.class.equals(managedType.getRawType()));
+ }
+
+}
diff --git a/core/java/android/hardware/camera2/params/ColorSpaceTransform.java b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java
index fa8c8ea..b4289db 100644
--- a/core/java/android/hardware/camera2/params/ColorSpaceTransform.java
+++ b/core/java/android/hardware/camera2/params/ColorSpaceTransform.java
@@ -139,8 +139,8 @@ public final class ColorSpaceTransform {
throw new IllegalArgumentException("row out of range");
}
- int numerator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_NUMERATOR];
- int denominator = mElements[row * ROWS * RATIONAL_SIZE + column + OFFSET_DENOMINATOR];
+ int numerator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_NUMERATOR];
+ int denominator = mElements[(row * COLUMNS + column) * RATIONAL_SIZE + OFFSET_DENOMINATOR];
return new Rational(numerator, denominator);
}
@@ -162,7 +162,7 @@ public final class ColorSpaceTransform {
public void copyElements(Rational[] destination, int offset) {
checkArgumentNonnegative(offset, "offset must not be negative");
checkNotNull(destination, "destination must not be null");
- if (destination.length + offset < COUNT) {
+ if (destination.length - offset < COUNT) {
throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
}
@@ -197,7 +197,7 @@ public final class ColorSpaceTransform {
public void copyElements(int[] destination, int offset) {
checkArgumentNonnegative(offset, "offset must not be negative");
checkNotNull(destination, "destination must not be null");
- if (destination.length + offset < COUNT_INT) {
+ if (destination.length - offset < COUNT_INT) {
throw new ArrayIndexOutOfBoundsException("destination too small to fit elements");
}
diff --git a/core/java/android/hardware/camera2/params/LensShadingMap.java b/core/java/android/hardware/camera2/params/LensShadingMap.java
index b328f57..9bbc33a 100644
--- a/core/java/android/hardware/camera2/params/LensShadingMap.java
+++ b/core/java/android/hardware/camera2/params/LensShadingMap.java
@@ -28,7 +28,7 @@ import java.util.Arrays;
/**
* Immutable class for describing a {@code 4 x N x M} lens shading map of floats.
*
- * @see CameraCharacteristics#LENS_SHADING_MAP
+ * @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
*/
public final class LensShadingMap {
@@ -62,12 +62,12 @@ public final class LensShadingMap {
public LensShadingMap(final float[] elements, final int rows, final int columns) {
mRows = checkArgumentPositive(rows, "rows must be positive");
- mColumns = checkArgumentPositive(rows, "columns must be positive");
+ mColumns = checkArgumentPositive(columns, "columns must be positive");
mElements = checkNotNull(elements, "elements must not be null");
if (elements.length != getGainFactorCount()) {
throw new IllegalArgumentException("elements must be " + getGainFactorCount() +
- " length");
+ " length, received " + elements.length);
}
// Every element must be finite and >= 1.0f
@@ -242,4 +242,4 @@ public final class LensShadingMap {
private final int mRows;
private final int mColumns;
private final float[] mElements;
-};
+}
diff --git a/core/java/android/hardware/camera2/params/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java
index a26c57d..93fd053 100644
--- a/core/java/android/hardware/camera2/params/MeteringRectangle.java
+++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package android.hardware.camera2.params;
import android.util.Size;
@@ -25,22 +26,50 @@ import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.utils.HashCodeHelpers;
/**
- * An immutable class to represent a rectangle {@code (x,y, width, height)} with an
- * additional weight component.
- *
- * </p>The rectangle is defined to be inclusive of the specified coordinates.</p>
- *
- * <p>When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel
+ * An immutable class to represent a rectangle {@code (x, y, width, height)} with an additional
+ * weight component.
+ * <p>
+ * The rectangle is defined to be inclusive of the specified coordinates.
+ * </p>
+ * <p>
+ * When used with a {@link CaptureRequest}, the coordinate system is based on the active pixel
* array, with {@code (0,0)} being the top-left pixel in the
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE active pixel array}, and
* {@code (android.sensor.info.activeArraySize.width - 1,
- * android.sensor.info.activeArraySize.height - 1)}
- * being the bottom-right pixel in the active pixel array.
+ * android.sensor.info.activeArraySize.height - 1)} being the bottom-right pixel in the active pixel
+ * array.
+ * </p>
+ * <p>
+ * The weight must range from {@value #METERING_WEIGHT_MIN} to {@value #METERING_WEIGHT_MAX}
+ * inclusively, and represents a weight for every pixel in the area. This means that a large
+ * metering area with the same weight as a smaller area will have more effect in the metering
+ * result. Metering areas can partially overlap and the camera device will add the weights in the
+ * overlap rectangle.
+ * </p>
+ * <p>
+ * If all rectangles have 0 weight, then no specific metering area needs to be used by the camera
+ * device. If the metering rectangle is outside the used android.scaler.cropRegion returned in
+ * capture result metadata, the camera device will ignore the sections outside the rectangle and
+ * output the used sections in the result metadata.
* </p>
- *
- * <p>The metering weight is nonnegative.</p>
*/
public final class MeteringRectangle {
+ /**
+ * The minimum value of valid metering weight.
+ */
+ public static final int METERING_WEIGHT_MIN = 0;
+
+ /**
+ * The maximum value of valid metering weight.
+ */
+ public static final int METERING_WEIGHT_MAX = 1000;
+
+ /**
+ * Weights set to this value will cause the camera device to ignore this rectangle.
+ * If all metering rectangles are weighed with 0, the camera device will choose its own metering
+ * rectangles.
+ */
+ public static final int METERING_WEIGHT_DONT_CARE = 0;
private final int mX;
private final int mY;
@@ -55,16 +84,17 @@ public final class MeteringRectangle {
* @param y coordinate >= 0
* @param width width >= 0
* @param height height >= 0
- * @param meteringWeight weight >= 0
- *
- * @throws IllegalArgumentException if any of the parameters were non-negative
+ * @param meteringWeight weight between {@value #METERING_WEIGHT_MIN} and
+ * {@value #METERING_WEIGHT_MAX} inclusively
+ * @throws IllegalArgumentException if any of the parameters were negative
*/
public MeteringRectangle(int x, int y, int width, int height, int meteringWeight) {
mX = checkArgumentNonnegative(x, "x must be nonnegative");
mY = checkArgumentNonnegative(y, "y must be nonnegative");
mWidth = checkArgumentNonnegative(width, "width must be nonnegative");
mHeight = checkArgumentNonnegative(height, "height must be nonnegative");
- mWeight = checkArgumentNonnegative(meteringWeight, "meteringWeight must be nonnegative");
+ mWeight = checkArgumentInRange(
+ meteringWeight, METERING_WEIGHT_MIN, METERING_WEIGHT_MAX, "meteringWeight");
}
/**
@@ -74,7 +104,7 @@ public final class MeteringRectangle {
* @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0
* @param meteringWeight weight >= 0
*
- * @throws IllegalArgumentException if any of the parameters were non-negative
+ * @throws IllegalArgumentException if any of the parameters were negative
* @throws NullPointerException if any of the arguments were null
*/
public MeteringRectangle(Point xy, Size dimensions, int meteringWeight) {
@@ -94,7 +124,7 @@ public final class MeteringRectangle {
* @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0
* @param meteringWeight weight >= 0
*
- * @throws IllegalArgumentException if any of the parameters were non-negative
+ * @throws IllegalArgumentException if any of the parameters were negative
* @throws NullPointerException if any of the arguments were null
*/
public MeteringRectangle(Rect rect, int meteringWeight) {
@@ -210,7 +240,7 @@ public final class MeteringRectangle {
&& mY == other.mY
&& mWidth == other.mWidth
&& mHeight == other.mHeight
- && mWidth == other.mWidth);
+ && mWeight == other.mWeight);
}
/**
diff --git a/core/java/android/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index a71a74d..723eda1 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -194,6 +194,8 @@ public final class HdmiCec {
DEVICE_RECORDER, // ADDR_RECORDER_3
DEVICE_TUNER, // ADDR_TUNER_4
DEVICE_PLAYBACK, // ADDR_PLAYBACK_3
+ DEVICE_RESERVED,
+ DEVICE_RESERVED,
DEVICE_TV, // ADDR_SPECIFIC_USE
};
@@ -210,6 +212,8 @@ public final class HdmiCec {
"Recorder_3",
"Tuner_4",
"Playback_3",
+ "Reserved_1",
+ "Reserved_2",
"Secondary_TV",
};
diff --git a/core/java/android/hardware/hdmi/HdmiCecClient.java b/core/java/android/hardware/hdmi/HdmiCecClient.java
index cd86cd8..dcb3624 100644
--- a/core/java/android/hardware/hdmi/HdmiCecClient.java
+++ b/core/java/android/hardware/hdmi/HdmiCecClient.java
@@ -69,44 +69,28 @@ public final class HdmiCecClient {
* Send &lt;Active Source&gt; message.
*/
public void sendActiveSource() {
- try {
- mService.sendActiveSource(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendActiveSource threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send &lt;Inactive Source&gt; message.
*/
public void sendInactiveSource() {
- try {
- mService.sendInactiveSource(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendInactiveSource threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send &lt;Text View On&gt; message.
*/
public void sendTextViewOn() {
- try {
- mService.sendTextViewOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendTextViewOn threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send &lt;Image View On&gt; message.
*/
public void sendImageViewOn() {
- try {
- mService.sendImageViewOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendImageViewOn threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
@@ -116,11 +100,7 @@ public final class HdmiCecClient {
* {@link HdmiCec#ADDR_TV}.
*/
public void sendGiveDevicePowerStatus(int address) {
- try {
- mService.sendGiveDevicePowerStatus(mBinder, address);
- } catch (RemoteException e) {
- Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
@@ -133,11 +113,7 @@ public final class HdmiCecClient {
* @return true if TV is on; otherwise false.
*/
public boolean isTvOn() {
- try {
- return mService.isTvOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "isTvOn threw exception ", e);
- }
- return false;
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
+ return true;
}
}
diff --git a/core/java/android/hardware/hdmi/HdmiCecManager.java b/core/java/android/hardware/hdmi/HdmiCecManager.java
index 10b058c..03c46d8 100644
--- a/core/java/android/hardware/hdmi/HdmiCecManager.java
+++ b/core/java/android/hardware/hdmi/HdmiCecManager.java
@@ -45,15 +45,7 @@ public final class HdmiCecManager {
* @return {@link HdmiCecClient} instance. {@code null} on failure.
*/
public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) {
- if (mService == null) {
- return null;
- }
- try {
- IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener));
- return HdmiCecClient.create(mService, b);
- } catch (RemoteException e) {
- return null;
- }
+ return HdmiCecClient.create(mService, null);
}
private IHdmiCecListener getListenerWrapper(final HdmiCecClient.Listener listener) {
diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java
index a8aa376..62fa279 100644
--- a/core/java/android/hardware/hdmi/HdmiCecMessage.java
+++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java
@@ -46,7 +46,7 @@ public final class HdmiCecMessage implements Parcelable {
public HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
mSource = source;
mDestination = destination;
- mOpcode = opcode;
+ mOpcode = opcode & 0xFF;
mParams = Arrays.copyOf(params, params.length);
}
diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
index 83da29a..f0bd237 100644
--- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
@@ -90,7 +90,7 @@ public final class HdmiPlaybackClient {
public void queryDisplayStatus(DisplayStatusCallback callback) {
// TODO: PendingResult.
try {
- mService.oneTouchPlay(getCallbackWrapper(callback));
+ mService.queryDisplayStatus(getCallbackWrapper(callback));
} catch (RemoteException e) {
Log.e(TAG, "queryDisplayStatus threw exception ", e);
}
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 06d8e4a..857e335 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -69,6 +69,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80;
final WeakReference<AbstractInputMethodService> mTarget;
+ final Context mContext;
final HandlerCaller mCaller;
final WeakReference<InputMethod> mInputMethod;
final int mTargetSdkVersion;
@@ -111,8 +112,8 @@ class IInputMethodWrapper extends IInputMethod.Stub
public IInputMethodWrapper(AbstractInputMethodService context,
InputMethod inputMethod) {
mTarget = new WeakReference<AbstractInputMethodService>(context);
- mCaller = new HandlerCaller(context.getApplicationContext(), null,
- this, true /*asyncHandler*/);
+ mContext = context.getApplicationContext();
+ mCaller = new HandlerCaller(mContext, null, this, true /*asyncHandler*/);
mInputMethod = new WeakReference<InputMethod>(inputMethod);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
}
@@ -186,7 +187,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs)msg.obj;
inputMethod.createSession(new InputMethodSessionCallbackWrapper(
- mCaller.mContext, (InputChannel)args.arg1,
+ mContext, (InputChannel)args.arg1,
(IInputSessionCallback)args.arg2));
args.recycle();
return;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4bccaf1..3417de1 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -39,6 +39,7 @@ import android.text.method.MovementMethod;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.view.Gravity;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -679,7 +680,7 @@ public class InputMethodService extends AbstractInputMethodService {
mInflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState,
- false);
+ WindowManager.LayoutParams.TYPE_INPUT_METHOD, Gravity.BOTTOM, false);
if (mHardwareAccelerated) {
mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index a9bace1..795117e 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -37,6 +37,8 @@ public class SoftInputWindow extends Dialog {
final Callback mCallback;
final KeyEvent.Callback mKeyEventCallback;
final KeyEvent.DispatcherState mDispatcherState;
+ final int mWindowType;
+ final int mGravity;
final boolean mTakesFocus;
private final Rect mBounds = new Rect();
@@ -64,12 +66,14 @@ public class SoftInputWindow extends Dialog {
*/
public SoftInputWindow(Context context, String name, int theme, Callback callback,
KeyEvent.Callback keyEventCallback, KeyEvent.DispatcherState dispatcherState,
- boolean takesFocus) {
+ int windowType, int gravity, boolean takesFocus) {
super(context, theme);
mName = name;
mCallback = callback;
mKeyEventCallback = keyEventCallback;
mDispatcherState = dispatcherState;
+ mWindowType = windowType;
+ mGravity = gravity;
mTakesFocus = takesFocus;
initDockWindow();
}
@@ -97,47 +101,6 @@ public class SoftInputWindow extends Dialog {
}
/**
- * Get the size of the DockWindow.
- *
- * @return If the DockWindow sticks to the top or bottom of the screen, the
- * return value is the height of the DockWindow, and its width is
- * equal to the width of the screen; If the DockWindow sticks to the
- * left or right of the screen, the return value is the width of the
- * DockWindow, and its height is equal to the height of the screen.
- */
- public int getSize() {
- WindowManager.LayoutParams lp = getWindow().getAttributes();
-
- if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
- return lp.height;
- } else {
- return lp.width;
- }
- }
-
- /**
- * Set the size of the DockWindow.
- *
- * @param size If the DockWindow sticks to the top or bottom of the screen,
- * <var>size</var> is the height of the DockWindow, and its width is
- * equal to the width of the screen; If the DockWindow sticks to the
- * left or right of the screen, <var>size</var> is the width of the
- * DockWindow, and its height is equal to the height of the screen.
- */
- public void setSize(int size) {
- WindowManager.LayoutParams lp = getWindow().getAttributes();
-
- if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
- lp.width = -1;
- lp.height = size;
- } else {
- lp.width = size;
- lp.height = -1;
- }
- getWindow().setAttributes(lp);
- }
-
- /**
* Set which boundary of the screen the DockWindow sticks to.
*
* @param gravity The boundary of the screen to stick. See {#link
@@ -147,18 +110,22 @@ public class SoftInputWindow extends Dialog {
*/
public void setGravity(int gravity) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
-
- boolean oldIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
-
lp.gravity = gravity;
+ updateWidthHeight(lp);
+ getWindow().setAttributes(lp);
+ }
- boolean newIsVertical = (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM);
+ public int getGravity() {
+ return getWindow().getAttributes().gravity;
+ }
- if (oldIsVertical != newIsVertical) {
- int tmp = lp.width;
- lp.width = lp.height;
- lp.height = tmp;
- getWindow().setAttributes(lp);
+ private void updateWidthHeight(WindowManager.LayoutParams lp) {
+ if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ } else {
+ lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ lp.height = WindowManager.LayoutParams.MATCH_PARENT;
}
}
@@ -201,14 +168,11 @@ public class SoftInputWindow extends Dialog {
private void initDockWindow() {
WindowManager.LayoutParams lp = getWindow().getAttributes();
- lp.type = WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+ lp.type = mWindowType;
lp.setTitle(mName);
- lp.gravity = Gravity.BOTTOM;
- lp.width = -1;
- // Let the input method window's orientation follow sensor based rotation
- // Turn this off for now, it is very problematic.
- //lp.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER;
+ lp.gravity = mGravity;
+ updateWidthHeight(lp);
getWindow().setAttributes(lp);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2f2aba3..a48a388 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -40,6 +40,7 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Protocol;
import java.net.InetAddress;
@@ -807,11 +808,34 @@ public class ConnectivityManager {
* @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
*/
public int startUsingNetworkFeature(int networkType, String feature) {
- try {
- return mService.startUsingNetworkFeature(networkType, feature,
- new Binder());
- } catch (RemoteException e) {
- return -1;
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
+ feature);
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ NetworkRequest request = null;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) {
+ Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
+ renewRequestLocked(l);
+ if (l.currentNetwork != null) {
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ } else {
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+ }
+
+ request = requestNetworkForFeatureLocked(netCap);
+ }
+ if (request != null) {
+ Log.d(TAG, "starting startUsingNeworkFeature for request " + request);
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ Log.d(TAG, " request Failed");
+ return PhoneConstants.APN_REQUEST_FAILED;
}
}
@@ -831,11 +855,172 @@ public class ConnectivityManager {
* @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
*/
public int stopUsingNetworkFeature(int networkType, String feature) {
- try {
- return mService.stopUsingNetworkFeature(networkType, feature);
- } catch (RemoteException e) {
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
+ feature);
return -1;
}
+
+ NetworkRequest request = removeRequestForFeature(netCap);
+ if (request != null) {
+ Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature);
+ releaseNetworkRequest(request);
+ }
+ return 1;
+ }
+
+ private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
+ if (networkType == TYPE_MOBILE) {
+ int cap = -1;
+ if ("enableMMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_MMS;
+ } else if ("enableSUPL".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_SUPL;
+ } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_DUN;
+ } else if ("enableHIPRI".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_INTERNET;
+ } else if ("enableFOTA".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_FOTA;
+ } else if ("enableIMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_IMS;
+ } else if ("enableCBS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_CBS;
+ } else {
+ return null;
+ }
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ netCap.addNetworkCapability(cap);
+ return netCap;
+ } else if (networkType == TYPE_WIFI) {
+ if ("p2p".equals(feature)) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+ netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+ return netCap;
+ }
+ }
+ return null;
+ }
+
+ private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
+ if (netCap == null) return TYPE_NONE;
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ return TYPE_MOBILE_CBS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ return TYPE_MOBILE_IMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+ return TYPE_MOBILE_FOTA;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+ return TYPE_MOBILE_DUN;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ return TYPE_MOBILE_SUPL;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ return TYPE_MOBILE_MMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ return TYPE_MOBILE_HIPRI;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) {
+ return TYPE_WIFI_P2P;
+ }
+ return TYPE_NONE;
+ }
+
+ private static class LegacyRequest {
+ NetworkCapabilities networkCapabilities;
+ NetworkRequest networkRequest;
+ int expireSequenceNumber;
+ Network currentNetwork;
+ int delay = -1;
+ NetworkCallbackListener networkCallbackListener = new NetworkCallbackListener() {
+ @Override
+ public void onAvailable(NetworkRequest request, Network network) {
+ currentNetwork = network;
+ Log.d(TAG, "startUsingNetworkFeature got Network:" + network);
+ network.bindProcessForHostResolution();
+ }
+ @Override
+ public void onLost(NetworkRequest request, Network network) {
+ if (network.equals(currentNetwork)) {
+ currentNetwork = null;
+ network.unbindProcessForHostResolution();
+ }
+ Log.d(TAG, "startUsingNetworkFeature lost Network:" + network);
+ }
+ };
+ }
+
+ private HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
+ new HashMap<NetworkCapabilities, LegacyRequest>();
+
+ private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) return l.networkRequest;
+ }
+ return null;
+ }
+
+ private void renewRequestLocked(LegacyRequest l) {
+ l.expireSequenceNumber++;
+ Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber);
+ sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay);
+ }
+
+ private void expireRequest(NetworkCapabilities netCap, int sequenceNum) {
+ int ourSeqNum = -1;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l == null) return;
+ ourSeqNum = l.expireSequenceNumber;
+ if (l.expireSequenceNumber == sequenceNum) {
+ releaseNetworkRequest(l.networkRequest);
+ sLegacyRequests.remove(netCap);
+ }
+ }
+ Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
+ }
+
+ private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
+ int delay = -1;
+ int type = legacyTypeForNetworkCapabilities(netCap);
+ try {
+ delay = mService.getRestoreDefaultNetworkDelay(type);
+ } catch (RemoteException e) {}
+ LegacyRequest l = new LegacyRequest();
+ l.networkCapabilities = netCap;
+ l.delay = delay;
+ l.expireSequenceNumber = 0;
+ l.networkRequest = sendRequestForNetwork(netCap, l.networkCallbackListener, 0,
+ REQUEST, type);
+ if (l.networkRequest == null) return null;
+ sLegacyRequests.put(netCap, l);
+ sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
+ return l.networkRequest;
+ }
+
+ private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
+ if (delay >= 0) {
+ Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
+ Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+ sCallbackHandler.sendMessageDelayed(msg, delay);
+ }
+ }
+
+ private NetworkRequest removeRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.remove(netCap);
+ if (l == null) return null;
+ return l.networkRequest;
+ }
}
/**
@@ -1782,8 +1967,10 @@ public class ConnectivityManager {
public static final int CALLBACK_RELEASED = BASE + 8;
/** @hide */
public static final int CALLBACK_EXIT = BASE + 9;
+ /** @hide obj = NetworkCapabilities, arg1 = seq number */
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
- private static class CallbackHandler extends Handler {
+ private class CallbackHandler extends Handler {
private final HashMap<NetworkRequest, NetworkCallbackListener>mCallbackMap;
private final AtomicInteger mRefCount;
private static final String TAG = "ConnectivityManager.CallbackHandler";
@@ -1903,6 +2090,10 @@ public class ConnectivityManager {
getLooper().quit();
break;
}
+ case EXPIRE_LEGACY_REQUEST: {
+ expireRequest((NetworkCapabilities)message.obj, message.arg1);
+ break;
+ }
}
}
@@ -1954,8 +2145,9 @@ public class ConnectivityManager {
private final static int LISTEN = 1;
private final static int REQUEST = 2;
- private NetworkRequest somethingForNetwork(NetworkCapabilities need,
- NetworkCallbackListener networkCallbackListener, int timeoutSec, int action) {
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener, int timeoutSec, int action,
+ int legacyType) {
NetworkRequest networkRequest = null;
if (networkCallbackListener == null) {
throw new IllegalArgumentException("null NetworkCallbackListener");
@@ -1968,7 +2160,7 @@ public class ConnectivityManager {
new Binder());
} else {
networkRequest = mService.requestNetwork(need, new Messenger(sCallbackHandler),
- timeoutSec, new Binder());
+ timeoutSec, new Binder(), legacyType);
}
if (networkRequest != null) {
synchronized(sNetworkCallbackListener) {
@@ -1998,7 +2190,7 @@ public class ConnectivityManager {
*/
public NetworkRequest requestNetwork(NetworkCapabilities need,
NetworkCallbackListener networkCallbackListener) {
- return somethingForNetwork(need, networkCallbackListener, 0, REQUEST);
+ return sendRequestForNetwork(need, networkCallbackListener, 0, REQUEST, TYPE_NONE);
}
/**
@@ -2021,7 +2213,8 @@ public class ConnectivityManager {
*/
public NetworkRequest requestNetwork(NetworkCapabilities need,
NetworkCallbackListener networkCallbackListener, int timeoutSec) {
- return somethingForNetwork(need, networkCallbackListener, timeoutSec, REQUEST);
+ return sendRequestForNetwork(need, networkCallbackListener, timeoutSec, REQUEST,
+ TYPE_NONE);
}
/**
@@ -2099,7 +2292,7 @@ public class ConnectivityManager {
*/
public NetworkRequest listenForNetwork(NetworkCapabilities need,
NetworkCallbackListener networkCallbackListener) {
- return somethingForNetwork(need, networkCallbackListener, 0, LISTEN);
+ return sendRequestForNetwork(need, networkCallbackListener, 0, LISTEN, TYPE_NONE);
}
/**
diff --git a/core/java/android/net/ConnectivityServiceProtocol.java b/core/java/android/net/ConnectivityServiceProtocol.java
deleted file mode 100644
index 74096b4..0000000
--- a/core/java/android/net/ConnectivityServiceProtocol.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2014 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 static com.android.internal.util.Protocol.BASE_CONNECTIVITY_SERVICE;
-
-/**
- * Describes the Internal protocols used to communicate with ConnectivityService.
- * @hide
- */
-public class ConnectivityServiceProtocol {
-
- private static final int BASE = BASE_CONNECTIVITY_SERVICE;
-
- private ConnectivityServiceProtocol() {}
-
- /**
- * This is a contract between ConnectivityService and various bearers.
- * A NetworkFactory is an abstract entity that creates NetworkAgent objects.
- * The bearers register with ConnectivityService using
- * ConnectivityManager.registerNetworkFactory, where they pass in a Messenger
- * to be used to deliver the following Messages.
- */
- public static class NetworkFactoryProtocol {
- private NetworkFactoryProtocol() {}
- /**
- * Pass a network request to the bearer. If the bearer believes it can
- * satisfy the request it should connect to the network and create a
- * NetworkAgent. Once the NetworkAgent is fully functional it will
- * register itself with ConnectivityService using registerNetworkAgent.
- * If the bearer cannot immediately satisfy the request (no network,
- * user disabled the radio, lower-scored network) it should remember
- * any NetworkRequests it may be able to satisfy in the future. It may
- * disregard any that it will never be able to service, for example
- * those requiring a different bearer.
- * msg.obj = NetworkRequest
- * msg.arg1 = score - the score of the any network currently satisfying this
- * request. If this bearer knows in advance it cannot
- * exceed this score it should not try to connect, holding the request
- * for the future.
- * Note that subsequent events may give a different (lower
- * or higher) score for this request, transmitted to each
- * NetworkFactory through additional CMD_REQUEST_NETWORK msgs
- * with the same NetworkRequest but an updated score.
- * Also, network conditions may change for this bearer
- * allowing for a better score in the future.
- */
- public static final int CMD_REQUEST_NETWORK = BASE;
-
- /**
- * Cancel a network request
- * msg.obj = NetworkRequest
- */
- public static final int CMD_CANCEL_REQUEST = BASE + 1;
- }
-}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index baec36a..5f1ff3e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -158,7 +158,7 @@ interface IConnectivityManager
in NetworkCapabilities nc, int score);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
- in Messenger messenger, int timeoutSec, in IBinder binder);
+ in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation);
@@ -170,4 +170,6 @@ interface IConnectivityManager
in PendingIntent operation);
void releaseNetworkRequest(in NetworkRequest networkRequest);
+
+ int getRestoreDefaultNetworkDelay(int networkType);
}
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index e489e05..64516e6 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -94,11 +94,26 @@ public class Network implements Parcelable {
mNetId = netId;
}
+ private void connectToHost(Socket socket, String host, int port) throws IOException {
+ // Lookup addresses only on this Network.
+ InetAddress[] hostAddresses = getAllByName(host);
+ // Try all but last address ignoring exceptions.
+ for (int i = 0; i < hostAddresses.length - 1; i++) {
+ try {
+ socket.connect(new InetSocketAddress(hostAddresses[i], port));
+ return;
+ } catch (IOException e) {
+ }
+ }
+ // Try last address. Do throw exceptions.
+ socket.connect(new InetSocketAddress(hostAddresses[hostAddresses.length - 1], port));
+ }
+
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
Socket socket = createSocket();
socket.bind(new InetSocketAddress(localHost, localPort));
- socket.connect(new InetSocketAddress(host, port));
+ connectToHost(socket, host, port);
return socket;
}
@@ -121,7 +136,7 @@ public class Network implements Parcelable {
@Override
public Socket createSocket(String host, int port) throws IOException {
Socket socket = createSocket();
- socket.connect(new InetSocketAddress(host, port));
+ connectToHost(socket, host, port);
return socket;
}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index c2b06a2..7e8b1f1 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -24,85 +24,39 @@ import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
-import android.util.SparseArray;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
+import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A Utility class for handling NetworkRequests.
- *
- * Created by bearer-specific code to handle tracking requests, scores,
- * network data and handle communicating with ConnectivityService. Two
- * abstract methods: connect and disconnect are used to act on the
- * underlying bearer code. Connect is called when we have a NetworkRequest
- * and our score is better than the current handling network's score, while
- * disconnect is used when ConnectivityService requests a disconnect.
+ * A Utility class for handling for communicating between bearer-specific
+ * code and ConnectivityService.
*
* A bearer may have more than one NetworkAgent if it can simultaneously
* support separate networks (IMS / Internet / MMS Apns on cellular, or
- * perhaps connections with different SSID or P2P for Wi-Fi). The bearer
- * code should pass its NetworkAgents the NetworkRequests each NetworkAgent
- * can handle, demultiplexing for different network types. The bearer code
- * can also filter out requests it can never handle.
+ * perhaps connections with different SSID or P2P for Wi-Fi).
*
- * Each NetworkAgent needs to be given a score and NetworkCapabilities for
- * their potential network. While disconnected, the NetworkAgent will check
- * each time its score changes or a NetworkRequest changes to see if
- * the NetworkAgent can provide a higher scored network for a NetworkRequest
- * that the NetworkAgent's NetworkCapabilties can satisfy. This condition will
- * trigger a connect request via connect(). After connection, connection data
- * should be given to the NetworkAgent by the bearer, including LinkProperties
- * NetworkCapabilties and NetworkInfo. After that the NetworkAgent will register
- * with ConnectivityService and forward the data on.
* @hide
*/
public abstract class NetworkAgent extends Handler {
- private final SparseArray<NetworkRequestAndScore> mNetworkRequests = new SparseArray<>();
- private boolean mConnectionRequested = false;
-
- private AsyncChannel mAsyncChannel;
+ private volatile AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
private static final boolean VDBG = true;
- // TODO - this class shouldn't cache data or it runs the risk of getting out of sync
- // Make the API require each of these when any is updated so we have the data we need,
- // without caching.
- private LinkProperties mLinkProperties;
- private NetworkInfo mNetworkInfo;
- private NetworkCapabilities mNetworkCapabilities;
- private int mNetworkScore;
- private boolean mRegistered = false;
private final Context mContext;
- private AtomicBoolean mHasRequests = new AtomicBoolean(false);
-
- // TODO - add a name member for logging purposes.
-
- protected final Object mLockObj = new Object();
-
+ private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
/**
- * Sent by self to queue up a new/modified request.
- * obj = NetworkRequestAndScore
- */
- private static final int CMD_ADD_REQUEST = BASE + 1;
-
- /**
- * Sent by self to queue up the removal of a request.
- * obj = NetworkRequest
- */
- private static final int CMD_REMOVE_REQUEST = BASE + 2;
-
- /**
* Sent by ConnectivityService to the NetworkAgent to inform it of
* suspected connectivity problems on its network. The NetworkAgent
* should take steps to verify and correct connectivity.
*/
- public static final int CMD_SUSPECT_BAD = BASE + 3;
+ public static final int CMD_SUSPECT_BAD = BASE;
/**
* Sent by the NetworkAgent (note the EVENT vs CMD prefix) to
@@ -110,84 +64,63 @@ public abstract class NetworkAgent extends Handler {
* Sent when the NetworkInfo changes, mainly due to change of state.
* obj = NetworkInfo
*/
- public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 4;
+ public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkCapabilties.
* obj = NetworkCapabilities
*/
- public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 5;
+ public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkProperties.
* obj = NetworkProperties
*/
- public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 6;
+ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* network score.
- * arg1 = network score int
+ * obj = network score Integer
*/
- public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 7;
+ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
- public NetworkAgent(Looper looper, Context context, String logTag) {
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score) {
super(looper);
LOG_TAG = logTag;
mContext = context;
- }
-
- /**
- * When conditions are right, register with ConnectivityService.
- * Connditions include having a well defined network and a request
- * that justifies it. The NetworkAgent will remain registered until
- * disconnected.
- * TODO - this should have all data passed in rather than caching
- */
- private void registerSelf() {
- synchronized(mLockObj) {
- if (!mRegistered && mConnectionRequested &&
- mNetworkInfo != null && mNetworkInfo.isConnected() &&
- mNetworkCapabilities != null &&
- mLinkProperties != null &&
- mNetworkScore != 0) {
- if (DBG) log("Registering NetworkAgent");
- mRegistered = true;
- ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(mNetworkInfo),
- new LinkProperties(mLinkProperties),
- new NetworkCapabilities(mNetworkCapabilities), mNetworkScore);
- } else if (DBG && !mRegistered) {
- String err = "Not registering due to ";
- if (mConnectionRequested == false) err += "no Connect requested ";
- if (mNetworkInfo == null) err += "null NetworkInfo ";
- if (mNetworkInfo != null && mNetworkInfo.isConnected() == false) {
- err += "NetworkInfo disconnected ";
- }
- if (mLinkProperties == null) err += "null LinkProperties ";
- if (mNetworkCapabilities == null) err += "null NetworkCapabilities ";
- if (mNetworkScore == 0) err += "null NetworkScore";
- log(err);
- }
+ if (ni == null || nc == null || lp == null) {
+ throw new IllegalArgumentException();
}
+
+ if (DBG) log("Registering NetworkAgent");
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- synchronized (mLockObj) {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- } else {
- if (DBG) log("NetworkAgent fully connected");
- mAsyncChannel = new AsyncChannel();
- mAsyncChannel.connected(null, this, msg.replyTo);
- mAsyncChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (DBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
}
}
break;
@@ -199,201 +132,69 @@ public abstract class NetworkAgent extends Handler {
}
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
if (DBG) log("NetworkAgent channel lost");
- disconnect();
- clear();
+ // let the client know CS is done with us.
+ unwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
break;
}
case CMD_SUSPECT_BAD: {
log("Unhandled Message " + msg);
break;
}
- case CMD_ADD_REQUEST: {
- handleAddRequest(msg);
- break;
- }
- case CMD_REMOVE_REQUEST: {
- handleRemoveRequest(msg);
- break;
- }
- }
- }
-
- private void clear() {
- synchronized(mLockObj) {
- mNetworkRequests.clear();
- mHasRequests.set(false);
- mConnectionRequested = false;
- mAsyncChannel = null;
- mRegistered = false;
- }
- }
-
- private static class NetworkRequestAndScore {
- NetworkRequest req;
- int score;
-
- NetworkRequestAndScore(NetworkRequest networkRequest, int score) {
- req = networkRequest;
- this.score = score;
- }
- }
-
- private void handleAddRequest(Message msg) {
- NetworkRequestAndScore n = (NetworkRequestAndScore)msg.obj;
- // replaces old request, updating score
- mNetworkRequests.put(n.req.requestId, n);
- mHasRequests.set(true);
- evalScores();
- }
-
- private void handleRemoveRequest(Message msg) {
- NetworkRequest networkRequest = (NetworkRequest)msg.obj;
-
- if (mNetworkRequests.get(networkRequest.requestId) != null) {
- mNetworkRequests.remove(networkRequest.requestId);
- if (mNetworkRequests.size() == 0) mHasRequests.set(false);
- evalScores();
}
}
- /**
- * called to go through our list of requests and see if we're
- * good enough to try connecting.
- *
- * Only does connects - we disconnect when requested via
- * CMD_CHANNEL_DISCONNECTED, generated by either a loss of connection
- * between modules (bearer or ConnectivityService dies) or more commonly
- * when the NetworkInfo reports to ConnectivityService it is disconnected.
- */
- private void evalScores() {
- if (mConnectionRequested) {
- if (VDBG) log("evalScores - already trying - size=" + mNetworkRequests.size());
- // already trying
- return;
- }
- if (VDBG) log("evalScores!");
- for (int i=0; i < mNetworkRequests.size(); i++) {
- int score = mNetworkRequests.valueAt(i).score;
- if (VDBG) log(" checking request Min " + score + " vs my score " + mNetworkScore);
- if (score < mNetworkScore) {
- // have a request that has a lower scored network servicing it
- // (or no network) than we could provide, so lets connect!
- mConnectionRequested = true;
- connect();
- return;
+ private void queueOrSendMessage(int what, Object obj) {
+ synchronized (mPreConnectedQueue) {
+ if (mAsyncChannel != null) {
+ mAsyncChannel.sendMessage(what, obj);
+ } else {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.obj = obj;
+ mPreConnectedQueue.add(msg);
}
}
}
- public void addNetworkRequest(NetworkRequest networkRequest, int score) {
- if (DBG) log("adding NetworkRequest " + networkRequest + " with score " + score);
- sendMessage(obtainMessage(CMD_ADD_REQUEST,
- new NetworkRequestAndScore(networkRequest, score)));
- }
-
- public void removeNetworkRequest(NetworkRequest networkRequest) {
- if (DBG) log("removing NetworkRequest " + networkRequest);
- sendMessage(obtainMessage(CMD_REMOVE_REQUEST, networkRequest));
- }
-
/**
* Called by the bearer code when it has new LinkProperties data.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy in anticipation of registering. This call
- * may also prompt registration if it causes the NetworkAgent to meet
- * the conditions (fully configured, connected, satisfys a request and
- * has sufficient score).
*/
public void sendLinkProperties(LinkProperties linkProperties) {
- linkProperties = new LinkProperties(linkProperties);
- synchronized(mLockObj) {
- mLinkProperties = linkProperties;
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, linkProperties);
- } else {
- registerSelf();
- }
- }
+ queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
}
/**
* Called by the bearer code when it has new NetworkInfo data.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy in anticipation of registering. This call
- * may also prompt registration if it causes the NetworkAgent to meet
- * the conditions (fully configured, connected, satisfys a request and
- * has sufficient score).
*/
public void sendNetworkInfo(NetworkInfo networkInfo) {
- networkInfo = new NetworkInfo(networkInfo);
- synchronized(mLockObj) {
- mNetworkInfo = networkInfo;
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_INFO_CHANGED, networkInfo);
- } else {
- registerSelf();
- }
- }
+ queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
}
/**
* Called by the bearer code when it has new NetworkCapabilities data.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy in anticipation of registering. This call
- * may also prompt registration if it causes the NetworkAgent to meet
- * the conditions (fully configured, connected, satisfys a request and
- * has sufficient score).
- * Note that if these capabilities make the network non-useful,
- * ConnectivityServce will tear this network down.
*/
public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
- synchronized(mLockObj) {
- mNetworkCapabilities = networkCapabilities;
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, networkCapabilities);
- } else {
- registerSelf();
- }
- }
- }
-
- public NetworkCapabilities getNetworkCapabilities() {
- synchronized(mLockObj) {
- return new NetworkCapabilities(mNetworkCapabilities);
- }
+ queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
+ new NetworkCapabilities(networkCapabilities));
}
/**
* Called by the bearer code when it has a new score for this network.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy.
*/
- public synchronized void sendNetworkScore(int score) {
- synchronized(mLockObj) {
- mNetworkScore = score;
- evalScores();
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_SCORE_CHANGED, mNetworkScore);
- } else {
- registerSelf();
- }
- }
- }
-
- public boolean hasRequests() {
- return mHasRequests.get();
+ public void sendNetworkScore(int score) {
+ queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score));
}
- public boolean isConnectionRequested() {
- synchronized(mLockObj) {
- return mConnectionRequested;
- }
- }
-
-
- abstract protected void connect();
- abstract protected void disconnect();
+ /**
+ * Called when ConnectivityService has indicated they no longer want this network.
+ * The parent factory should (previously) have received indication of the change
+ * as well, either canceling NetworkRequests or altering their score such that this
+ * network won't be immediately requested again.
+ */
+ abstract protected void unwanted();
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
new file mode 100644
index 0000000..a20e8e7
--- /dev/null
+++ b/core/java/android/net/NetworkFactory.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+/**
+ * A NetworkFactory is an entity that creates NetworkAgent objects.
+ * The bearers register with ConnectivityService using {@link #register} and
+ * their factory will start receiving scored NetworkRequests. NetworkRequests
+ * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
+ * overridden function. All of these can be dynamic - changing NetworkCapabilities
+ * or score forces re-evaluation of all current requests.
+ *
+ * If any requests pass the filter some overrideable functions will be called.
+ * If the bearer only cares about very simple start/stopNetwork callbacks, those
+ * functions can be overridden. If the bearer needs more interaction, it can
+ * override addNetworkRequest and removeNetworkRequest which will give it each
+ * request that passes their current filters.
+ * @hide
+ **/
+public class NetworkFactory extends Handler {
+ private static final boolean DBG = true;
+
+ private static final int BASE = Protocol.BASE_NETWORK_FACTORY;
+ /**
+ * Pass a network request to the bearer. If the bearer believes it can
+ * satisfy the request it should connect to the network and create a
+ * NetworkAgent. Once the NetworkAgent is fully functional it will
+ * register itself with ConnectivityService using registerNetworkAgent.
+ * If the bearer cannot immediately satisfy the request (no network,
+ * user disabled the radio, lower-scored network) it should remember
+ * any NetworkRequests it may be able to satisfy in the future. It may
+ * disregard any that it will never be able to service, for example
+ * those requiring a different bearer.
+ * msg.obj = NetworkRequest
+ * msg.arg1 = score - the score of the any network currently satisfying this
+ * request. If this bearer knows in advance it cannot
+ * exceed this score it should not try to connect, holding the request
+ * for the future.
+ * Note that subsequent events may give a different (lower
+ * or higher) score for this request, transmitted to each
+ * NetworkFactory through additional CMD_REQUEST_NETWORK msgs
+ * with the same NetworkRequest but an updated score.
+ * Also, network conditions may change for this bearer
+ * allowing for a better score in the future.
+ */
+ public static final int CMD_REQUEST_NETWORK = BASE;
+
+ /**
+ * Cancel a network request
+ * msg.obj = NetworkRequest
+ */
+ public static final int CMD_CANCEL_REQUEST = BASE + 1;
+
+ /**
+ * Internally used to set our best-guess score.
+ * msg.arg1 = new score
+ */
+ private static final int CMD_SET_SCORE = BASE + 2;
+
+ /**
+ * Internally used to set our current filter for coarse bandwidth changes with
+ * technology changes.
+ * msg.obj = new filter
+ */
+ private static final int CMD_SET_FILTER = BASE + 3;
+
+ private final Context mContext;
+ private final String LOG_TAG;
+
+ private final SparseArray<NetworkRequestInfo> mNetworkRequests =
+ new SparseArray<NetworkRequestInfo>();
+
+ private int mScore;
+ private NetworkCapabilities mCapabilityFilter;
+
+ private int mRefCount = 0;
+ private Messenger mMessenger = null;
+
+ public NetworkFactory(Looper looper, Context context, String logTag,
+ NetworkCapabilities filter) {
+ super(looper);
+ LOG_TAG = logTag;
+ mContext = context;
+ mCapabilityFilter = filter;
+ }
+
+ public void register() {
+ if (DBG) log("Registering NetworkFactory");
+ if (mMessenger == null) {
+ mMessenger = new Messenger(this);
+ ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
+ }
+ }
+
+ public void unregister() {
+ if (DBG) log("Unregistering NetworkFactory");
+ if (mMessenger != null) {
+ ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
+ mMessenger = null;
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_REQUEST_NETWORK: {
+ handleAddRequest((NetworkRequest)msg.obj, msg.arg1);
+ break;
+ }
+ case CMD_CANCEL_REQUEST: {
+ handleRemoveRequest((NetworkRequest) msg.obj);
+ break;
+ }
+ case CMD_SET_SCORE: {
+ handleSetScore(msg.arg1);
+ break;
+ }
+ case CMD_SET_FILTER: {
+ handleSetFilter((NetworkCapabilities) msg.obj);
+ break;
+ }
+ }
+ }
+
+ private class NetworkRequestInfo {
+ public final NetworkRequest request;
+ public int score;
+ public boolean requested; // do we have a request outstanding, limited by score
+
+ public NetworkRequestInfo(NetworkRequest request, int score) {
+ this.request = request;
+ this.score = score;
+ this.requested = false;
+ }
+ }
+
+ private void handleAddRequest(NetworkRequest request, int score) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n == null) {
+ n = new NetworkRequestInfo(request, score);
+ mNetworkRequests.put(n.request.requestId, n);
+ } else {
+ n.score = score;
+ }
+ if (DBG) log("got request " + request + " with score " + score);
+ if (DBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
+
+ evalRequest(n);
+ }
+
+ private void handleRemoveRequest(NetworkRequest request) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n != null && n.requested) {
+ mNetworkRequests.remove(request.requestId);
+ releaseNetworkFor(n.request);
+ }
+ }
+
+ private void handleSetScore(int score) {
+ mScore = score;
+ evalRequests();
+ }
+
+ private void handleSetFilter(NetworkCapabilities netCap) {
+ mCapabilityFilter = netCap;
+ evalRequests();
+ }
+
+ /**
+ * Overridable function to provide complex filtering.
+ * Called for every request every time a new NetworkRequest is seen
+ * and whenever the filterScore or filterNetworkCapabilities change.
+ *
+ * acceptRequest can be overriden to provide complex filter behavior
+ * for the incoming requests
+ *
+ * For output, this class will call {@link #needNetworkFor} and
+ * {@link #releaseNetworkFor} for every request that passes the filters.
+ * If you don't need to see every request, you can leave the base
+ * implementations of those two functions and instead override
+ * {@link #startNetwork} and {@link #stopNetwork}.
+ *
+ * If you want to see every score fluctuation on every request, set
+ * your score filter to a very high number and watch {@link #needNetworkFor}.
+ *
+ * @return {@code true} to accept the request.
+ */
+ public boolean acceptRequest(NetworkRequest request, int score) {
+ return true;
+ }
+
+ private void evalRequest(NetworkRequestInfo n) {
+ if (n.requested == false && n.score < mScore &&
+ n.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ mCapabilityFilter) && acceptRequest(n.request, n.score)) {
+ needNetworkFor(n.request, n.score);
+ n.requested = true;
+ } else if (n.requested == true &&
+ (n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) {
+ releaseNetworkFor(n.request);
+ n.requested = false;
+ }
+ }
+
+ private void evalRequests() {
+ for (int i = 0; i < mNetworkRequests.size(); i++) {
+ NetworkRequestInfo n = mNetworkRequests.valueAt(i);
+
+ evalRequest(n);
+ }
+ }
+
+ // override to do simple mode (request independent)
+ protected void startNetwork() { }
+ protected void stopNetwork() { }
+
+ // override to do fancier stuff
+ protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+ if (++mRefCount == 1) startNetwork();
+ }
+
+ protected void releaseNetworkFor(NetworkRequest networkRequest) {
+ if (--mRefCount == 0) stopNetwork();
+ }
+
+
+ public void addNetworkRequest(NetworkRequest networkRequest, int score) {
+ sendMessage(obtainMessage(CMD_REQUEST_NETWORK,
+ new NetworkRequestInfo(networkRequest, score)));
+ }
+
+ public void removeNetworkRequest(NetworkRequest networkRequest) {
+ sendMessage(obtainMessage(CMD_CANCEL_REQUEST, networkRequest));
+ }
+
+ public void setScoreFilter(int score) {
+ sendMessage(obtainMessage(CMD_SET_SCORE, score, 0));
+ }
+
+ public void setCapabilityFilter(NetworkCapabilities netCap) {
+ sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
+ }
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, s);
+ }
+}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 9e656ee..d279412 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -188,6 +188,15 @@ public class NetworkInfo implements Parcelable {
}
/**
+ * @hide
+ */
+ public void setType(int type) {
+ synchronized (this) {
+ mNetworkType = type;
+ }
+ }
+
+ /**
* Return a network-type-specific integer describing the subtype
* of the network.
* @return the network subtype
@@ -198,7 +207,10 @@ public class NetworkInfo implements Parcelable {
}
}
- void setSubtype(int subtype, String subtypeName) {
+ /**
+ * @hide
+ */
+ public void setSubtype(int subtype, String subtypeName) {
synchronized (this) {
mSubtype = subtype;
mSubtypeName = subtypeName;
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 480cb05..47377e9 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -47,19 +47,19 @@ public class NetworkRequest implements Parcelable {
public final int requestId;
/**
- * Set for legacy requests and the default.
+ * Set for legacy requests and the default. Set to TYPE_NONE for none.
* Causes CONNECTIVITY_ACTION broadcasts to be sent.
* @hide
*/
- public final boolean needsBroadcasts;
+ public final int legacyType;
/**
* @hide
*/
- public NetworkRequest(NetworkCapabilities nc, boolean needsBroadcasts, int rId) {
+ public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId) {
requestId = rId;
networkCapabilities = nc;
- this.needsBroadcasts = needsBroadcasts;
+ this.legacyType = legacyType;
}
/**
@@ -68,7 +68,7 @@ public class NetworkRequest implements Parcelable {
public NetworkRequest(NetworkRequest that) {
networkCapabilities = new NetworkCapabilities(that.networkCapabilities);
requestId = that.requestId;
- needsBroadcasts = that.needsBroadcasts;
+ this.legacyType = that.legacyType;
}
// implement the Parcelable interface
@@ -77,16 +77,16 @@ public class NetworkRequest implements Parcelable {
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(networkCapabilities, flags);
- dest.writeInt(needsBroadcasts ? 1 : 0);
+ dest.writeInt(legacyType);
dest.writeInt(requestId);
}
public static final Creator<NetworkRequest> CREATOR =
new Creator<NetworkRequest>() {
public NetworkRequest createFromParcel(Parcel in) {
NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
- boolean needsBroadcasts = (in.readInt() == 1);
+ int legacyType = in.readInt();
int requestId = in.readInt();
- NetworkRequest result = new NetworkRequest(nc, needsBroadcasts, requestId);
+ NetworkRequest result = new NetworkRequest(nc, legacyType, requestId);
return result;
}
public NetworkRequest[] newArray(int size) {
@@ -95,14 +95,14 @@ public class NetworkRequest implements Parcelable {
};
public String toString() {
- return "NetworkRequest [ id=" + requestId + ", needsBroadcasts=" + needsBroadcasts +
+ return "NetworkRequest [ id=" + requestId + ", legacyType=" + legacyType +
", " + networkCapabilities.toString() + " ]";
}
public boolean equals(Object obj) {
if (obj instanceof NetworkRequest == false) return false;
NetworkRequest that = (NetworkRequest)obj;
- return (that.needsBroadcasts == this.needsBroadcasts &&
+ return (that.legacyType == this.legacyType &&
that.requestId == this.requestId &&
((that.networkCapabilities == null && this.networkCapabilities == null) ||
(that.networkCapabilities != null &&
@@ -110,7 +110,7 @@ public class NetworkRequest implements Parcelable {
}
public int hashCode() {
- return requestId + (needsBroadcasts ? 1013 : 2026) +
+ return requestId + (legacyType * 1013) +
(networkCapabilities.hashCode() * 1051);
}
}
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index b0449224..cabda5d 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -2,6 +2,7 @@ package android.nfc.cardemulation;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -21,6 +22,8 @@ import android.util.Log;
* <p>The format of AIDs is defined in the ISO/IEC 7816-4 specification. This class
* requires the AIDs to be input as a hexadecimal string, with an even amount of
* hexadecimal characters, e.g. "F014811481".
+ *
+ * @hide
*/
public final class AidGroup implements Parcelable {
/**
@@ -30,7 +33,7 @@ public final class AidGroup implements Parcelable {
static final String TAG = "AidGroup";
- final ArrayList<String> aids;
+ final List<String> aids;
final String category;
final String description;
@@ -40,7 +43,7 @@ public final class AidGroup implements Parcelable {
* @param aids The list of AIDs present in the group
* @param category The category of this group, e.g. {@link CardEmulation#CATEGORY_PAYMENT}
*/
- public AidGroup(ArrayList<String> aids, String category) {
+ public AidGroup(List<String> aids, String category) {
if (aids == null || aids.size() == 0) {
throw new IllegalArgumentException("No AIDS in AID group.");
}
@@ -72,7 +75,7 @@ public final class AidGroup implements Parcelable {
/**
* @return the list of AIDs in this group
*/
- public ArrayList<String> getAids() {
+ public List<String> getAids() {
return aids;
}
@@ -121,11 +124,6 @@ public final class AidGroup implements Parcelable {
}
};
- /**
- * @hide
- * Note: description is not serialized, since it's not localized
- * and resource identifiers don't make sense to persist.
- */
static public AidGroup createFromXml(XmlPullParser parser) throws XmlPullParserException, IOException {
String category = parser.getAttributeValue(null, "category");
ArrayList<String> aids = new ArrayList<String>();
@@ -152,9 +150,6 @@ public final class AidGroup implements Parcelable {
}
}
- /**
- * @hide
- */
public void writeAsXml(XmlSerializer out) throws IOException {
out.attribute(null, "category", category);
for (String aid : aids) {
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index e24a22a..4b9e890 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -303,12 +303,13 @@ public final class CardEmulation {
}
/**
- * Registers a group of AIDs for the specified service.
+ * Registers a list of AIDs for a specific category for the
+ * specified service.
*
- * <p>If an AID group for that category was previously
+ * <p>If a list of AIDs for that category was previously
* registered for this service (either statically
* through the manifest, or dynamically by using this API),
- * that AID group will be replaced with this one.
+ * that list of AIDs will be replaced with this one.
*
* <p>Note that you can only register AIDs for a service that
* is running under the same UID as the caller of this API. Typically
@@ -317,10 +318,13 @@ public final class CardEmulation {
* be shared between packages using shared UIDs.
*
* @param service The component name of the service
- * @param aidGroup The group of AIDs to be registered
+ * @param category The category of AIDs to be registered
+ * @param aids A list containing the AIDs to be registered
* @return whether the registration was successful.
*/
- public boolean registerAidGroupForService(ComponentName service, AidGroup aidGroup) {
+ public boolean registerAidsForService(ComponentName service, String category,
+ List<String> aids) {
+ AidGroup aidGroup = new AidGroup(aids, category);
try {
return sService.registerAidGroupForService(UserHandle.myUserId(), service, aidGroup);
} catch (RemoteException e) {
@@ -341,21 +345,24 @@ public final class CardEmulation {
}
/**
- * Retrieves the currently registered AID group for the specified
+ * Retrieves the currently registered AIDs for the specified
* category for a service.
*
- * <p>Note that this will only return AID groups that were dynamically
- * registered using {@link #registerAidGroupForService(ComponentName, AidGroup)}
- * method. It will *not* return AID groups that were statically registered
+ * <p>Note that this will only return AIDs that were dynamically
+ * registered using {@link #registerAidsForService(ComponentName, String, List)}
+ * method. It will *not* return AIDs that were statically registered
* in the manifest.
*
* @param service The component name of the service
- * @param category The category of the AID group to be returned, e.g. {@link #CATEGORY_PAYMENT}
- * @return The AID group, or null if it couldn't be found
+ * @param category The category for which the AIDs were registered,
+ * e.g. {@link #CATEGORY_PAYMENT}
+ * @return The list of AIDs registered for this category, or null if it couldn't be found.
*/
- public AidGroup getAidGroupForService(ComponentName service, String category) {
+ public List<String> getAidsForService(ComponentName service, String category) {
try {
- return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service,
+ category);
+ return (group != null ? group.getAids() : null);
} catch (RemoteException e) {
recoverService();
if (sService == null) {
@@ -363,7 +370,9 @@ public final class CardEmulation {
return null;
}
try {
- return sService.getAidGroupForService(UserHandle.myUserId(), service, category);
+ AidGroup group = sService.getAidGroupForService(UserHandle.myUserId(), service,
+ category);
+ return (group != null ? group.getAids() : null);
} catch (RemoteException ee) {
Log.e(TAG, "Failed to recover CardEmulationService.");
return null;
@@ -372,21 +381,21 @@ public final class CardEmulation {
}
/**
- * Removes a registered AID group for the specified category for the
+ * Removes a previously registered list of AIDs for the specified category for the
* service provided.
*
- * <p>Note that this will only remove AID groups that were dynamically
- * registered using the {@link #registerAidGroupForService(ComponentName, AidGroup)}
- * method. It will *not* remove AID groups that were statically registered in
- * the manifest. If a dynamically registered AID group is removed using
+ * <p>Note that this will only remove AIDs that were dynamically
+ * registered using the {@link #registerAidsForService(ComponentName, String, List)}
+ * method. It will *not* remove AIDs that were statically registered in
+ * the manifest. If dynamically registered AIDs are removed using
* this method, and a statically registered AID group for the same category
* exists in the manifest, the static AID group will become active again.
*
* @param service The component name of the service
- * @param category The category of the AID group to be removed, e.g. {@link #CATEGORY_PAYMENT}
+ * @param category The category of the AIDs to be removed, e.g. {@link #CATEGORY_PAYMENT}
* @return whether the group was successfully removed.
*/
- public boolean removeAidGroupForService(ComponentName service, String category) {
+ public boolean removeAidsForService(ComponentName service, String category) {
try {
return sService.removeAidGroupForService(UserHandle.myUserId(), service, category);
} catch (RemoteException e) {
diff --git a/core/java/android/os/CommonBundle.java b/core/java/android/os/BaseBundle.java
index c1b202c..c2a45ba 100644
--- a/core/java/android/os/CommonBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -27,7 +27,7 @@ import java.util.Set;
/**
* A mapping from String values to various types.
*/
-abstract class CommonBundle implements Parcelable, Cloneable {
+public class BaseBundle {
private static final String TAG = "Bundle";
static final boolean DEBUG = false;
@@ -63,7 +63,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* inside of the Bundle.
* @param capacity Initial size of the ArrayMap.
*/
- CommonBundle(ClassLoader loader, int capacity) {
+ BaseBundle(ClassLoader loader, int capacity) {
mMap = capacity > 0 ?
new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
mClassLoader = loader == null ? getClass().getClassLoader() : loader;
@@ -72,7 +72,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* Constructs a new, empty Bundle.
*/
- CommonBundle() {
+ BaseBundle() {
this((ClassLoader) null, 0);
}
@@ -82,11 +82,11 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param parcelledData a Parcel containing a Bundle
*/
- CommonBundle(Parcel parcelledData) {
+ BaseBundle(Parcel parcelledData) {
readFromParcelInner(parcelledData);
}
- CommonBundle(Parcel parcelledData, int length) {
+ BaseBundle(Parcel parcelledData, int length) {
readFromParcelInner(parcelledData, length);
}
@@ -97,7 +97,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param loader An explicit ClassLoader to use when instantiating objects
* inside of the Bundle.
*/
- CommonBundle(ClassLoader loader) {
+ BaseBundle(ClassLoader loader) {
this(loader, 0);
}
@@ -107,7 +107,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param capacity the initial capacity of the Bundle
*/
- CommonBundle(int capacity) {
+ BaseBundle(int capacity) {
this((ClassLoader) null, capacity);
}
@@ -117,7 +117,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param b a Bundle to be copied.
*/
- CommonBundle(CommonBundle b) {
+ BaseBundle(BaseBundle b) {
if (b.mParcelledData != null) {
if (b.mParcelledData == EMPTY_PARCEL) {
mParcelledData = EMPTY_PARCEL;
@@ -148,7 +148,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @hide
*/
- String getPairValue() {
+ public String getPairValue() {
unparcel();
int size = mMap.size();
if (size > 1) {
@@ -228,7 +228,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* @hide
*/
- boolean isParcelled() {
+ public boolean isParcelled() {
return mParcelledData != null;
}
@@ -237,7 +237,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @return the number of mappings as an int.
*/
- int size() {
+ public int size() {
unparcel();
return mMap.size();
}
@@ -245,7 +245,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* Returns true if the mapping of this Bundle is empty, false otherwise.
*/
- boolean isEmpty() {
+ public boolean isEmpty() {
unparcel();
return mMap.isEmpty();
}
@@ -253,7 +253,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
/**
* Removes all elements from the mapping of this Bundle.
*/
- void clear() {
+ public void clear() {
unparcel();
mMap.clear();
}
@@ -265,7 +265,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String key
* @return true if the key is part of the mapping, false otherwise
*/
- boolean containsKey(String key) {
+ public boolean containsKey(String key) {
unparcel();
return mMap.containsKey(key);
}
@@ -276,7 +276,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String key
* @return an Object, or null
*/
- Object get(String key) {
+ public Object get(String key) {
unparcel();
return mMap.get(key);
}
@@ -286,24 +286,24 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @param key a String key
*/
- void remove(String key) {
+ public void remove(String key) {
unparcel();
mMap.remove(key);
}
/**
- * Inserts all mappings from the given PersistableBundle into this CommonBundle.
+ * Inserts all mappings from the given PersistableBundle into this BaseBundle.
*
* @param bundle a PersistableBundle
*/
- void putAll(PersistableBundle bundle) {
+ public void putAll(PersistableBundle bundle) {
unparcel();
bundle.unparcel();
mMap.putAll(bundle.mMap);
}
/**
- * Inserts all mappings from the given Map into this CommonBundle.
+ * Inserts all mappings from the given Map into this BaseBundle.
*
* @param map a Map
*/
@@ -317,7 +317,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
*
* @return a Set of String keys
*/
- Set<String> keySet() {
+ public Set<String> keySet() {
unparcel();
return mMap.keySet();
}
@@ -377,7 +377,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an int, or null
*/
- void putInt(String key, int value) {
+ public void putInt(String key, int value) {
unparcel();
mMap.put(key, value);
}
@@ -389,7 +389,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a long
*/
- void putLong(String key, long value) {
+ public void putLong(String key, long value) {
unparcel();
mMap.put(key, value);
}
@@ -413,7 +413,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a double
*/
- void putDouble(String key, double value) {
+ public void putDouble(String key, double value) {
unparcel();
mMap.put(key, value);
}
@@ -425,7 +425,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a String, or null
*/
- void putString(String key, String value) {
+ public void putString(String key, String value) {
unparcel();
mMap.put(key, value);
}
@@ -545,7 +545,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value an int array object, or null
*/
- void putIntArray(String key, int[] value) {
+ public void putIntArray(String key, int[] value) {
unparcel();
mMap.put(key, value);
}
@@ -557,7 +557,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a long array object, or null
*/
- void putLongArray(String key, long[] value) {
+ public void putLongArray(String key, long[] value) {
unparcel();
mMap.put(key, value);
}
@@ -581,7 +581,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a double array object, or null
*/
- void putDoubleArray(String key, double[] value) {
+ public void putDoubleArray(String key, double[] value) {
unparcel();
mMap.put(key, value);
}
@@ -593,7 +593,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @param value a String array object, or null
*/
- void putStringArray(String key, String[] value) {
+ public void putStringArray(String key, String[] value) {
unparcel();
mMap.put(key, value);
}
@@ -611,18 +611,6 @@ abstract class CommonBundle implements Parcelable, Cloneable {
}
/**
- * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a Bundle object, or null
- */
- void putPersistableBundle(String key, PersistableBundle value) {
- unparcel();
- mMap.put(key, value);
- }
-
- /**
* Returns the value associated with the given key, or false if
* no mapping of the desired type exists for the given key.
*
@@ -789,7 +777,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String
* @return an int value
*/
- int getInt(String key) {
+ public int getInt(String key) {
unparcel();
return getInt(key, 0);
}
@@ -802,7 +790,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return an int value
*/
- int getInt(String key, int defaultValue) {
+ public int getInt(String key, int defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -823,7 +811,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String
* @return a long value
*/
- long getLong(String key) {
+ public long getLong(String key) {
unparcel();
return getLong(key, 0L);
}
@@ -836,7 +824,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a long value
*/
- long getLong(String key, long defaultValue) {
+ public long getLong(String key, long defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -891,7 +879,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String
* @return a double value
*/
- double getDouble(String key) {
+ public double getDouble(String key) {
unparcel();
return getDouble(key, 0.0);
}
@@ -904,7 +892,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param defaultValue Value to return if key does not exist
* @return a double value
*/
- double getDouble(String key, double defaultValue) {
+ public double getDouble(String key, double defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -926,7 +914,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a String value, or null
*/
- String getString(String key) {
+ public String getString(String key) {
unparcel();
final Object o = mMap.get(key);
try {
@@ -946,7 +934,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @return the String value associated with the given key, or defaultValue
* if no valid String object is currently mapped to that key.
*/
- String getString(String key, String defaultValue) {
+ public String getString(String key, String defaultValue) {
final String s = getString(key);
return (s == null) ? defaultValue : s;
}
@@ -990,28 +978,6 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* value is explicitly associated with the key.
*
* @param key a String, or null
- * @return a Bundle value, or null
- */
- PersistableBundle getPersistableBundle(String key) {
- unparcel();
- Object o = mMap.get(key);
- if (o == null) {
- return null;
- }
- try {
- return (PersistableBundle) o;
- } catch (ClassCastException e) {
- typeWarning(key, o, "Bundle", 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 a Serializable value, or null
*/
Serializable getSerializable(String key) {
@@ -1190,7 +1156,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return an int[] value, or null
*/
- int[] getIntArray(String key) {
+ public int[] getIntArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1212,7 +1178,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a long[] value, or null
*/
- long[] getLongArray(String key) {
+ public long[] getLongArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1256,7 +1222,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a double[] value, or null
*/
- double[] getDoubleArray(String key) {
+ public double[] getDoubleArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
@@ -1278,7 +1244,7 @@ abstract class CommonBundle implements Parcelable, Cloneable {
* @param key a String, or null
* @return a String[] value, or null
*/
- String[] getStringArray(String key) {
+ public String[] getStringArray(String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index d3c6fd5..e627d49 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -931,6 +931,14 @@ public abstract class BatteryStats implements Parcelable {
}
}
+ /**
+ * Don't allow any more batching in to the current history event. This
+ * is called when printing partial histories, so to ensure that the next
+ * history event will go in to a new batch after what was printed in the
+ * last partial history.
+ */
+ public abstract void commitCurrentHistoryBatchLocked();
+
public abstract int getHistoryTotalSize();
public abstract int getHistoryUsedSize();
@@ -3303,7 +3311,8 @@ public abstract class BatteryStats implements Parcelable {
if (rec.time >= histStart) {
if (histStart >= 0 && !printed) {
if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
- || rec.cmd == HistoryItem.CMD_RESET) {
+ || rec.cmd == HistoryItem.CMD_RESET
+ || rec.cmd == HistoryItem.CMD_START) {
printed = true;
hprinter.printNextItem(pw, rec, baseTime, checkin,
(flags&DUMP_VERBOSE) != 0);
@@ -3365,6 +3374,7 @@ public abstract class BatteryStats implements Parcelable {
}
}
if (histStart >= 0) {
+ commitCurrentHistoryBatchLocked();
pw.print(checkin ? "NEXT: " : " NEXT: "); pw.println(lastTime+1);
}
}
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c85e418..e42c3fe 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -28,14 +28,14 @@ import java.util.Set;
* A mapping from String values to various Parcelable types.
*
*/
-public final class Bundle extends CommonBundle {
+public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
public static final Bundle EMPTY;
static final Parcel EMPTY_PARCEL;
static {
EMPTY = new Bundle();
EMPTY.mMap = ArrayMap.EMPTY;
- EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
+ EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL;
}
private boolean mHasFds = false;
@@ -125,14 +125,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * @hide
- */
- @Override
- public String getPairValue() {
- return super.getPairValue();
- }
-
- /**
* Changes the ClassLoader this Bundle uses when instantiating objects.
*
* @param loader An explicit ClassLoader to use when instantiating objects
@@ -168,32 +160,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * @hide
- */
- @Override
- public boolean isParcelled() {
- return super.isParcelled();
- }
-
- /**
- * Returns the number of mappings contained in this Bundle.
- *
- * @return the number of mappings as an int.
- */
- @Override
- public int size() {
- return super.size();
- }
-
- /**
- * Returns true if the mapping of this Bundle is empty, false otherwise.
- */
- @Override
- public boolean isEmpty() {
- return super.isEmpty();
- }
-
- /**
* Removes all elements from the mapping of this Bundle.
*/
@Override
@@ -205,39 +171,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Returns true if the given key is contained in the mapping
- * of this Bundle.
- *
- * @param key a String key
- * @return true if the key is part of the mapping, false otherwise
- */
- @Override
- public boolean containsKey(String key) {
- return super.containsKey(key);
- }
-
- /**
- * Returns the entry with the given key as an object.
- *
- * @param key a String key
- * @return an Object, or null
- */
- @Override
- public Object get(String key) {
- return super.get(key);
- }
-
- /**
- * Removes any entry with the given key from the mapping of this Bundle.
- *
- * @param key a String key
- */
- @Override
- public void remove(String key) {
- super.remove(key);
- }
-
- /**
* Inserts all mappings from the given Bundle into this Bundle.
*
* @param bundle a Bundle
@@ -253,25 +186,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Inserts all mappings from the given PersistableBundle into this Bundle.
- *
- * @param bundle a PersistableBundle
- */
- public void putAll(PersistableBundle bundle) {
- super.putAll(bundle);
- }
-
- /**
- * Returns a Set containing the Strings used as keys in this Bundle.
- *
- * @return a Set of String keys
- */
- @Override
- public Set<String> keySet() {
- return super.keySet();
- }
-
- /**
* Reports whether the bundle contains any parcelled file descriptors.
*/
public boolean hasFileDescriptors() {
@@ -384,30 +298,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Inserts an int value into the mapping of this Bundle, replacing
- * any existing value for the given key.
- *
- * @param key a String, or null
- * @param value an int, or null
- */
- @Override
- public void putInt(String key, int value) {
- super.putInt(key, value);
- }
-
- /**
- * Inserts a long value into the mapping of this Bundle, replacing
- * any existing value for the given key.
- *
- * @param key a String, or null
- * @param value a long
- */
- @Override
- public void putLong(String key, long value) {
- super.putLong(key, value);
- }
-
- /**
* Inserts a float value into the mapping of this Bundle, replacing
* any existing value for the given key.
*
@@ -420,30 +310,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Inserts a double value into the mapping of this Bundle, replacing
- * any existing value for the given key.
- *
- * @param key a String, or null
- * @param value a double
- */
- @Override
- public void putDouble(String key, double value) {
- super.putDouble(key, value);
- }
-
- /**
- * Inserts a String value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a String, or null
- */
- @Override
- public void putString(String key, String value) {
- super.putString(key, value);
- }
-
- /**
* Inserts a CharSequence value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -616,30 +482,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Inserts an int array value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value an int array object, or null
- */
- @Override
- public void putIntArray(String key, int[] value) {
- super.putIntArray(key, value);
- }
-
- /**
- * Inserts a long array value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a long array object, or null
- */
- @Override
- public void putLongArray(String key, long[] value) {
- super.putLongArray(key, value);
- }
-
- /**
* Inserts a float array value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -652,30 +494,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Inserts a double array value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a double array object, or null
- */
- @Override
- public void putDoubleArray(String key, double[] value) {
- super.putDoubleArray(key, value);
- }
-
- /**
- * Inserts a String array value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a String array object, or null
- */
- @Override
- public void putStringArray(String key, String[] value) {
- super.putStringArray(key, value);
- }
-
- /**
* Inserts a CharSequence array value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
@@ -700,17 +518,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a Bundle object, or null
- */
- public void putPersistableBundle(String key, PersistableBundle value) {
- super.putPersistableBundle(key, value);
- }
-
- /**
* 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.
*
@@ -846,56 +653,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Returns the value associated with the given key, or 0 if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @return an int value
- */
- @Override
- public int getInt(String key) {
- return super.getInt(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @param defaultValue Value to return if key does not exist
- * @return an int value
- */
- @Override
- public int getInt(String key, int defaultValue) {
- return super.getInt(key, defaultValue);
- }
-
- /**
- * Returns the value associated with the given key, or 0L if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @return a long value
- */
- @Override
- public long getLong(String key) {
- return super.getLong(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @param defaultValue Value to return if key does not exist
- * @return a long value
- */
- @Override
- public long getLong(String key, long defaultValue) {
- return super.getLong(key, defaultValue);
- }
-
- /**
* Returns the value associated with the given key, or 0.0f if
* no mapping of the desired type exists for the given key.
*
@@ -921,58 +678,6 @@ public final class Bundle extends CommonBundle {
}
/**
- * Returns the value associated with the given key, or 0.0 if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @return a double value
- */
- @Override
- public double getDouble(String key) {
- return super.getDouble(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @param defaultValue Value to return if key does not exist
- * @return a double value
- */
- @Override
- public double getDouble(String key, double defaultValue) {
- return super.getDouble(key, defaultValue);
- }
-
- /**
- * 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 a String value, or null
- */
- @Override
- public String getString(String key) {
- return super.getString(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String, or null
- * @param defaultValue Value to return if key does not exist
- * @return the String value associated with the given key, or defaultValue
- * if no valid String object is currently mapped to that key.
- */
- @Override
- public String getString(String key, String defaultValue) {
- return super.getString(key, defaultValue);
- }
-
- /**
* 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.
@@ -1027,18 +732,6 @@ public final class Bundle extends CommonBundle {
* value is explicitly associated with the key.
*
* @param key a String, or null
- * @return a PersistableBundle value, or null
- */
- public PersistableBundle getPersistableBundle(String key) {
- return super.getPersistableBundle(key);
- }
-
- /**
- * 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 a Parcelable value, or null
*/
public <T extends Parcelable> T getParcelable(String key) {
@@ -1232,32 +925,6 @@ public final class Bundle extends CommonBundle {
* value is explicitly associated with the key.
*
* @param key a String, or null
- * @return an int[] value, or null
- */
- @Override
- public int[] getIntArray(String key) {
- return super.getIntArray(key);
- }
-
- /**
- * 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 a long[] value, or null
- */
- @Override
- public long[] getLongArray(String key) {
- return super.getLongArray(key);
- }
-
- /**
- * 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 a float[] value, or null
*/
@Override
@@ -1271,32 +938,6 @@ public final class Bundle extends CommonBundle {
* value is explicitly associated with the key.
*
* @param key a String, or null
- * @return a double[] value, or null
- */
- @Override
- public double[] getDoubleArray(String key) {
- return super.getDoubleArray(key);
- }
-
- /**
- * 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 a String[] value, or null
- */
- @Override
- public String[] getStringArray(String key) {
- return super.getStringArray(key);
- }
-
- /**
- * 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 a CharSequence[] value, or null
*/
@Override
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e98a26b..e84b695 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -191,6 +191,10 @@ public class Environment {
return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
}
+ public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
+ return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+ }
+
public File[] buildExternalStorageAppObbDirs(String packageName) {
return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
}
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index cd8d515..c01f688 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -32,7 +32,8 @@ import java.util.Set;
* restored.
*
*/
-public final class PersistableBundle extends CommonBundle implements XmlUtils.WriteMapCallback {
+public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
+ XmlUtils.WriteMapCallback {
private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
public static final PersistableBundle EMPTY;
static final Parcel EMPTY_PARCEL;
@@ -40,7 +41,7 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
static {
EMPTY = new PersistableBundle();
EMPTY.mMap = ArrayMap.EMPTY;
- EMPTY_PARCEL = CommonBundle.EMPTY_PARCEL;
+ EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL;
}
/**
@@ -51,31 +52,6 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
}
/**
- * Constructs a PersistableBundle whose data is stored as a Parcel. The data
- * will be unparcelled on first contact, using the assigned ClassLoader.
- *
- * @param parcelledData a Parcel containing a PersistableBundle
- */
- PersistableBundle(Parcel parcelledData) {
- super(parcelledData);
- }
-
- /* package */ PersistableBundle(Parcel parcelledData, int length) {
- super(parcelledData, length);
- }
-
- /**
- * Constructs a new, empty PersistableBundle that uses a specific ClassLoader for
- * instantiating Parcelable and Serializable objects.
- *
- * @param loader An explicit ClassLoader to use when instantiating objects
- * inside of the PersistableBundle.
- */
- public PersistableBundle(ClassLoader loader) {
- super(loader);
- }
-
- /**
* Constructs a new, empty PersistableBundle sized to hold the given number of
* elements. The PersistableBundle will grow as needed.
*
@@ -127,6 +103,10 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
}
}
+ /* package */ PersistableBundle(Parcel parcelledData, int length) {
+ super(parcelledData, length);
+ }
+
/**
* Make a PersistableBundle for a single key/value pair.
*
@@ -139,33 +119,6 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
}
/**
- * @hide
- */
- @Override
- public String getPairValue() {
- return super.getPairValue();
- }
-
- /**
- * Changes the ClassLoader this PersistableBundle uses when instantiating objects.
- *
- * @param loader An explicit ClassLoader to use when instantiating objects
- * inside of the PersistableBundle.
- */
- @Override
- public void setClassLoader(ClassLoader loader) {
- super.setClassLoader(loader);
- }
-
- /**
- * Return the ClassLoader currently associated with this PersistableBundle.
- */
- @Override
- public ClassLoader getClassLoader() {
- return super.getClassLoader();
- }
-
- /**
* Clones the current PersistableBundle. The internal map is cloned, but the keys and
* values to which it refers are copied by reference.
*/
@@ -175,300 +128,15 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
}
/**
- * @hide
- */
- @Override
- public boolean isParcelled() {
- return super.isParcelled();
- }
-
- /**
- * Returns the number of mappings contained in this PersistableBundle.
- *
- * @return the number of mappings as an int.
- */
- @Override
- public int size() {
- return super.size();
- }
-
- /**
- * Returns true if the mapping of this PersistableBundle is empty, false otherwise.
- */
- @Override
- public boolean isEmpty() {
- return super.isEmpty();
- }
-
- /**
- * Removes all elements from the mapping of this PersistableBundle.
- */
- @Override
- public void clear() {
- super.clear();
- }
-
- /**
- * Returns true if the given key is contained in the mapping
- * of this PersistableBundle.
- *
- * @param key a String key
- * @return true if the key is part of the mapping, false otherwise
- */
- @Override
- public boolean containsKey(String key) {
- return super.containsKey(key);
- }
-
- /**
- * Returns the entry with the given key as an object.
- *
- * @param key a String key
- * @return an Object, or null
- */
- @Override
- public Object get(String key) {
- return super.get(key);
- }
-
- /**
- * Removes any entry with the given key from the mapping of this PersistableBundle.
- *
- * @param key a String key
- */
- @Override
- public void remove(String key) {
- super.remove(key);
- }
-
- /**
- * Inserts all mappings from the given PersistableBundle into this Bundle.
- *
- * @param bundle a PersistableBundle
- */
- @Override
- public void putAll(PersistableBundle bundle) {
- super.putAll(bundle);
- }
-
- /**
- * Returns a Set containing the Strings used as keys in this PersistableBundle.
- *
- * @return a Set of String keys
- */
- @Override
- public Set<String> keySet() {
- return super.keySet();
- }
-
- /**
- * Inserts an int value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key.
- *
- * @param key a String, or null
- * @param value an int, or null
- */
- @Override
- public void putInt(String key, int value) {
- super.putInt(key, value);
- }
-
- /**
- * Inserts a long value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key.
- *
- * @param key a String, or null
- * @param value a long
- */
- @Override
- public void putLong(String key, long value) {
- super.putLong(key, value);
- }
-
- /**
- * Inserts a double value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key.
- *
- * @param key a String, or null
- * @param value a double
- */
- @Override
- public void putDouble(String key, double value) {
- super.putDouble(key, value);
- }
-
- /**
- * Inserts a String value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a String, or null
- */
- @Override
- public void putString(String key, String value) {
- super.putString(key, value);
- }
-
- /**
- * Inserts an int array value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value an int array object, or null
- */
- @Override
- public void putIntArray(String key, int[] value) {
- super.putIntArray(key, value);
- }
-
- /**
- * Inserts a long array value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a long array object, or null
- */
- @Override
- public void putLongArray(String key, long[] value) {
- super.putLongArray(key, value);
- }
-
- /**
- * Inserts a double array value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a double array object, or null
- */
- @Override
- public void putDoubleArray(String key, double[] value) {
- super.putDoubleArray(key, value);
- }
-
- /**
- * Inserts a String array value into the mapping of this PersistableBundle, replacing
- * any existing value for the given key. Either key or value may be null.
- *
- * @param key a String, or null
- * @param value a String array object, or null
- */
- @Override
- public void putStringArray(String key, String[] value) {
- super.putStringArray(key, value);
- }
-
- /**
* Inserts a PersistableBundle value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
* @param key a String, or null
* @param value a Bundle object, or null
*/
- @Override
public void putPersistableBundle(String key, PersistableBundle value) {
- super.putPersistableBundle(key, value);
- }
-
- /**
- * Returns the value associated with the given key, or 0 if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @return an int value
- */
- @Override
- public int getInt(String key) {
- return super.getInt(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @param defaultValue Value to return if key does not exist
- * @return an int value
- */
- @Override
- public int getInt(String key, int defaultValue) {
- return super.getInt(key, defaultValue);
- }
-
- /**
- * Returns the value associated with the given key, or 0L if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @return a long value
- */
- @Override
- public long getLong(String key) {
- return super.getLong(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @param defaultValue Value to return if key does not exist
- * @return a long value
- */
- @Override
- public long getLong(String key, long defaultValue) {
- return super.getLong(key, defaultValue);
- }
-
- /**
- * Returns the value associated with the given key, or 0.0 if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @return a double value
- */
- @Override
- public double getDouble(String key) {
- return super.getDouble(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String
- * @param defaultValue Value to return if key does not exist
- * @return a double value
- */
- @Override
- public double getDouble(String key, double defaultValue) {
- return super.getDouble(key, defaultValue);
- }
-
- /**
- * 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 a String value, or null
- */
- @Override
- public String getString(String key) {
- return super.getString(key);
- }
-
- /**
- * Returns the value associated with the given key, or defaultValue if
- * no mapping of the desired type exists for the given key.
- *
- * @param key a String, or null
- * @param defaultValue Value to return if key does not exist
- * @return the String value associated with the given key, or defaultValue
- * if no valid String object is currently mapped to that key.
- */
- @Override
- public String getString(String key, String defaultValue) {
- return super.getString(key, defaultValue);
+ unparcel();
+ mMap.put(key, value);
}
/**
@@ -479,61 +147,18 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
* @param key a String, or null
* @return a Bundle value, or null
*/
- @Override
public PersistableBundle getPersistableBundle(String key) {
- return super.getPersistableBundle(key);
- }
-
- /**
- * 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 int[] value, or null
- */
- @Override
- public int[] getIntArray(String key) {
- return super.getIntArray(key);
- }
-
- /**
- * 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 a long[] value, or null
- */
- @Override
- public long[] getLongArray(String key) {
- return super.getLongArray(key);
- }
-
- /**
- * 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 a double[] value, or null
- */
- @Override
- public double[] getDoubleArray(String key) {
- return super.getDoubleArray(key);
- }
-
- /**
- * 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 a String[] value, or null
- */
- @Override
- public String[] getStringArray(String key) {
- return super.getStringArray(key);
+ unparcel();
+ Object o = mMap.get(key);
+ if (o == null) {
+ return null;
+ }
+ try {
+ return (PersistableBundle) o;
+ } catch (ClassCastException e) {
+ typeWarning(key, o, "Bundle", e);
+ return null;
+ }
}
public static final Parcelable.Creator<PersistableBundle> CREATOR =
@@ -549,38 +174,6 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
}
};
- /**
- * Report the nature of this Parcelable's contents
- */
- @Override
- public int describeContents() {
- return 0;
- }
-
- /**
- * Writes the PersistableBundle contents to a Parcel, typically in order for
- * it to be passed through an IBinder connection.
- * @param parcel The parcel to copy this bundle to.
- */
- @Override
- public void writeToParcel(Parcel parcel, int flags) {
- final boolean oldAllowFds = parcel.pushAllowFds(false);
- try {
- super.writeToParcelInner(parcel, flags);
- } finally {
- parcel.restoreAllowFds(oldAllowFds);
- }
- }
-
- /**
- * Reads the Parcel contents into this PersistableBundle, typically in order for
- * it to be passed through an IBinder connection.
- * @param parcel The parcel to overwrite this bundle from.
- */
- public void readFromParcel(Parcel parcel) {
- super.readFromParcelInner(parcel);
- }
-
/** @hide */
@Override
public void writeUnknownObject(Object v, String name, XmlSerializer out)
@@ -614,8 +207,29 @@ public final class PersistableBundle extends CommonBundle implements XmlUtils.Wr
}
/**
- * @hide
+ * Report the nature of this Parcelable's contents
*/
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Writes the PersistableBundle contents to a Parcel, typically in order for
+ * it to be passed through an IBinder connection.
+ * @param parcel The parcel to copy this bundle to.
+ */
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ final boolean oldAllowFds = parcel.pushAllowFds(false);
+ try {
+ writeToParcelInner(parcel, flags);
+ } finally {
+ parcel.restoreAllowFds(oldAllowFds);
+ }
+ }
+
+ /** @hide */
public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
XmlPullParserException {
final int outerDepth = in.getDepth();
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 57ed979..474192f 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -70,6 +70,8 @@ public final class Trace {
public static final long TRACE_TAG_DALVIK = 1L << 14;
/** @hide */
public static final long TRACE_TAG_RS = 1L << 15;
+ /** @hide */
+ public static final long TRACE_TAG_BIONIC = 1L << 16;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 4963991..68b91cb 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -58,24 +58,6 @@ import java.util.concurrent.atomic.AtomicInteger;
* argument of {@link android.content.Context#STORAGE_SERVICE}.
*/
public class StorageManager {
-
- /// Consts to match the password types in cryptfs.h
- /** Master key is encrypted with a password.
- */
- public static final int CRYPT_TYPE_PASSWORD = 0;
-
- /** Master key is encrypted with the default password.
- */
- public static final int CRYPT_TYPE_DEFAULT = 1;
-
- /** Master key is encrypted with a pattern.
- */
- public static final int CRYPT_TYPE_PATTERN = 2;
-
- /** Master key is encrypted with a pin.
- */
- public static final int CRYPT_TYPE_PIN = 3;
-
private static final String TAG = "StorageManager";
private final ContentResolver mResolver;
@@ -663,4 +645,14 @@ public class StorageManager {
return Settings.Global.getLong(mResolver, Settings.Global.SYS_STORAGE_FULL_THRESHOLD_BYTES,
DEFAULT_FULL_THRESHOLD_BYTES);
}
+
+ /// Consts to match the password types in cryptfs.h
+ /** @hide */
+ public static final int CRYPT_TYPE_PASSWORD = 0;
+ /** @hide */
+ public static final int CRYPT_TYPE_DEFAULT = 1;
+ /** @hide */
+ public static final int CRYPT_TYPE_PATTERN = 2;
+ /** @hide */
+ public static final int CRYPT_TYPE_PIN = 3;
}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index d2d6ade..d66fc0f 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,10 @@
package android.preference;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -37,20 +40,25 @@ import android.widget.SeekBar.OnSeekBarChangeListener;
* @hide
*/
public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callback {
+ private static final String TAG = "SeekBarVolumizer";
public interface Callback {
void onSampleStarting(SeekBarVolumizer sbv);
}
- private Context mContext;
- private Handler mHandler;
+ private final Context mContext;
+ private final Handler mHandler;
+ private final H mUiHandler = new H();
private final Callback mCallback;
+ private final Uri mDefaultUri;
+ private final AudioManager mAudioManager;
+ private final int mStreamType;
+ private final int mMaxStreamVolume;
+ private final Receiver mReceiver = new Receiver();
+ private final Observer mVolumeObserver;
- private AudioManager mAudioManager;
- private int mStreamType;
private int mOriginalStreamVolume;
private Ringtone mRingtone;
-
private int mLastProgress = -1;
private SeekBar mSeekBar;
private int mVolumeBeforeMute = -1;
@@ -58,44 +66,25 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
private static final int MSG_SET_STREAM_VOLUME = 0;
private static final int MSG_START_SAMPLE = 1;
private static final int MSG_STOP_SAMPLE = 2;
+ private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
- private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- if (mSeekBar != null && mAudioManager != null) {
- int volume = mAudioManager.getStreamVolume(mStreamType);
- mSeekBar.setProgress(volume);
- }
- }
- };
-
- public SeekBarVolumizer(Context context, SeekBar seekBar, int streamType, Uri defaultUri,
+ public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
Callback callback) {
mContext = context;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mStreamType = streamType;
- mSeekBar = seekBar;
-
- HandlerThread thread = new HandlerThread(VolumePreference.TAG + ".CallbackHandler");
+ mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
+ HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
thread.start();
mHandler = new Handler(thread.getLooper(), this);
mCallback = callback;
-
- initSeekBar(seekBar, defaultUri);
- }
-
- private void initSeekBar(SeekBar seekBar, Uri defaultUri) {
- seekBar.setMax(mAudioManager.getStreamMaxVolume(mStreamType));
mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
- seekBar.setProgress(mOriginalStreamVolume);
- seekBar.setOnSeekBarChangeListener(this);
-
+ mVolumeObserver = new Observer(mHandler);
mContext.getContentResolver().registerContentObserver(
System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
false, mVolumeObserver);
-
+ mReceiver.setListening(true);
if (defaultUri == null) {
if (mStreamType == AudioManager.STREAM_RING) {
defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
@@ -105,12 +94,19 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
defaultUri = Settings.System.DEFAULT_ALARM_ALERT_URI;
}
}
+ mDefaultUri = defaultUri;
+ mHandler.sendEmptyMessage(MSG_INIT_SAMPLE);
+ }
- mRingtone = RingtoneManager.getRingtone(mContext, defaultUri);
-
- if (mRingtone != null) {
- mRingtone.setStreamType(mStreamType);
+ public void setSeekBar(SeekBar seekBar) {
+ if (mSeekBar != null) {
+ mSeekBar.setOnSeekBarChangeListener(null);
}
+ mSeekBar = seekBar;
+ mSeekBar.setOnSeekBarChangeListener(null);
+ mSeekBar.setMax(mMaxStreamVolume);
+ mSeekBar.setProgress(mLastProgress > -1 ? mLastProgress : mOriginalStreamVolume);
+ mSeekBar.setOnSeekBarChangeListener(this);
}
@Override
@@ -125,12 +121,22 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
case MSG_STOP_SAMPLE:
onStopSample();
break;
+ case MSG_INIT_SAMPLE:
+ onInitSample();
+ break;
default:
- Log.e(VolumePreference.TAG, "invalid SeekBarVolumizer message: "+msg.what);
+ Log.e(TAG, "invalid SeekBarVolumizer message: "+msg.what);
}
return true;
}
+ private void onInitSample() {
+ mRingtone = RingtoneManager.getRingtone(mContext, mDefaultUri);
+ if (mRingtone != null) {
+ mRingtone.setStreamType(mStreamType);
+ }
+ }
+
private void postStartSample() {
mHandler.removeMessages(MSG_START_SAMPLE);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_SAMPLE),
@@ -143,7 +149,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
mCallback.onSampleStarting(this);
}
if (mRingtone != null) {
- mRingtone.play();
+ try {
+ mRingtone.play();
+ } catch (Throwable e) {
+ Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
+ }
}
}
}
@@ -165,6 +175,8 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
postStopSample();
mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
mSeekBar.setOnSeekBarChangeListener(null);
+ mReceiver.setListening(false);
+ mHandler.getLooper().quitSafely();
}
public void revertVolume() {
@@ -245,4 +257,62 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
postSetVolume(mLastProgress);
}
}
-} \ No newline at end of file
+
+ private final class H extends Handler {
+ private static final int UPDATE_SLIDER = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == UPDATE_SLIDER) {
+ if (mSeekBar != null) {
+ mSeekBar.setProgress(msg.arg1);
+ mLastProgress = mSeekBar.getProgress();
+ }
+ }
+ }
+
+ public void postUpdateSlider(int volume) {
+ obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
+ }
+ }
+
+ private final class Observer extends ContentObserver {
+ public Observer(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mSeekBar != null && mAudioManager != null) {
+ final int volume = mAudioManager.getStreamVolume(mStreamType);
+ mUiHandler.postUpdateSlider(volume);
+ }
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+ private boolean mListening;
+
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ mContext.registerReceiver(this, filter);
+ } else {
+ mContext.unregisterReceiver(this);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction())) return;
+ final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ if (mSeekBar != null && streamType == mStreamType && streamValue != -1) {
+ mUiHandler.postUpdateSlider(streamValue);
+ }
+ }
+ }
+}
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
index 171e5c3..df9e10e 100644
--- a/core/java/android/preference/VolumePreference.java
+++ b/core/java/android/preference/VolumePreference.java
@@ -66,7 +66,8 @@ public class VolumePreference extends SeekBarDialogPreference implements
super.onBindDialogView(view);
final SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
- mSeekBarVolumizer = new SeekBarVolumizer(getContext(), seekBar, mStreamType, null, this);
+ mSeekBarVolumizer = new SeekBarVolumizer(getContext(), mStreamType, null, this);
+ mSeekBarVolumizer.setSeekBar(seekBar);
getPreferenceManager().registerOnActivityStopListener(this);
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b53ea81..6db78f4 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1152,8 +1152,6 @@ public final class ContactsContract {
* address book index, which is usually the first letter of the sort key.
* When this parameter is supplied, the row counts are returned in the
* cursor extras bundle.
- *
- * @hide
*/
public final static class ContactCounts {
@@ -1163,7 +1161,24 @@ public final class ContactsContract {
* first letter of the sort key. This parameter does not affect the main
* content of the cursor.
*
- * @hide
+ * <p>
+ * <pre>
+ * Example:
+ * Uri uri = Contacts.CONTENT_URI.buildUpon()
+ * .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true")
+ * .build();
+ * Cursor cursor = getContentResolver().query(uri,
+ * new String[] {Contacts.DISPLAY_NAME},
+ * null, null, null);
+ * Bundle bundle = cursor.getExtras();
+ * if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) &&
+ * bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) {
+ * String sections[] =
+ * bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+ * int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ * }
+ * </pre>
+ * </p>
*/
public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras";
@@ -1171,8 +1186,6 @@ public final class ContactsContract {
* The array of address book index titles, which are returned in the
* same order as the data in the cursor.
* <p>TYPE: String[]</p>
- *
- * @hide
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles";
@@ -1180,8 +1193,6 @@ public final class ContactsContract {
* The array of group counts for the corresponding group. Contains the same number
* of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array.
* <p>TYPE: int[]</p>
- *
- * @hide
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts";
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e9ffc52..bec401e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4461,6 +4461,12 @@ public final class Settings {
INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
/**
+ * Whether the device should wake when the wake gesture sensor detects motion.
+ * @hide
+ */
+ public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled";
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java
deleted file mode 100644
index e4f93a8..0000000
--- a/core/java/android/provider/TvContract.java
+++ /dev/null
@@ -1,741 +0,0 @@
-/*
- * Copyright (C) 2014 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.provider;
-
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.net.Uri;
-import android.tv.TvInputService;
-
-import java.util.List;
-
-/**
- * <p>
- * The contract between the TV provider and applications. Contains definitions for the supported
- * URIs and columns.
- * </p>
- * <h3>Overview</h3>
- * <p>
- * TvContract defines a basic database of TV content metadata such as channel and program
- * information. The information is stored in {@link Channels} and {@link Programs} tables.
- * </p>
- * <ul>
- * <li>A row in the {@link Channels} table represents information about a TV channel. The data
- * format can vary greatly from standard to standard or according to service provider, thus
- * the columns here are mostly comprised of basic entities that are usually seen to users
- * regardless of standard such as channel number and name.</li>
- * <li>A row in the {@link Programs} table represents a set of data describing a TV program such
- * as program title and start time.</li>
- * </ul>
- */
-public final class TvContract {
- /** The authority for the TV provider. */
- public static final String AUTHORITY = "com.android.tv";
-
- private static final String PATH_CHANNEL = "channel";
- private static final String PATH_PROGRAM = "program";
- private static final String PATH_INPUT = "input";
-
- /**
- * An optional query, update or delete URI parameter that allows the caller to specify start
- * time (in milliseconds since the epoch) to filter programs.
- *
- * @hide
- */
- public static final String PARAM_START_TIME = "start_time";
-
- /**
- * An optional query, update or delete URI parameter that allows the caller to specify end time
- * (in milliseconds since the epoch) to filter programs.
- *
- * @hide
- */
- public static final String PARAM_END_TIME = "end_time";
-
- /**
- * A query, update or delete URI parameter that allows the caller to operate on all or
- * browsable-only channels. If set to "true", the rows that contain non-browsable channels are
- * not affected.
- *
- * @hide
- */
- public static final String PARAM_BROWSABLE_ONLY = "browable_only";
-
- /**
- * Builds a URI that points to a specific channel.
- *
- * @param channelId The ID of the channel to point to.
- */
- public static final Uri buildChannelUri(long channelId) {
- return ContentUris.withAppendedId(Channels.CONTENT_URI, channelId);
- }
-
- /**
- * Builds a URI that points to all browsable channels from a given TV input.
- *
- * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements
- * the given TV input.
- */
- public static final Uri buildChannelsUriForInput(ComponentName name) {
- return buildChannelsUriForInput(name, true);
- }
-
- /**
- * Builds a URI that points to all or browsable-only channels from a given TV input.
- *
- * @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements
- * the given TV input.
- * @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set
- * to {@code false} the URI points to all channels regardless of whether they are
- * browsable or not.
- */
- public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
- .appendPath(PATH_INPUT).appendPath(name.getPackageName())
- .appendPath(name.getClassName()).appendPath(PATH_CHANNEL)
- .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
- }
-
- /**
- * Builds a URI that points to a specific program.
- *
- * @param programId The ID of the program to point to.
- */
- public static final Uri buildProgramUri(long programId) {
- return ContentUris.withAppendedId(Programs.CONTENT_URI, programId);
- }
-
- /**
- * Builds a URI that points to all programs on a given channel.
- *
- * @param channelUri The URI of the channel to return programs for.
- */
- public static final Uri buildProgramsUriForChannel(Uri channelUri) {
- if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
- throw new IllegalArgumentException("Not a channel: " + channelUri);
- }
- String channelId = String.valueOf(ContentUris.parseId(channelUri));
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
- .appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build();
- }
-
- /**
- * Builds a URI that points to programs on a specific channel whose schedules overlap with the
- * given time frame.
- *
- * @param channelUri The URI of the channel to return programs for.
- * @param startTime The start time used to filter programs. The returned programs should have
- * {@link Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this time.
- * @param endTime The end time used to filter programs. The returned programs should have
- * {@link Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time.
- */
- public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
- long endTime) {
- Uri uri = buildProgramsUriForChannel(channelUri);
- return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime))
- .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build();
- }
-
- /**
- * Builds a URI that points to a specific program the user watched.
- *
- * @param watchedProgramId The ID of the watched program to point to.
- * @hide
- */
- public static final Uri buildWatchedProgramUri(long watchedProgramId) {
- return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
- }
-
- /**
- * Extracts the {@link Channels#COLUMN_PACKAGE_NAME} from a given URI.
- *
- * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
- * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
- * @hide
- */
- public static final String getPackageName(Uri channelsUri) {
- final List<String> paths = channelsUri.getPathSegments();
- if (paths.size() < 4) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- return paths.get(1);
- }
-
- /**
- * Extracts the {@link Channels#COLUMN_SERVICE_NAME} from a given URI.
- *
- * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
- * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
- * @hide
- */
- public static final String getServiceName(Uri channelsUri) {
- final List<String> paths = channelsUri.getPathSegments();
- if (paths.size() < 4) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
- throw new IllegalArgumentException("Not channels: " + channelsUri);
- }
- return paths.get(2);
- }
-
- /**
- * Extracts the {@link Channels#_ID} from a given URI.
- *
- * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
- * {@link #buildProgramsUriForChannel(Uri, long, long)}.
- * @hide
- */
- public static final String getChannelId(Uri programsUri) {
- final List<String> paths = programsUri.getPathSegments();
- if (paths.size() < 3) {
- throw new IllegalArgumentException("Not programs: " + programsUri);
- }
- if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) {
- throw new IllegalArgumentException("Not programs: " + programsUri);
- }
- return paths.get(1);
- }
-
-
- private TvContract() {}
-
- /**
- * Common base for the tables of TV channels/programs.
- */
- public interface BaseTvColumns extends BaseColumns {
- /**
- * The name of the package that owns a row in each table.
- * <p>
- * The TV provider fills it in with the name of the package that provides the initial data
- * of that row. If the package is later uninstalled, the rows it owns are automatically
- * removed from the tables.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_PACKAGE_NAME = "package_name";
- }
-
- /** Column definitions for the TV channels table. */
- public static final class Channels implements BaseTvColumns {
-
- /** The content:// style URI for this table. */
- public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
- + PATH_CHANNEL);
-
- /** The MIME type of a directory of TV channels. */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.com.android.tv.channels";
-
- /** The MIME type of a single TV channel. */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.com.android.tv.channels";
-
- /** A generic channel type. */
- public static final int TYPE_OTHER = 0x0;
-
- /** The special channel type used for pass-through inputs such as HDMI. */
- public static final int TYPE_PASSTHROUGH = 0x00010000;
-
- /** The channel type for DVB-T (terrestrial). */
- public static final int TYPE_DVB_T = 0x00020000;
-
- /** The channel type for DVB-T2 (terrestrial). */
- public static final int TYPE_DVB_T2 = 0x00020001;
-
- /** The channel type for DVB-S (satellite). */
- public static final int TYPE_DVB_S = 0x00020100;
-
- /** The channel type for DVB-S2 (satellite). */
- public static final int TYPE_DVB_S2 = 0x00020101;
-
- /** The channel type for DVB-C (cable). */
- public static final int TYPE_DVB_C = 0x00020200;
-
- /** The channel type for DVB-C2 (cable). */
- public static final int TYPE_DVB_C2 = 0x00020201;
-
- /** The channel type for DVB-H (handheld). */
- public static final int TYPE_DVB_H = 0x00020300;
-
- /** The channel type for DVB-SH (satellite). */
- public static final int TYPE_DVB_SH = 0x00020400;
-
- /** The channel type for ATSC (terrestrial). */
- public static final int TYPE_ATSC_T = 0x00030000;
-
- /** The channel type for ATSC (cable). */
- public static final int TYPE_ATSC_C = 0x00030200;
-
- /** The channel type for ATSC-M/H (mobile/handheld). */
- public static final int TYPE_ATSC_M_H = 0x00030200;
-
- /** The channel type for ISDB-T (terrestrial). */
- public static final int TYPE_ISDB_T = 0x00040000;
-
- /** The channel type for ISDB-Tb (Brazil). */
- public static final int TYPE_ISDB_TB = 0x00040100;
-
- /** The channel type for ISDB-S (satellite). */
- public static final int TYPE_ISDB_S = 0x00040200;
-
- /** The channel type for ISDB-C (cable). */
- public static final int TYPE_ISDB_C = 0x00040300;
-
- /** The channel type for 1seg (handheld). */
- public static final int TYPE_1SEG = 0x00040400;
-
- /** The channel type for DTMB (terrestrial). */
- public static final int TYPE_DTMB = 0x00050000;
-
- /** The channel type for CMMB (handheld). */
- public static final int TYPE_CMMB = 0x00050100;
-
- /** The channel type for T-DMB (terrestrial). */
- public static final int TYPE_T_DMB = 0x00060000;
-
- /** The channel type for S-DMB (satellite). */
- public static final int TYPE_S_DMB = 0x00060100;
-
- /** A generic service type. */
- public static final int SERVICE_TYPE_OTHER = 0x0;
-
- /** The service type for regular TV channels. */
- public static final int SERVICE_TYPE_TV = 0x1;
-
- /** The service type for radio channels. */
- public static final int SERVICE_TYPE_RADIO = 0x2;
-
- /**
- * The name of the {@link TvInputService} subclass that provides this TV channel. This
- * should be a fully qualified class name (such as, "com.example.project.TvInputService").
- * <p>
- * This is a required field.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_SERVICE_NAME = "service_name";
-
- /**
- * The predefined type of this TV channel.
- * <p>
- * This is primarily used to indicate which broadcast standard (e.g. ATSC, DVB or ISDB) the
- * current channel conforms to, with an exception being {@link #TYPE_PASSTHROUGH}, which is
- * a special channel type used only by pass-through inputs such as HDMI. The value should
- * match to one of the followings: {@link #TYPE_OTHER}, {@link #TYPE_PASSTHROUGH},
- * {@link #TYPE_DVB_T}, {@link #TYPE_DVB_T2}, {@link #TYPE_DVB_S}, {@link #TYPE_DVB_S2},
- * {@link #TYPE_DVB_C}, {@link #TYPE_DVB_C2}, {@link #TYPE_DVB_H}, {@link #TYPE_DVB_SH},
- * {@link #TYPE_ATSC_T}, {@link #TYPE_ATSC_C}, {@link #TYPE_ATSC_M_H}, {@link #TYPE_ISDB_T},
- * {@link #TYPE_ISDB_TB}, {@link #TYPE_ISDB_S}, {@link #TYPE_ISDB_C} {@link #TYPE_1SEG},
- * {@link #TYPE_DTMB}, {@link #TYPE_CMMB}, {@link #TYPE_T_DMB}, {@link #TYPE_S_DMB}
- * </p><p>
- * This is a required field.
- * </p><p>
- * Type: INTEGER
- * </p>
- */
- public static final String COLUMN_TYPE = "type";
-
- /**
- * The predefined service type of this TV channel.
- * <p>
- * This is primarily used to indicate whether the current channel is a regular TV channel or
- * a radio-like channel. Use the same coding for {@code service_type} in the underlying
- * broadcast standard if it is defined there (e.g. ATSC A/53, ETSI EN 300 468 and ARIB
- * STD-B10). Otherwise use one of the followings: {@link #SERVICE_TYPE_OTHER},
- * {@link #SERVICE_TYPE_TV}, {@link #SERVICE_TYPE_RADIO}
- * </p><p>
- * This is a required field.
- * </p><p>
- * Type: INTEGER
- * </p>
- */
- public static final String COLUMN_SERVICE_TYPE = "service_type";
-
- /**
- * The original network ID of this TV channel.
- * <p>
- * This is used to identify the originating delivery system, if applicable. Use the same
- * coding for {@code origianal_network_id} in the underlying broadcast standard if it is
- * defined there (e.g. ETSI EN 300 468/TR 101 211 and ARIB STD-B10). If channels cannot be
- * globally identified by 2-tuple {{@link #COLUMN_TRANSPORT_STREAM_ID},
- * {@link #COLUMN_SERVICE_ID}}, one must carefully assign a value to this field to form a
- * unique 3-tuple identification {{@link #COLUMN_ORIGINAL_NETWORK_ID},
- * {@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}} for its channels.
- * </p><p>
- * This is a required field if the channel cannot be uniquely identified by a 2-tuple
- * {{@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}}.
- * </p><p>
- * Type: INTEGER
- * </p>
- */
- public static final String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id";
-
- /**
- * The transport stream ID of this channel.
- * <p>
- * This is used to identify the Transport Stream that contains the current channel from any
- * other multiplex within a network, if applicable. Use the same coding for
- * {@code transport_stream_id} defined in ISO/IEC 13818-1 if the channel is transmitted via
- * the MPEG Transport Stream as is the case for many digital broadcast standards.
- * </p><p>
- * This is a required field if the current channel is transmitted via the MPEG Transport
- * Stream.
- * </p><p>
- * Type: INTEGER
- * </p>
- */
- public static final String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
-
- /**
- * The service ID of this channel.
- * <p>
- * This is used to identify the current service (roughly equivalent to channel) from any
- * other service within the Transport Stream, if applicable. Use the same coding for
- * {@code service_id} in the underlying broadcast standard if it is defined there (e.g. ETSI
- * EN 300 468 and ARIB STD-B10) or {@code program_number} (which usually has the same value
- * as {@code service_id}) in ISO/IEC 13818-1 if the channel is transmitted via the MPEG
- * Transport Stream.
- * </p><p>
- * This is a required field if the current channel is transmitted via the MPEG Transport
- * Stream.
- * </p><p>
- * Type: INTEGER
- * </p>
- */
- public static final String COLUMN_SERVICE_ID = "service_id";
-
- /**
- * The channel number that is displayed to the user.
- * <p>
- * The format can vary depending on broadcast standard and product specification.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_DISPLAY_NUMBER = "display_number";
-
- /**
- * The channel name that is displayed to the user.
- * <p>
- * A call sign is a good candidate to use for this purpose but any name that helps the user
- * recognize the current channel will be enough. Can also be empty depending on broadcast
- * standard.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_DISPLAY_NAME = "display_name";
-
- /**
- * The description of this TV channel.
- * <p>
- * Can be empty initially.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_DESCRIPTION = "description";
-
- /**
- * The flag indicating whether this TV channel is browsable or not.
- * <p>
- * A value of 1 indicates the channel is included in the channel list that applications use
- * to browse channels, a value of 0 indicates the channel is not included in the list. If
- * not specified, this value is set to 1 (browsable) by default.
- * </p><p>
- * Type: INTEGER (boolean)
- * </p>
- */
- public static final String COLUMN_BROWSABLE = "browsable";
-
- /**
- * The flag indicating whether this TV channel is searchable or not.
- * <p>
- * In some regions, it is not allowed to surface search results for a given channel without
- * broadcaster's consent. This is used to impose such restriction. A value of 1 indicates
- * the channel is searchable and can be included in search results, a value of 0 indicates
- * the channel and its TV programs are hidden from search. If not specified, this value is
- * set to 1 (searchable) by default.
- * </p>
- * <p>
- * Type: INTEGER (boolean)
- * </p>
- */
- public static final String COLUMN_SEARCHABLE = "searchable";
-
- /**
- * The flag indicating whether this TV channel is locked or not.
- * <p>
- * This is primarily used for alternative parental control to prevent unauthorized users
- * from watching the current channel regardless of the content rating. A value of 1
- * indicates the channel is locked and the user is required to enter passcode to unlock it
- * in order to watch the current program from the channel, a value of 0 indicates the
- * channel is not locked thus the user is not prompted to enter passcode If not specified,
- * this value is set to 0 (not locked) by default.
- * </p><p>
- * Type: INTEGER (boolean)
- * </p>
- */
- public static final String COLUMN_LOCKED = "locked";
-
- /**
- * Generic data used by individual TV input services.
- * <p>
- * Type: BLOB
- * </p>
- */
- public static final String COLUMN_DATA = "data";
-
-
- /**
- * The version number of this row entry used by TV input services.
- * <p>
- * This is best used by sync adapters to identify the rows to update. The number can be
- * defined by individual TV input services. One may assign the same value as
- * {@code version_number} that appears in ETSI EN 300 468 or ATSC A/65, if the data are
- * coming from a TV broadcast.
- * </p><p>
- * Type: INTEGER
- * </p>
- */
- public static final String COLUMN_VERSION_NUMBER = "version_number";
-
- private Channels() {}
- }
-
- /** Column definitions for the TV programs table. */
- public static final class Programs implements BaseTvColumns {
-
- /** The content:// style URI for this table. */
- public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
- + PATH_PROGRAM);
-
- /** The MIME type of a directory of TV programs. */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.com.android.tv.programs";
-
- /** The MIME type of a single TV program. */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.com.android.tv.programs";
-
- /**
- * The ID of the TV channel that contains this TV program.
- * <p>
- * This is a part of the channel URI and matches to {@link BaseColumns#_ID}.
- * </p><p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_CHANNEL_ID = "channel_id";
-
- /**
- * The title of this TV program.
- * <p>
- * Type: TEXT
- * </p>
- **/
- public static final String COLUMN_TITLE = "title";
-
- /**
- * The start time of this TV program, in milliseconds since the epoch.
- * <p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
-
- /**
- * The end time of this TV program, in milliseconds since the epoch.
- * <p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
-
- /**
- * The comma-separated genre string of this TV program.
- * <p>
- * Use the same language appeared in the underlying broadcast standard, if applicable. (For
- * example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or
- * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, use one of the
- * following genres:
- * <ul>
- * <li>Family/Kids</li>
- * <li>Sports</li>
- * <li>Shopping</li>
- * <li>Movies</li>
- * <li>Comedy</li>
- * <li>Travel</li>
- * <li>Drama</li>
- * <li>Education</li>
- * <li>Animal/Wildlife</li>
- * <li>News</li>
- * <li>Gaming</li>
- * <li>Others</li>
- * </ul>
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_GENRE = "genre";
-
- /**
- * The description of this TV program that is displayed to the user by default.
- * <p>
- * The maximum length of this field is 256 characters.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_DESCRIPTION = "description";
-
- /**
- * The detailed, lengthy description of this TV program that is displayed only when the user
- * wants to see more information.
- * <p>
- * TV input services should leave this field empty if they have no additional
- * details beyond {@link #COLUMN_DESCRIPTION}.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_LONG_DESCRIPTION = "long_description";
-
- /**
- * The comma-separated audio languages of this TV program.
- * <p>
- * This is used to describe available audio languages included in the program. Use
- * 3-character language code as specified by ISO 639-2.
- * </p><p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_AUDIO_LANGUAGE = "audio_language";
-
- /**
- * Generic data used by TV input services.
- * <p>
- * Type: BLOB
- * </p>
- */
- public static final String COLUMN_DATA = "data";
-
- /**
- * The version number of this row entry used by TV input services.
- * <p>
- * This is best used by sync adapters to identify the rows to update. The number can be
- * defined by individual TV input services. One may assign the same value as
- * {@code version_number} in ETSI EN 300 468 or ATSC A/65, if the data are coming from a TV
- * broadcast.
- * </p><p>
- * Type: INTEGER
- * </p>
- */
- public static final String COLUMN_VERSION_NUMBER = "version_number";
-
- private Programs() {}
- }
-
- /**
- * Column definitions for the TV programs that the user watched. Applications do not have access
- * to this table.
- *
- * @hide
- */
- public static final class WatchedPrograms implements BaseColumns {
-
- /** The content:// style URI for this table. */
- public static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/watched_program");
-
- /** The MIME type of a directory of watched programs. */
- public static final String CONTENT_TYPE =
- "vnd.android.cursor.dir/vnd.com.android.tv.watched_programs";
-
- /** The MIME type of a single item in this table. */
- public static final String CONTENT_ITEM_TYPE =
- "vnd.android.cursor.item/vnd.com.android.tv.watched_programs";
-
- /**
- * The UTC time that the user started watching this TV program, in milliseconds since the
- * epoch.
- * <p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_WATCH_START_TIME_UTC_MILLIS =
- "watch_start_time_utc_millis";
-
- /**
- * The UTC time that the user stopped watching this TV program, in milliseconds since the
- * epoch.
- * <p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis";
-
- /**
- * The channel ID that contains this TV program.
- * <p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_CHANNEL_ID = "channel_id";
-
- /**
- * The title of this TV program.
- * <p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_TITLE = "title";
-
- /**
- * The start time of this TV program, in milliseconds since the epoch.
- * <p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
-
- /**
- * The end time of this TV program, in milliseconds since the epoch.
- * <p>
- * Type: INTEGER (long)
- * </p>
- */
- public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
-
- /**
- * The description of this TV program.
- * <p>
- * Type: TEXT
- * </p>
- */
- public static final String COLUMN_DESCRIPTION = "description";
-
- private WatchedPrograms() {}
- }
-}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 846e292..d02fc7b 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -41,12 +41,18 @@ public class ZenModeConfig implements Parcelable {
public static final String SLEEP_MODE_NIGHTS = "nights";
public static final String SLEEP_MODE_WEEKNIGHTS = "weeknights";
+ public static final int SOURCE_ANYONE = 0;
+ public static final int SOURCE_CONTACT = 1;
+ public static final int SOURCE_STAR = 2;
+ public static final int MAX_SOURCE = SOURCE_STAR;
+
private static final int XML_VERSION = 1;
private static final String ZEN_TAG = "zen";
private static final String ZEN_ATT_VERSION = "version";
private static final String ALLOW_TAG = "allow";
private static final String ALLOW_ATT_CALLS = "calls";
private static final String ALLOW_ATT_MESSAGES = "messages";
+ private static final String ALLOW_ATT_FROM = "from";
private static final String SLEEP_TAG = "sleep";
private static final String SLEEP_ATT_MODE = "mode";
@@ -61,6 +67,7 @@ public class ZenModeConfig implements Parcelable {
public boolean allowCalls;
public boolean allowMessages;
+ public int allowFrom = SOURCE_ANYONE;
public String sleepMode;
public int sleepStartHour;
@@ -92,6 +99,7 @@ public class ZenModeConfig implements Parcelable {
conditionIds = new Uri[len];
source.readTypedArray(conditionIds, Uri.CREATOR);
}
+ allowFrom = source.readInt();
}
@Override
@@ -120,6 +128,7 @@ public class ZenModeConfig implements Parcelable {
} else {
dest.writeInt(0);
}
+ dest.writeInt(allowFrom);
}
@Override
@@ -127,6 +136,7 @@ public class ZenModeConfig implements Parcelable {
return new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
.append("allowCalls=").append(allowCalls)
.append(",allowMessages=").append(allowMessages)
+ .append(",allowFrom=").append(sourceToString(allowFrom))
.append(",sleepMode=").append(sleepMode)
.append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
.append(",sleepEnd=").append(sleepEndHour).append('.').append(sleepEndMinute)
@@ -137,6 +147,19 @@ public class ZenModeConfig implements Parcelable {
.append(']').toString();
}
+ public static String sourceToString(int source) {
+ switch (source) {
+ case SOURCE_ANYONE:
+ return "anyone";
+ case SOURCE_CONTACT:
+ return "contacts";
+ case SOURCE_STAR:
+ return "stars";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof ZenModeConfig)) return false;
@@ -144,6 +167,7 @@ public class ZenModeConfig implements Parcelable {
final ZenModeConfig other = (ZenModeConfig) o;
return other.allowCalls == allowCalls
&& other.allowMessages == allowMessages
+ && other.allowFrom == allowFrom
&& Objects.equals(other.sleepMode, sleepMode)
&& other.sleepStartHour == sleepStartHour
&& other.sleepStartMinute == sleepStartMinute
@@ -155,8 +179,8 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowMessages, sleepMode, sleepStartHour,
- sleepStartMinute, sleepEndHour, sleepEndMinute,
+ return Objects.hash(allowCalls, allowMessages, allowFrom, sleepMode,
+ sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute,
Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds));
}
@@ -191,6 +215,10 @@ public class ZenModeConfig implements Parcelable {
if (ALLOW_TAG.equals(tag)) {
rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
+ if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
+ throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom);
+ }
} else if (SLEEP_TAG.equals(tag)) {
final String mode = parser.getAttributeValue(null, SLEEP_ATT_MODE);
rt.sleepMode = (SLEEP_MODE_NIGHTS.equals(mode)
@@ -224,6 +252,7 @@ public class ZenModeConfig implements Parcelable {
out.startTag(null, ALLOW_TAG);
out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
+ out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
out.endTag(null, ALLOW_TAG);
out.startTag(null, SLEEP_TAG);
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index a83544d..2e9077a 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -20,8 +20,8 @@ import android.app.Dialog;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
-import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.graphics.Region;
import android.inputmethodservice.SoftInputWindow;
import android.os.Binder;
@@ -32,6 +32,7 @@ import android.os.Message;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -46,9 +47,22 @@ import com.android.internal.app.IVoiceInteractorRequest;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.lang.ref.WeakReference;
+
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+/**
+ * An active voice interaction session, providing a facility for the implementation
+ * to interact with the user in the voice interaction layer. This interface is no shown
+ * by default, but you can request that it be shown with {@link #showWindow()}, which
+ * will result in a later call to {@link #onCreateContentView()} in which the UI can be
+ * built
+ *
+ * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
+ * when done. It can also initiate voice interactions with applications by calling
+ * {@link #startVoiceActivity}</p>.
+ */
public abstract class VoiceInteractionSession implements KeyEvent.Callback {
static final String TAG = "VoiceInteractionSession";
static final boolean DEBUG = true;
@@ -79,11 +93,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
+ final WeakReference<VoiceInteractionSession> mWeakRef
+ = new WeakReference<VoiceInteractionSession>(this);
+
final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
@Override
public IVoiceInteractorRequest startConfirmation(String callingPackage,
- IVoiceInteractorCallback callback, String prompt, Bundle extras) {
- Request request = findRequest(callback, true);
+ IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) {
+ Request request = newRequest(callback);
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
new Caller(callingPackage, Binder.getCallingUid()), request,
prompt, extras));
@@ -91,9 +108,19 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
@Override
+ public IVoiceInteractorRequest startAbortVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
+ Request request = newRequest(callback);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ message, extras));
+ return request.mInterface;
+ }
+
+ @Override
public IVoiceInteractorRequest startCommand(String callingPackage,
IVoiceInteractorCallback callback, String command, Bundle extras) {
- Request request = findRequest(callback, true);
+ Request request = newRequest(callback);
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
new Caller(callingPackage, Binder.getCallingUid()), request,
command, extras));
@@ -142,29 +169,60 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
@Override
public void cancel() throws RemoteException {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ VoiceInteractionSession session = mSession.get();
+ if (session != null) {
+ session.mHandlerCaller.sendMessage(
+ session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ }
}
};
final IVoiceInteractorCallback mCallback;
- final HandlerCaller mHandlerCaller;
- Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) {
+ final WeakReference<VoiceInteractionSession> mSession;
+
+ Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) {
mCallback = callback;
- mHandlerCaller = handlerCaller;
+ mSession = session.mWeakRef;
+ }
+
+ void finishRequest() {
+ VoiceInteractionSession session = mSession.get();
+ if (session == null) {
+ throw new IllegalStateException("VoiceInteractionSession has been destroyed");
+ }
+ Request req = session.removeRequest(mInterface.asBinder());
+ if (req == null) {
+ throw new IllegalStateException("Request not active: " + this);
+ } else if (req != this) {
+ throw new IllegalStateException("Current active request " + req
+ + " not same as calling request " + this);
+ }
}
public void sendConfirmResult(boolean confirmed, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ " confirmed=" + confirmed + " result=" + result);
+ finishRequest();
mCallback.deliverConfirmationResult(mInterface, confirmed, result);
} catch (RemoteException e) {
}
}
+ public void sendAbortVoiceResult(Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ + " result=" + result);
+ finishRequest();
+ mCallback.deliverAbortVoiceResult(mInterface, result);
+ } catch (RemoteException e) {
+ }
+ }
+
public void sendCommandResult(boolean complete, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ " result=" + result);
+ finishRequest();
mCallback.deliverCommandResult(mInterface, complete, result);
} catch (RemoteException e) {
}
@@ -173,6 +231,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
public void sendCancelResult() {
try {
if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
+ finishRequest();
mCallback.deliverCancel(mInterface);
} catch (RemoteException e) {
}
@@ -190,9 +249,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
static final int MSG_START_CONFIRMATION = 1;
- static final int MSG_START_COMMAND = 2;
- static final int MSG_SUPPORTS_COMMANDS = 3;
- static final int MSG_CANCEL = 4;
+ static final int MSG_START_ABORT_VOICE = 2;
+ static final int MSG_START_COMMAND = 3;
+ static final int MSG_SUPPORTS_COMMANDS = 4;
+ static final int MSG_CANCEL = 5;
static final int MSG_TASK_STARTED = 100;
static final int MSG_TASK_FINISHED = 101;
@@ -208,9 +268,16 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
args = (SomeArgs)msg.obj;
if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface
+ " prompt=" + args.arg3 + " extras=" + args.arg4);
- onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3,
+ onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
(Bundle)args.arg4);
break;
+ case MSG_START_ABORT_VOICE:
+ args = (SomeArgs)msg.obj;
+ if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface
+ + " message=" + args.arg3 + " extras=" + args.arg4);
+ onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3,
+ (Bundle) args.arg4);
+ break;
case MSG_START_COMMAND:
args = (SomeArgs)msg.obj;
if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
@@ -262,14 +329,14 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
*/
public static final class Insets {
/**
- * This is the top part of the UI that is the main content. It is
+ * This is the part of the UI that is the main content. It is
* used to determine the basic space needed, to resize/pan the
* application behind. It is assumed that this inset does not
* change very much, since any change will cause a full resize/pan
* of the application behind. This value is relative to the top edge
* of the input method window.
*/
- public int contentTopInsets;
+ public final Rect contentInsets = new Rect();
/**
* This is the region of the UI that is touchable. It is used when
@@ -311,7 +378,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
new ViewTreeObserver.OnComputeInternalInsetsListener() {
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
onComputeInsets(mTmpInsets);
- info.contentInsets.top = info.visibleInsets.top = mTmpInsets.contentTopInsets;
+ info.contentInsets.set(mTmpInsets.contentInsets);
+ info.visibleInsets.set(mTmpInsets.contentInsets);
info.touchableRegion.set(mTmpInsets.touchableRegion);
info.setTouchableInsets(mTmpInsets.touchableInsets);
}
@@ -327,18 +395,20 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mCallbacks, true);
}
- Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) {
+ Request newRequest(IVoiceInteractorCallback callback) {
+ synchronized (this) {
+ Request req = new Request(callback, this);
+ mActiveRequests.put(req.mInterface.asBinder(), req);
+ return req;
+ }
+ }
+
+ Request removeRequest(IBinder reqInterface) {
synchronized (this) {
- Request req = mActiveRequests.get(callback.asBinder());
+ Request req = mActiveRequests.get(reqInterface);
if (req != null) {
- if (newRequest) {
- throw new IllegalArgumentException("Given request callback " + callback
- + " is already active");
- }
- return req;
+ mActiveRequests.remove(req);
}
- req = new Request(callback, mHandlerCaller);
- mActiveRequests.put(callback.asBinder(), req);
return req;
}
}
@@ -423,11 +493,34 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mTheme = theme;
}
+ /**
+ * Ask that a new activity be started for voice interaction. This will create a
+ * new dedicated task in the activity manager for this voice interaction session;
+ * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
+ * will be set for you to make it a new task.
+ *
+ * <p>The newly started activity will be displayed to the user in a special way, as
+ * a layer under the voice interaction UI.</p>
+ *
+ * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor}
+ * through which it can perform voice interactions through your session. These requests
+ * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands},
+ * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}.
+ *
+ * <p>You will receive a call to {@link #onTaskStarted} when the task starts up
+ * and {@link #onTaskFinished} when the last activity has finished.
+ *
+ * @param intent The Intent to start this voice interaction. The given Intent will
+ * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
+ * this is part of a voice interaction.
+ */
public void startVoiceActivity(Intent intent) {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
}
try {
+ intent.migrateExtraStreamToClipData();
+ intent.prepareToLeaveProcess();
int res = mSystemService.startVoiceActivity(mToken, intent,
intent.resolveType(mContext.getContentResolver()));
Instrumentation.checkStartActivityResult(res, intent);
@@ -435,14 +528,23 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
+ /**
+ * Convenience for inflating views.
+ */
public LayoutInflater getLayoutInflater() {
return mInflater;
}
+ /**
+ * Retrieve the window being used to show the session's UI.
+ */
public Dialog getWindow() {
return mWindow;
}
+ /**
+ * Finish the session.
+ */
public void finish() {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
@@ -454,22 +556,35 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
+ /**
+ * Initiatize a new session.
+ *
+ * @param args The arguments that were supplied to
+ * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}.
+ */
public void onCreate(Bundle args) {
mTheme = mTheme != 0 ? mTheme
: com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
mInflater = (LayoutInflater)mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
- mCallbacks, this, mDispatcherState, true);
+ mCallbacks, this, mDispatcherState,
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.TOP, true);
mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
initViews();
mWindow.getWindow().setLayout(MATCH_PARENT, WRAP_CONTENT);
mWindow.setToken(mToken);
}
+ /**
+ * Last callback to the session as it is being finished.
+ */
public void onDestroy() {
}
+ /**
+ * Hook in which to create the session's UI.
+ */
public View onCreateContentView() {
return null;
}
@@ -502,6 +617,11 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
finish();
}
+ /**
+ * Sessions automatically watch for requests that all system UI be closed (such as when
+ * the user presses HOME), which will appear here. The default implementation always
+ * calls {@link #finish}.
+ */
public void onCloseSystemDialogs() {
finish();
}
@@ -517,20 +637,106 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
int[] loc = mTmpLocation;
View decor = getWindow().getWindow().getDecorView();
decor.getLocationInWindow(loc);
- outInsets.contentTopInsets = loc[1];
+ outInsets.contentInsets.top = 0;
+ outInsets.contentInsets.left = 0;
+ outInsets.contentInsets.right = 0;
+ outInsets.contentInsets.bottom = 0;
outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_FRAME;
outInsets.touchableRegion.setEmpty();
}
+ /**
+ * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
+ * has actually started.
+ *
+ * @param intent The original {@link Intent} supplied to
+ * {@link #startVoiceActivity(android.content.Intent)}.
+ * @param taskId Unique ID of the now running task.
+ */
public void onTaskStarted(Intent intent, int taskId) {
}
+ /**
+ * Called when the last activity of a task initiated by
+ * {@link #startVoiceActivity(android.content.Intent)} has finished. The default
+ * implementation calls {@link #finish()} on the assumption that this represents
+ * the completion of a voice action. You can override the implementation if you would
+ * like a different behavior.
+ *
+ * @param intent The original {@link Intent} supplied to
+ * {@link #startVoiceActivity(android.content.Intent)}.
+ * @param taskId Unique ID of the finished task.
+ */
public void onTaskFinished(Intent intent, int taskId) {
finish();
}
- public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands);
- public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras);
+ /**
+ * Request to query for what extended commands the session supports.
+ *
+ * @param caller Who is making the request.
+ * @param commands An array of commands that are being queried.
+ * @return Return an array of booleans indicating which of each entry in the
+ * command array is supported. A true entry in the array indicates the command
+ * is supported; false indicates it is not. The default implementation returns
+ * an array of all false entries.
+ */
+ public boolean[] onGetSupportedCommands(Caller caller, String[] commands) {
+ return new boolean[commands.length];
+ }
+
+ /**
+ * Request to confirm with the user before proceeding with an unrecoverable operation,
+ * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
+ * VoiceInteractor.ConfirmationRequest}.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param prompt The prompt informing the user of what will happen, as per
+ * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+ */
+ public abstract void onConfirm(Caller caller, Request request, CharSequence prompt,
+ Bundle extras);
+
+ /**
+ * Request to abort the voice interaction session because the voice activity can not
+ * complete its interaction using voice. Corresponds to
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest
+ * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty
+ * confirmation back to allow the activity to exit.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param message The message informing the user of the problem, as per
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+ */
+ public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+ request.sendAbortVoiceResult(null);
+ }
+
+ /**
+ * Process an arbitrary extended command from the caller,
+ * corresponding to a {@link android.app.VoiceInteractor.CommandRequest
+ * VoiceInteractor.CommandRequest}.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param command The command that is being executed, as per
+ * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+ */
public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
+
+ /**
+ * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
+ * that was previously delivered to {@link #onConfirm} or {@link #onCommand}.
+ *
+ * @param request The request that is being canceled.
+ */
public abstract void onCancel(Request request);
}
diff --git a/core/java/android/speech/tts/Markup.java b/core/java/android/speech/tts/Markup.java
new file mode 100644
index 0000000..c886e5d
--- /dev/null
+++ b/core/java/android/speech/tts/Markup.java
@@ -0,0 +1,537 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that provides markup to a synthesis request to control aspects of speech.
+ * <p>
+ * Markup itself is a feature agnostic data format; the {@link Utterance} class defines the currently
+ * available set of features and should be used to construct instances of the Markup class.
+ * </p>
+ * <p>
+ * A marked up sentence is a tree. Each node has a type, an optional plain text, a set of
+ * parameters, and a list of children.
+ * The <b>type</b> defines what it contains, e.g. "text", "date", "measure", etc. A Markup node
+ * can be either a part of sentence (often a leaf node), or node altering some property of its
+ * children (node with children). The top level node has to be of type "utterance" and its children
+ * are synthesized in order.
+ * The <b>plain text</b> is optional except for the top level node. If the synthesis engine does not
+ * support Markup at all, it should use the plain text of the top level node. If an engine does not
+ * recognize or support a node type, it will try to use the plain text of that node if provided. If
+ * the plain text is null, it will synthesize its children in order.
+ * <b>Parameters</b> are key-value pairs specific to each node type. In case of a date node the
+ * parameters may be for example "month: 7" and "day: 10".
+ * The <b>nested markups</b> are children and can for example be used to nest semiotic classes (a
+ * measure may have a node of type "decimal" as its child) or to modify some property of its
+ * children. See "plain text" on how they are processed if the parent of the children is unknown to
+ * the engine.
+ * <p>
+ */
+public final class Markup implements Parcelable {
+
+ private String mType;
+ private String mPlainText;
+
+ private Bundle mParameters = new Bundle();
+ private List<Markup> mNestedMarkups = new ArrayList<Markup>();
+
+ private static final String TYPE = "type";
+ private static final String PLAIN_TEXT = "plain_text";
+ private static final String MARKUP = "markup";
+
+ private static final String IDENTIFIER_REGEX = "([0-9a-z_]+)";
+ private static final Pattern legalIdentifierPattern = Pattern.compile(IDENTIFIER_REGEX);
+
+ /**
+ * Constructs an empty markup.
+ */
+ public Markup() {}
+
+ /**
+ * Constructs a markup of the given type.
+ */
+ public Markup(String type) {
+ setType(type);
+ }
+
+ /**
+ * Returns the type of this node; can be null.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Sets the type of this node. can be null. May only contain [0-9a-z_].
+ */
+ public void setType(String type) {
+ if (type != null) {
+ Matcher matcher = legalIdentifierPattern.matcher(type);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Type cannot be empty and may only contain " +
+ "0-9, a-z and underscores.");
+ }
+ }
+ mType = type;
+ }
+
+ /**
+ * Returns this node's plain text; can be null.
+ */
+ public String getPlainText() {
+ return mPlainText;
+ }
+
+ /**
+ * Sets this nodes's plain text; can be null.
+ */
+ public void setPlainText(String plainText) {
+ mPlainText = plainText;
+ }
+
+ /**
+ * Adds or modifies a parameter.
+ * @param key The key; may only contain [0-9a-z_] and cannot be "type" or "plain_text".
+ * @param value The value.
+ * @throws An {@link IllegalArgumentException} if the key is null or empty.
+ * @return this
+ */
+ public Markup setParameter(String key, String value) {
+ if (key == null || key.isEmpty()) {
+ throw new IllegalArgumentException("Key cannot be null or empty.");
+ }
+ if (key.equals("type")) {
+ throw new IllegalArgumentException("Key cannot be \"type\".");
+ }
+ if (key.equals("plain_text")) {
+ throw new IllegalArgumentException("Key cannot be \"plain_text\".");
+ }
+ Matcher matcher = legalIdentifierPattern.matcher(key);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Key may only contain 0-9, a-z and underscores.");
+ }
+
+ if (value != null) {
+ mParameters.putString(key, value);
+ } else {
+ removeParameter(key);
+ }
+ return this;
+ }
+
+ /**
+ * Removes the parameter with the given key
+ */
+ public void removeParameter(String key) {
+ mParameters.remove(key);
+ }
+
+ /**
+ * Returns the value of the parameter.
+ * @param key The parameter key.
+ * @return The value of the parameter or null if the parameter is not set.
+ */
+ public String getParameter(String key) {
+ return mParameters.getString(key);
+ }
+
+ /**
+ * Returns the number of parameters that have been set.
+ */
+ public int parametersSize() {
+ return mParameters.size();
+ }
+
+ /**
+ * Appends a child to the list of children
+ * @param markup The child.
+ * @return This instance.
+ * @throws {@link IllegalArgumentException} if markup is null.
+ */
+ public Markup addNestedMarkup(Markup markup) {
+ if (markup == null) {
+ throw new IllegalArgumentException("Nested markup cannot be null");
+ }
+ mNestedMarkups.add(markup);
+ return this;
+ }
+
+ /**
+ * Removes the given node from its children.
+ * @param markup The child to remove.
+ * @return True if this instance was modified by this operation, false otherwise.
+ */
+ public boolean removeNestedMarkup(Markup markup) {
+ return mNestedMarkups.remove(markup);
+ }
+
+ /**
+ * Returns the index'th child.
+ * @param i The index of the child.
+ * @return The child.
+ * @throws {@link IndexOutOfBoundsException} if i < 0 or i >= nestedMarkupSize()
+ */
+ public Markup getNestedMarkup(int i) {
+ return mNestedMarkups.get(i);
+ }
+
+
+ /**
+ * Returns the number of children.
+ */
+ public int nestedMarkupSize() {
+ return mNestedMarkups.size();
+ }
+
+ /**
+ * Returns a string representation of this Markup instance. Can be deserialized back to a Markup
+ * instance with markupFromString().
+ */
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ if (mType != null) {
+ out.append(TYPE + ": \"" + mType + "\"");
+ }
+ if (mPlainText != null) {
+ out.append(out.length() > 0 ? " " : "");
+ out.append(PLAIN_TEXT + ": \"" + escapeQuotedString(mPlainText) + "\"");
+ }
+ // Sort the parameters alphabetically by key so we have a stable output.
+ SortedMap<String, String> sortedMap = new TreeMap<String, String>();
+ for (String key : mParameters.keySet()) {
+ sortedMap.put(key, mParameters.getString(key));
+ }
+ for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
+ out.append(out.length() > 0 ? " " : "");
+ out.append(entry.getKey() + ": \"" + escapeQuotedString(entry.getValue()) + "\"");
+ }
+ for (Markup m : mNestedMarkups) {
+ out.append(out.length() > 0 ? " " : "");
+ String nestedStr = m.toString();
+ if (nestedStr.isEmpty()) {
+ out.append(MARKUP + " {}");
+ } else {
+ out.append(MARKUP + " { " + m.toString() + " }");
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Escapes backslashes and double quotes in the plain text and parameter values before this
+ * instance is written to a string.
+ * @param str The string to escape.
+ * @return The escaped string.
+ */
+ private static String escapeQuotedString(String str) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '"') {
+ out.append("\\\"");
+ } else if (str.charAt(i) == '\\') {
+ out.append("\\\\");
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * The reverse of the escape method, returning plain text and parameter values to their original
+ * form.
+ * @param str An escaped string.
+ * @return The unescaped string.
+ */
+ private static String unescapeQuotedString(String str) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '\\') {
+ i++;
+ if (i >= str.length()) {
+ throw new IllegalArgumentException("Unterminated escape sequence in string: " +
+ str);
+ }
+ c = str.charAt(i);
+ if (c == '\\') {
+ out.append("\\");
+ } else if (c == '"') {
+ out.append("\"");
+ } else {
+ throw new IllegalArgumentException("Unsupported escape sequence: \\" + c +
+ " in string " + str);
+ }
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Returns true if the given string consists only of whitespace.
+ * @param str The string to check.
+ * @return True if the given string consists only of whitespace.
+ */
+ private static boolean isWhitespace(String str) {
+ return Pattern.matches("\\s*", str);
+ }
+
+ /**
+ * Parses the given string, and overrides the values of this instance with those contained
+ * in the given string.
+ * @param str The string to parse; can have superfluous whitespace.
+ * @return An empty string on success, else the remainder of the string that could not be
+ * parsed.
+ */
+ private String fromReadableString(String str) {
+ while (!isWhitespace(str)) {
+ String newStr = matchValue(str);
+ if (newStr == null) {
+ newStr = matchMarkup(str);
+
+ if (newStr == null) {
+ return str;
+ }
+ }
+ str = newStr;
+ }
+ return "";
+ }
+
+ // Matches: key : "value"
+ // where key is an identifier and value can contain escaped quotes
+ // there may be superflouous whitespace
+ // The value string may contain quotes and backslashes.
+ private static final String OPTIONAL_WHITESPACE = "\\s*";
+ private static final String VALUE_REGEX = "((\\\\.|[^\\\"])*)";
+ private static final String KEY_VALUE_REGEX =
+ "\\A" + OPTIONAL_WHITESPACE + // start of string
+ IDENTIFIER_REGEX + OPTIONAL_WHITESPACE + ":" + OPTIONAL_WHITESPACE + // key:
+ "\"" + VALUE_REGEX + "\""; // "value"
+ private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(KEY_VALUE_REGEX);
+
+ /**
+ * Tries to match a key-value pair at the start of the string. If found, add that as a parameter
+ * of this instance.
+ * @param str The string to parse.
+ * @return The remainder of the string without the parsed key-value pair on success, else null.
+ */
+ private String matchValue(String str) {
+ // Matches: key: "value"
+ Matcher matcher = KEY_VALUE_PATTERN.matcher(str);
+ if (!matcher.find()) {
+ return null;
+ }
+ String key = matcher.group(1);
+ String value = matcher.group(2);
+
+ if (key == null || value == null) {
+ return null;
+ }
+ String unescapedValue = unescapeQuotedString(value);
+ if (key.equals(TYPE)) {
+ this.mType = unescapedValue;
+ } else if (key.equals(PLAIN_TEXT)) {
+ this.mPlainText = unescapedValue;
+ } else {
+ setParameter(key, unescapedValue);
+ }
+
+ return str.substring(matcher.group(0).length());
+ }
+
+ // matches 'markup {'
+ private static final Pattern OPEN_MARKUP_PATTERN =
+ Pattern.compile("\\A" + OPTIONAL_WHITESPACE + MARKUP + OPTIONAL_WHITESPACE + "\\{");
+ // matches '}'
+ private static final Pattern CLOSE_MARKUP_PATTERN =
+ Pattern.compile("\\A" + OPTIONAL_WHITESPACE + "\\}");
+
+ /**
+ * Tries to parse a Markup specification from the start of the string. If so, add that markup to
+ * the list of nested Markup's of this instance.
+ * @param str The string to parse.
+ * @return The remainder of the string without the parsed Markup on success, else null.
+ */
+ private String matchMarkup(String str) {
+ // find and strip "markup {"
+ Matcher matcher = OPEN_MARKUP_PATTERN.matcher(str);
+
+ if (!matcher.find()) {
+ return null;
+ }
+ String strRemainder = str.substring(matcher.group(0).length());
+ // parse and strip markup contents
+ Markup nestedMarkup = new Markup();
+ strRemainder = nestedMarkup.fromReadableString(strRemainder);
+
+ // find and strip "}"
+ Matcher matcherClose = CLOSE_MARKUP_PATTERN.matcher(strRemainder);
+ if (!matcherClose.find()) {
+ return null;
+ }
+ strRemainder = strRemainder.substring(matcherClose.group(0).length());
+
+ // Everything parsed, add markup
+ this.addNestedMarkup(nestedMarkup);
+
+ // Return remainder
+ return strRemainder;
+ }
+
+ /**
+ * Returns a Markup instance from the string representation generated by toString().
+ * @param string The string representation generated by toString().
+ * @return The new Markup instance.
+ * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+ */
+ public static Markup markupFromString(String string) throws IllegalArgumentException {
+ Markup m = new Markup();
+ if (m.fromReadableString(string).isEmpty()) {
+ return m;
+ } else {
+ throw new IllegalArgumentException("Cannot parse input to Markup");
+ }
+ }
+
+ /**
+ * Compares the specified object with this Markup for equality.
+ * @return True if the given object is a Markup instance with the same type, plain text,
+ * parameters and the nested markups are also equal to each other and in the same order.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) return true;
+ if ( !(o instanceof Markup) ) return false;
+ Markup m = (Markup) o;
+
+ if (nestedMarkupSize() != this.nestedMarkupSize()) {
+ return false;
+ }
+
+ if (!(mType == null ? m.mType == null : mType.equals(m.mType))) {
+ return false;
+ }
+ if (!(mPlainText == null ? m.mPlainText == null : mPlainText.equals(m.mPlainText))) {
+ return false;
+ }
+ if (!equalBundles(mParameters, m.mParameters)) {
+ return false;
+ }
+
+ for (int i = 0; i < this.nestedMarkupSize(); i++) {
+ if (!mNestedMarkups.get(i).equals(m.mNestedMarkups.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if two bundles are equal to each other. Used by equals(o).
+ */
+ private boolean equalBundles(Bundle one, Bundle two) {
+ if (one == null || two == null) {
+ return false;
+ }
+
+ if(one.size() != two.size()) {
+ return false;
+ }
+
+ Set<String> valuesOne = one.keySet();
+ for(String key : valuesOne) {
+ Object valueOne = one.get(key);
+ Object valueTwo = two.get(key);
+ if (valueOne instanceof Bundle && valueTwo instanceof Bundle &&
+ !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
+ return false;
+ } else if (valueOne == null) {
+ if (valueTwo != null || !two.containsKey(key)) {
+ return false;
+ }
+ } else if(!valueOne.equals(valueTwo)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns an unmodifiable list of the children.
+ * @return An unmodifiable list of children that throws an {@link UnsupportedOperationException}
+ * if an attempt is made to modify it
+ */
+ public List<Markup> getNestedMarkups() {
+ return Collections.unmodifiableList(mNestedMarkups);
+ }
+
+ /**
+ * @hide
+ */
+ public Markup(Parcel in) {
+ mType = in.readString();
+ mPlainText = in.readString();
+ mParameters = in.readBundle();
+ in.readList(mNestedMarkups, Markup.class.getClassLoader());
+ }
+
+ /**
+ * Creates a deep copy of the given markup.
+ */
+ public Markup(Markup markup) {
+ mType = markup.mType;
+ mPlainText = markup.mPlainText;
+ mParameters = markup.mParameters;
+ for (Markup nested : markup.getNestedMarkups()) {
+ addNestedMarkup(new Markup(nested));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeString(mPlainText);
+ dest.writeBundle(mParameters);
+ dest.writeList(mNestedMarkups);
+ }
+
+ /**
+ * @hide
+ */
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Markup createFromParcel(Parcel in) {
+ return new Markup(in);
+ }
+
+ public Markup[] newArray(int size) {
+ return new Markup[size];
+ }
+ };
+}
+
diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java
index a1da49c..130e3f9 100644
--- a/core/java/android/speech/tts/SynthesisRequestV2.java
+++ b/core/java/android/speech/tts/SynthesisRequestV2.java
@@ -4,11 +4,12 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.speech.tts.TextToSpeechClient.UtteranceId;
+import android.util.Log;
/**
* Service-side representation of a synthesis request from a V2 API client. Contains:
* <ul>
- * <li>The utterance to synthesize</li>
+ * <li>The markup object to synthesize containing the utterance.</li>
* <li>The id of the utterance (String, result of {@link UtteranceId#toUniqueString()}</li>
* <li>The synthesis voice name (String, result of {@link VoiceInfo#getName()})</li>
* <li>Voice parameters (Bundle of parameters)</li>
@@ -16,8 +17,11 @@ import android.speech.tts.TextToSpeechClient.UtteranceId;
* </ul>
*/
public final class SynthesisRequestV2 implements Parcelable {
- /** Synthesis utterance. */
- private final String mText;
+
+ private static final String TAG = "SynthesisRequestV2";
+
+ /** Synthesis markup */
+ private final Markup mMarkup;
/** Synthesis id. */
private final String mUtteranceId;
@@ -34,9 +38,9 @@ public final class SynthesisRequestV2 implements Parcelable {
/**
* Constructor for test purposes.
*/
- public SynthesisRequestV2(String text, String utteranceId, String voiceName,
+ public SynthesisRequestV2(Markup markup, String utteranceId, String voiceName,
Bundle voiceParams, Bundle audioParams) {
- this.mText = text;
+ this.mMarkup = markup;
this.mUtteranceId = utteranceId;
this.mVoiceName = voiceName;
this.mVoiceParams = voiceParams;
@@ -49,15 +53,18 @@ public final class SynthesisRequestV2 implements Parcelable {
* @hide
*/
public SynthesisRequestV2(Parcel in) {
- this.mText = in.readString();
+ this.mMarkup = (Markup) in.readValue(Markup.class.getClassLoader());
this.mUtteranceId = in.readString();
this.mVoiceName = in.readString();
this.mVoiceParams = in.readBundle();
this.mAudioParams = in.readBundle();
}
- SynthesisRequestV2(String text, String utteranceId, RequestConfig rconfig) {
- this.mText = text;
+ /**
+ * Constructor to request the synthesis of a sentence.
+ */
+ SynthesisRequestV2(Markup markup, String utteranceId, RequestConfig rconfig) {
+ this.mMarkup = markup;
this.mUtteranceId = utteranceId;
this.mVoiceName = rconfig.getVoice().getName();
this.mVoiceParams = rconfig.getVoiceParams();
@@ -71,7 +78,7 @@ public final class SynthesisRequestV2 implements Parcelable {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mText);
+ dest.writeValue(mMarkup);
dest.writeString(mUtteranceId);
dest.writeString(mVoiceName);
dest.writeBundle(mVoiceParams);
@@ -82,7 +89,18 @@ public final class SynthesisRequestV2 implements Parcelable {
* @return the text which should be synthesized.
*/
public String getText() {
- return mText;
+ if (mMarkup.getPlainText() == null) {
+ Log.e(TAG, "Plaintext of markup is null.");
+ return "";
+ }
+ return mMarkup.getPlainText();
+ }
+
+ /**
+ * @return the markup which should be synthesized.
+ */
+ public Markup getMarkup() {
+ return mMarkup;
}
/**
diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java
index 85f702b..0c0be83 100644
--- a/core/java/android/speech/tts/TextToSpeechClient.java
+++ b/core/java/android/speech/tts/TextToSpeechClient.java
@@ -512,7 +512,6 @@ public class TextToSpeechClient {
}
}
-
/**
* Connects the client to TTS service. This method returns immediately, and connects to the
* service in the background.
@@ -794,15 +793,14 @@ public class TextToSpeechClient {
return mService != null && mEstablished;
}
- boolean runAction(Action action) {
+ <T> ActionResult<T> runAction(Action<T> action) {
synchronized (mLock) {
try {
- action.run(mService);
- return true;
+ return new ActionResult<T>(true, action.run(mService));
} catch (Exception ex) {
Log.e(TAG, action.getName() + " failed", ex);
disconnect();
- return false;
+ return new ActionResult<T>(false);
}
}
}
@@ -822,7 +820,7 @@ public class TextToSpeechClient {
}
}
- private abstract class Action {
+ private abstract class Action<T> {
private final String mName;
public Action(String name) {
@@ -830,7 +828,21 @@ public class TextToSpeechClient {
}
public String getName() {return mName;}
- abstract void run(ITextToSpeechService service) throws RemoteException;
+ abstract T run(ITextToSpeechService service) throws RemoteException;
+ }
+
+ private class ActionResult<T> {
+ boolean mSuccess;
+ T mResult;
+
+ ActionResult(boolean success) {
+ mSuccess = success;
+ }
+
+ ActionResult(boolean success, T result) {
+ mSuccess = success;
+ mResult = result;
+ }
}
private IBinder getCallerIdentity() {
@@ -840,18 +852,17 @@ public class TextToSpeechClient {
return null;
}
- private boolean runAction(Action action) {
+ private <T> ActionResult<T> runAction(Action<T> action) {
synchronized (mLock) {
if (mServiceConnection == null) {
Log.w(TAG, action.getName() + " failed: not bound to TTS engine");
- return false;
+ return new ActionResult<T>(false);
}
if (!mServiceConnection.isEstablished()) {
Log.w(TAG, action.getName() + " failed: not fully bound to TTS engine");
- return false;
+ return new ActionResult<T>(false);
}
- mServiceConnection.runAction(action);
- return true;
+ return mServiceConnection.runAction(action);
}
}
@@ -862,13 +873,14 @@ public class TextToSpeechClient {
* other utterances in the queue.
*/
public void stop() {
- runAction(new Action(ACTION_STOP_NAME) {
+ runAction(new Action<Void>(ACTION_STOP_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
if (service.stop(getCallerIdentity()) != Status.SUCCESS) {
Log.e(TAG, "Stop failed");
}
mCallbacks.clear();
+ return null;
}
});
}
@@ -876,7 +888,7 @@ public class TextToSpeechClient {
private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak";
/**
- * Speaks the string using the specified queuing strategy using current
+ * Speaks the string using the specified queuing strategy and the current
* voice. 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.
@@ -887,15 +899,38 @@ public class TextToSpeechClient {
* in {@link RequestCallbacks}.
* @param config Synthesis request configuration. Can't be null. Has to contain a
* voice.
- * @param callbacks Synthesis request callbacks. If null, default request
+ * @param callbacks Synthesis request callbacks. If null, the default request
* callbacks object will be used.
*/
public void queueSpeak(final String utterance, final UtteranceId utteranceId,
final RequestConfig config,
final RequestCallbacks callbacks) {
- runAction(new Action(ACTION_QUEUE_SPEAK_NAME) {
+ queueSpeak(createMarkupFromString(utterance), utteranceId, config, callbacks);
+ }
+
+ /**
+ * Speaks the {@link Markup} (which can be constructed with {@link Utterance}) using
+ * the specified queuing strategy and the current voice. 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.
+ *
+ * @param markup The Markup to be spoken. The written equivalent of the spoken
+ * text should be no longer than 1000 characters.
+ * @param utteranceId Unique identificator used to track the synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param config Synthesis request configuration. Can't be null. Has to contain a
+ * voice.
+ * @param callbacks Synthesis request callbacks. If null, the default request
+ * callbacks object will be used.
+ */
+ public void queueSpeak(final Markup markup,
+ final UtteranceId utteranceId,
+ final RequestConfig config,
+ final RequestCallbacks callbacks) {
+ runAction(new Action<Void>(ACTION_QUEUE_SPEAK_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -903,15 +938,16 @@ public class TextToSpeechClient {
int addCallbackStatus = addCallback(utteranceId, c);
if (addCallbackStatus != Status.SUCCESS) {
c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
- return;
+ return null;
}
int queueResult = service.speakV2(
getCallerIdentity(),
- new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config));
+ new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
}
+ return null;
}
});
}
@@ -931,15 +967,40 @@ public class TextToSpeechClient {
* @param outputFile File to write the generated audio data to.
* @param config Synthesis request configuration. Can't be null. Have to contain a
* voice.
- * @param callbacks Synthesis request callbacks. If null, default request
+ * @param callbacks Synthesis request callbacks. If null, the default request
* callbacks object will be used.
*/
public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId,
final File outputFile, final RequestConfig config,
final RequestCallbacks callbacks) {
- runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
+ queueSynthesizeToFile(createMarkupFromString(utterance), utteranceId, outputFile, config, callbacks);
+ }
+
+ /**
+ * Synthesizes the given {@link Markup} (can be constructed with {@link Utterance})
+ * 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.
+ *
+ * @param markup The Markup that should be synthesized. The written equivalent of
+ * the spoken text should be no longer than 1000 characters.
+ * @param utteranceId Unique identificator used to track the synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param outputFile File to write the generated audio data to.
+ * @param config Synthesis request configuration. Can't be null. Have to contain a
+ * voice.
+ * @param callbacks Synthesis request callbacks. If null, the default request
+ * callbacks object will be used.
+ */
+ public void queueSynthesizeToFile(
+ final Markup markup,
+ final UtteranceId utteranceId,
+ final File outputFile, final RequestConfig config,
+ final RequestCallbacks callbacks) {
+ runAction(new Action<Void>(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -947,7 +1008,7 @@ public class TextToSpeechClient {
int addCallbackStatus = addCallback(utteranceId, c);
if (addCallbackStatus != Status.SUCCESS) {
c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
- return;
+ return null;
}
ParcelFileDescriptor fileDescriptor = null;
@@ -955,7 +1016,7 @@ public class TextToSpeechClient {
if (outputFile.exists() && !outputFile.canWrite()) {
Log.e(TAG, "No permissions to write to " + outputFile);
removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
- return;
+ return null;
}
fileDescriptor = ParcelFileDescriptor.open(outputFile,
ParcelFileDescriptor.MODE_WRITE_ONLY |
@@ -964,8 +1025,7 @@ public class TextToSpeechClient {
int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(),
fileDescriptor,
- new SynthesisRequestV2(utterance, utteranceId.toUniqueString(),
- config));
+ new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
fileDescriptor.close();
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
@@ -977,10 +1037,18 @@ public class TextToSpeechClient {
Log.e(TAG, "Closing file " + outputFile + " failed", e);
removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
}
+ return null;
}
});
}
+ private static Markup createMarkupFromString(String str) {
+ return new Utterance()
+ .append(new Utterance.TtsText(str))
+ .setNoWarningOnFallback(true)
+ .createMarkup();
+ }
+
private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence";
/**
@@ -997,9 +1065,9 @@ public class TextToSpeechClient {
*/
public void queueSilence(final long durationInMs, final UtteranceId utteranceId,
final RequestCallbacks callbacks) {
- runAction(new Action(ACTION_QUEUE_SILENCE_NAME) {
+ runAction(new Action<Void>(ACTION_QUEUE_SILENCE_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -1015,6 +1083,7 @@ public class TextToSpeechClient {
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
}
+ return null;
}
});
}
@@ -1038,9 +1107,9 @@ public class TextToSpeechClient {
*/
public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId,
final RequestConfig config, final RequestCallbacks callbacks) {
- runAction(new Action(ACTION_QUEUE_AUDIO_NAME) {
+ runAction(new Action<Void>(ACTION_QUEUE_AUDIO_NAME) {
@Override
- public void run(ITextToSpeechService service) throws RemoteException {
+ public Void run(ITextToSpeechService service) throws RemoteException {
RequestCallbacks c = mDefaultRequestCallbacks;
if (callbacks != null) {
c = callbacks;
@@ -1056,10 +1125,35 @@ public class TextToSpeechClient {
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
}
+ return null;
}
});
}
+ private static final String ACTION_IS_SPEAKING_NAME = "isSpeaking";
+
+ /**
+ * Checks whether the TTS engine is busy speaking. Note that a speech item is
+ * considered complete once it's audio data has been sent to the audio mixer, or
+ * written to a file. There might be a finite lag between this point, and when
+ * the audio hardware completes playback.
+ *
+ * @return {@code true} if the TTS engine is speaking.
+ */
+ public boolean isSpeaking() {
+ ActionResult<Boolean> result = runAction(new Action<Boolean>(ACTION_IS_SPEAKING_NAME) {
+ @Override
+ public Boolean run(ITextToSpeechService service) throws RemoteException {
+ return service.isSpeaking();
+ }
+ });
+ if (!result.mSuccess) {
+ return false; // We can't really say, return false
+ }
+ return result.mResult;
+ }
+
+
class InternalHandler extends Handler {
final static int WHAT_ENGINE_STATUS_CHANGED = 1;
final static int WHAT_SERVICE_DISCONNECTED = 2;
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 6b899d9..14a4024 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -352,6 +352,12 @@ public abstract class TextToSpeechService extends Service {
params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true");
}
+ String noWarning = request.getMarkup().getParameter(Utterance.KEY_NO_WARNING_ON_FALLBACK);
+ if (noWarning == null || noWarning.equals("false")) {
+ Log.w("TextToSpeechService", "The synthesis engine does not support Markup, falling " +
+ "back to the given plain text.");
+ }
+
// Build V1 request
SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params);
Locale locale = selectedVoice.getLocale();
@@ -856,14 +862,53 @@ public abstract class TextToSpeechService extends Service {
}
}
+ /**
+ * Estimate of the character count equivalent of a Markup instance. Calculated
+ * by summing the characters of all Markups of type "text". Each other node
+ * is counted as a single character, as the character count of other nodes
+ * is non-trivial to calculate and we don't want to accept arbitrarily large
+ * requests.
+ */
+ private int estimateSynthesisLengthFromMarkup(Markup m) {
+ int size = 0;
+ if (m.getType() != null &&
+ m.getType().equals("text") &&
+ m.getParameter("text") != null) {
+ size += m.getParameter("text").length();
+ } else if (m.getType() == null ||
+ !m.getType().equals("utterance")) {
+ size += 1;
+ }
+ for (Markup nested : m.getNestedMarkups()) {
+ size += estimateSynthesisLengthFromMarkup(nested);
+ }
+ return size;
+ }
+
@Override
public boolean isValid() {
- if (mSynthesisRequest.getText() == null) {
- Log.e(TAG, "null synthesis text");
+ if (mSynthesisRequest.getMarkup() == null) {
+ Log.e(TAG, "No markup in request.");
return false;
}
- if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) {
- Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars");
+ String type = mSynthesisRequest.getMarkup().getType();
+ if (type == null) {
+ Log.w(TAG, "Top level markup node should have type \"utterance\", not null");
+ return false;
+ } else if (!type.equals("utterance")) {
+ Log.w(TAG, "Top level markup node should have type \"utterance\" instead of " +
+ "\"" + type + "\"");
+ return false;
+ }
+
+ int estimate = estimateSynthesisLengthFromMarkup(mSynthesisRequest.getMarkup());
+ if (estimate >= TextToSpeech.getMaxSpeechInputLength()) {
+ Log.w(TAG, "Text too long: estimated size of text was " + estimate + " chars.");
+ return false;
+ }
+
+ if (estimate <= 0) {
+ Log.e(TAG, "null synthesis text");
return false;
}
diff --git a/core/java/android/speech/tts/Utterance.java b/core/java/android/speech/tts/Utterance.java
new file mode 100644
index 0000000..0a29283
--- /dev/null
+++ b/core/java/android/speech/tts/Utterance.java
@@ -0,0 +1,595 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class acts as a builder for {@link Markup} instances.
+ * <p>
+ * Each Utterance consists of a list of the semiotic classes ({@link Utterance.TtsCardinal} and
+ * {@link Utterance.TtsText}).
+ * <p>Each semiotic class can be supplied with morphosyntactic features
+ * (gender, animacy, multiplicity and case), it is up to the synthesis engine to use this
+ * information during synthesis.
+ * Examples where morphosyntactic features matter:
+ * <ul>
+ * <li>In French, the number one is verbalized differently based on the gender of the noun
+ * it modifies. "un homme" (one man) versus "une femme" (one woman).
+ * <li>In German the grammatical case (accusative, locative, etc) needs to be included to be
+ * verbalize correctly. In German you'd have the sentence "Sie haben 1 kilometer vor Ihnen" (You
+ * have 1 kilometer ahead of you), "1" in this case needs to become inflected to the accusative
+ * form ("einen") instead of the nominative form "ein".
+ * </p>
+ * <p>
+ * Utterance usage example:
+ * Markup m1 = new Utterance().append("The Eiffel Tower is")
+ * .append(new TtsCardinal(324))
+ * .append("meters tall.");
+ * Markup m2 = new Utterance().append("Sie haben")
+ * .append(new TtsCardinal(1).setGender(Utterance.GENDER_MALE)
+ * .append("Tag frei.");
+ * </p>
+ */
+public class Utterance {
+
+ /***
+ * Toplevel type of markup representation.
+ */
+ public static final String TYPE_UTTERANCE = "utterance";
+ /***
+ * The no_warning_on_fallback parameter can be set to "false" or "true", true indicating that
+ * no warning will be given when the synthesizer does not support Markup. This is used when
+ * the user only provides a string to the API instead of a markup.
+ */
+ public static final String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback";
+
+ // Gender.
+ public final static int GENDER_UNKNOWN = 0;
+ public final static int GENDER_NEUTRAL = 1;
+ public final static int GENDER_MALE = 2;
+ public final static int GENDER_FEMALE = 3;
+
+ // Animacy.
+ public final static int ANIMACY_UNKNOWN = 0;
+ public final static int ANIMACY_ANIMATE = 1;
+ public final static int ANIMACY_INANIMATE = 2;
+
+ // Multiplicity.
+ public final static int MULTIPLICITY_UNKNOWN = 0;
+ public final static int MULTIPLICITY_SINGLE = 1;
+ public final static int MULTIPLICITY_DUAL = 2;
+ public final static int MULTIPLICITY_PLURAL = 3;
+
+ // Case.
+ public final static int CASE_UNKNOWN = 0;
+ public final static int CASE_NOMINATIVE = 1;
+ public final static int CASE_ACCUSATIVE = 2;
+ public final static int CASE_DATIVE = 3;
+ public final static int CASE_ABLATIVE = 4;
+ public final static int CASE_GENITIVE = 5;
+ public final static int CASE_VOCATIVE = 6;
+ public final static int CASE_LOCATIVE = 7;
+ public final static int CASE_INSTRUMENTAL = 8;
+
+ private List<AbstractTts<? extends AbstractTts<?>>> says =
+ new ArrayList<AbstractTts<? extends AbstractTts<?>>>();
+ Boolean mNoWarningOnFallback = null;
+
+ /**
+ * Objects deriving from this class can be appended to a Utterance. This class uses generics
+ * so method from this class can return instances of its child classes, resulting in a better
+ * API (CRTP pattern).
+ */
+ public static abstract class AbstractTts<C extends AbstractTts<C>> {
+
+ protected Markup mMarkup = new Markup();
+
+ /**
+ * Empty constructor.
+ */
+ protected AbstractTts() {
+ }
+
+ /**
+ * Construct with Markup.
+ * @param markup
+ */
+ protected AbstractTts(Markup markup) {
+ mMarkup = markup;
+ }
+
+ /**
+ * Returns the type of this class, e.g. "cardinal" or "measure".
+ * @return The type.
+ */
+ public String getType() {
+ return mMarkup.getType();
+ }
+
+ /**
+ * A fallback plain text can be provided, in case the engine does not support this class
+ * type, or even Markup altogether.
+ * @param plainText A string with the plain text.
+ * @return This instance.
+ */
+ @SuppressWarnings("unchecked")
+ public C setPlainText(String plainText) {
+ mMarkup.setPlainText(plainText);
+ return (C) this;
+ }
+
+ /**
+ * Returns the plain text (fallback) string.
+ * @return Plain text string or null if not set.
+ */
+ public String getPlainText() {
+ return mMarkup.getPlainText();
+ }
+
+ /**
+ * Populates the plainText if not set and builds a Markup instance.
+ * @return The Markup object describing this instance.
+ */
+ public Markup getMarkup() {
+ return new Markup(mMarkup);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected C setParameter(String key, String value) {
+ mMarkup.setParameter(key, value);
+ return (C) this;
+ }
+
+ protected String getParameter(String key) {
+ return mMarkup.getParameter(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected C removeParameter(String key) {
+ mMarkup.removeParameter(key);
+ return (C) this;
+ }
+
+ /**
+ * Returns a string representation of this instance, can be deserialized to an equal
+ * Utterance instance.
+ */
+ public String toString() {
+ return mMarkup.toString();
+ }
+
+ /**
+ * Returns a generated plain text alternative for this instance if this instance isn't
+ * better representated by the list of it's children.
+ * @return Best effort plain text representation of this instance, can be null.
+ */
+ public String generatePlainText() {
+ return null;
+ }
+ }
+
+ public static abstract class AbstractTtsSemioticClass<C extends AbstractTtsSemioticClass<C>>
+ extends AbstractTts<C> {
+ // Keys.
+ private static final String KEY_GENDER = "gender";
+ private static final String KEY_ANIMACY = "animacy";
+ private static final String KEY_MULTIPLICITY = "multiplicity";
+ private static final String KEY_CASE = "case";
+
+ protected AbstractTtsSemioticClass() {
+ super();
+ }
+
+ protected AbstractTtsSemioticClass(Markup markup) {
+ super(markup);
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setGender(int gender) {
+ if (gender < 0 || gender > 3) {
+ throw new IllegalArgumentException("Only four types of gender can be set: " +
+ "unknown, neutral, maculine and female.");
+ }
+ if (gender != GENDER_UNKNOWN) {
+ setParameter(KEY_GENDER, String.valueOf(gender));
+ } else {
+ setParameter(KEY_GENDER, null);
+ }
+ return (C) this;
+ }
+
+ public int getGender() {
+ String gender = mMarkup.getParameter(KEY_GENDER);
+ return gender != null ? Integer.valueOf(gender) : GENDER_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setAnimacy(int animacy) {
+ if (animacy < 0 || animacy > 2) {
+ throw new IllegalArgumentException(
+ "Only two types of animacy can be set: unknown, animate and inanimate");
+ }
+ if (animacy != ANIMACY_UNKNOWN) {
+ setParameter(KEY_ANIMACY, String.valueOf(animacy));
+ } else {
+ setParameter(KEY_ANIMACY, null);
+ }
+ return (C) this;
+ }
+
+ public int getAnimacy() {
+ String animacy = getParameter(KEY_ANIMACY);
+ return animacy != null ? Integer.valueOf(animacy) : ANIMACY_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setMultiplicity(int multiplicity) {
+ if (multiplicity < 0 || multiplicity > 3) {
+ throw new IllegalArgumentException(
+ "Only four types of multiplicity can be set: unknown, single, dual and " +
+ "plural.");
+ }
+ if (multiplicity != MULTIPLICITY_UNKNOWN) {
+ setParameter(KEY_MULTIPLICITY, String.valueOf(multiplicity));
+ } else {
+ setParameter(KEY_MULTIPLICITY, null);
+ }
+ return (C) this;
+ }
+
+ public int getMultiplicity() {
+ String multiplicity = mMarkup.getParameter(KEY_MULTIPLICITY);
+ return multiplicity != null ? Integer.valueOf(multiplicity) : MULTIPLICITY_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setCase(int grammaticalCase) {
+ if (grammaticalCase < 0 || grammaticalCase > 8) {
+ throw new IllegalArgumentException(
+ "Only nine types of grammatical case can be set.");
+ }
+ if (grammaticalCase != CASE_UNKNOWN) {
+ setParameter(KEY_CASE, String.valueOf(grammaticalCase));
+ } else {
+ setParameter(KEY_CASE, null);
+ }
+ return (C) this;
+ }
+
+ public int getCase() {
+ String grammaticalCase = mMarkup.getParameter(KEY_CASE);
+ return grammaticalCase != null ? Integer.valueOf(grammaticalCase) : CASE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Class that contains regular text, synthesis engine pronounces it using its regular pipeline.
+ * Parameters:
+ * <ul>
+ * <li>Text: the text to synthesize</li>
+ * </ul>
+ */
+ public static class TtsText extends AbstractTtsSemioticClass<TtsText> {
+
+ // The type of this node.
+ protected static final String TYPE_TEXT = "text";
+ // The text parameter stores the text to be synthesized.
+ private static final String KEY_TEXT = "text";
+
+ /**
+ * Default constructor.
+ */
+ public TtsText() {
+ mMarkup.setType(TYPE_TEXT);
+ }
+
+ /**
+ * Constructor that sets the text to be synthesized.
+ * @param text The text to be synthesized.
+ */
+ public TtsText(String text) {
+ this();
+ setText(text);
+ }
+
+ /**
+ * Constructs a TtsText with the values of the Markup, does not check if the given Markup is
+ * of the right type.
+ */
+ private TtsText(Markup markup) {
+ super(markup);
+ }
+
+ /**
+ * Sets the text to be synthesized.
+ * @return This instance.
+ */
+ public TtsText setText(String text) {
+ setParameter(KEY_TEXT, text);
+ return this;
+ }
+
+ /**
+ * Returns the text to be synthesized.
+ * @return This instance.
+ */
+ public String getText() {
+ return getParameter(KEY_TEXT);
+ }
+
+ /**
+ * Generates a best effort plain text, in this case simply the text.
+ */
+ @Override
+ public String generatePlainText() {
+ return getText();
+ }
+ }
+
+ /**
+ * Contains a cardinal.
+ * Parameters:
+ * <ul>
+ * <li>integer: the integer to synthesize</li>
+ * </ul>
+ */
+ public static class TtsCardinal extends AbstractTtsSemioticClass<TtsCardinal> {
+
+ // The type of this node.
+ protected static final String TYPE_CARDINAL = "cardinal";
+ // The parameter integer stores the integer to synthesize.
+ private static final String KEY_INTEGER = "integer";
+
+ /**
+ * Default constructor.
+ */
+ public TtsCardinal() {
+ mMarkup.setType(TYPE_CARDINAL);
+ }
+
+ /**
+ * Constructor that sets the integer to be synthesized.
+ */
+ public TtsCardinal(int integer) {
+ this();
+ setInteger(integer);
+ }
+
+ /**
+ * Constructor that sets the integer to be synthesized.
+ */
+ public TtsCardinal(String integer) {
+ this();
+ setInteger(integer);
+ }
+
+ /**
+ * Constructs a TtsText with the values of the Markup.
+ * Does not check if the given Markup is of the right type.
+ */
+ private TtsCardinal(Markup markup) {
+ super(markup);
+ }
+
+ /**
+ * Sets the integer.
+ * @return This instance.
+ */
+ public TtsCardinal setInteger(int integer) {
+ return setInteger(String.valueOf(integer));
+ }
+
+ /**
+ * Sets the integer.
+ * @param integer A non-empty string of digits with an optional '-' in front.
+ * @return This instance.
+ */
+ public TtsCardinal setInteger(String integer) {
+ if (!integer.matches("-?\\d+")) {
+ throw new IllegalArgumentException("Expected a cardinal: \"" + integer + "\"");
+ }
+ setParameter(KEY_INTEGER, integer);
+ return this;
+ }
+
+ /**
+ * Returns the integer parameter.
+ */
+ public String getInteger() {
+ return getParameter(KEY_INTEGER);
+ }
+
+ /**
+ * Generates a best effort plain text, in this case simply the integer.
+ */
+ @Override
+ public String generatePlainText() {
+ return getInteger();
+ }
+ }
+
+ /**
+ * Default constructor.
+ */
+ public Utterance() {}
+
+ /**
+ * Returns the plain text of a given Markup if it was set; if it's not set, recursively call the
+ * this same method on its children.
+ */
+ private String constructPlainText(Markup m) {
+ StringBuilder plainText = new StringBuilder();
+ if (m.getPlainText() != null) {
+ plainText.append(m.getPlainText());
+ } else {
+ for (Markup nestedMarkup : m.getNestedMarkups()) {
+ String nestedPlainText = constructPlainText(nestedMarkup);
+ if (!nestedPlainText.isEmpty()) {
+ if (plainText.length() != 0) {
+ plainText.append(" ");
+ }
+ plainText.append(nestedPlainText);
+ }
+ }
+ }
+ return plainText.toString();
+ }
+
+ /**
+ * Creates a Markup instance with auto generated plain texts for the relevant nodes, in case the
+ * user has not provided one already.
+ * @return A Markup instance representing this utterance.
+ */
+ public Markup createMarkup() {
+ Markup markup = new Markup(TYPE_UTTERANCE);
+ StringBuilder plainText = new StringBuilder();
+ for (AbstractTts<? extends AbstractTts<?>> say : says) {
+ // Get a copy of this markup, and generate a plaintext for it if is not set.
+ Markup sayMarkup = say.getMarkup();
+ if (sayMarkup.getPlainText() == null) {
+ sayMarkup.setPlainText(say.generatePlainText());
+ }
+ if (plainText.length() != 0) {
+ plainText.append(" ");
+ }
+ plainText.append(constructPlainText(sayMarkup));
+ markup.addNestedMarkup(sayMarkup);
+ }
+ if (mNoWarningOnFallback != null) {
+ markup.setParameter(KEY_NO_WARNING_ON_FALLBACK,
+ mNoWarningOnFallback ? "true" : "false");
+ }
+ markup.setPlainText(plainText.toString());
+ return markup;
+ }
+
+ /**
+ * Appends an element to this Utterance instance.
+ * @return this instance
+ */
+ public Utterance append(AbstractTts<? extends AbstractTts<?>> say) {
+ says.add(say);
+ return this;
+ }
+
+ private Utterance append(Markup markup) {
+ if (markup.getType().equals(TtsText.TYPE_TEXT)) {
+ append(new TtsText(markup));
+ } else if (markup.getType().equals(TtsCardinal.TYPE_CARDINAL)) {
+ append(new TtsCardinal(markup));
+ } else {
+ // Unknown node, a class we don't know about.
+ if (markup.getPlainText() != null) {
+ append(new TtsText(markup.getPlainText()));
+ } else {
+ // No plainText specified; add its children
+ // seperately. In case of a new prosody node,
+ // we would still verbalize it correctly.
+ for (Markup nested : markup.getNestedMarkups()) {
+ append(nested);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns a string representation of this Utterance instance. Can be deserialized back to an
+ * Utterance instance with utteranceFromString(). Can be used to store utterances to be used
+ * at a later time.
+ */
+ public String toString() {
+ String out = "type: \"" + TYPE_UTTERANCE + "\"";
+ if (mNoWarningOnFallback != null) {
+ out += " no_warning_on_fallback: \"" + (mNoWarningOnFallback ? "true" : "false") + "\"";
+ }
+ for (AbstractTts<? extends AbstractTts<?>> say : says) {
+ out += " markup { " + say.getMarkup().toString() + " }";
+ }
+ return out;
+ }
+
+ /**
+ * Returns an Utterance instance from the string representation generated by toString().
+ * @param string The string representation generated by toString().
+ * @return The new Utterance instance.
+ * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+ */
+ static public Utterance utteranceFromString(String string) throws IllegalArgumentException {
+ Utterance utterance = new Utterance();
+ Markup markup = Markup.markupFromString(string);
+ if (!markup.getType().equals(TYPE_UTTERANCE)) {
+ throw new IllegalArgumentException("Top level markup should be of type \"" +
+ TYPE_UTTERANCE + "\", but was of type \"" +
+ markup.getType() + "\".") ;
+ }
+ for (Markup nestedMarkup : markup.getNestedMarkups()) {
+ utterance.append(nestedMarkup);
+ }
+ return utterance;
+ }
+
+ /**
+ * Appends a new TtsText with the given text.
+ * @param text The text to synthesize.
+ * @return This instance.
+ */
+ public Utterance append(String text) {
+ return append(new TtsText(text));
+ }
+
+ /**
+ * Appends a TtsCardinal representing the given number.
+ * @param integer The integer to synthesize.
+ * @return this
+ */
+ public Utterance append(int integer) {
+ return append(new TtsCardinal(integer));
+ }
+
+ /**
+ * Returns the n'th element in this Utterance.
+ * @param i The index.
+ * @return The n'th element in this Utterance.
+ * @throws {@link IndexOutOfBoundsException} - if i < 0 || i >= size()
+ */
+ public AbstractTts<? extends AbstractTts<?>> get(int i) {
+ return says.get(i);
+ }
+
+ /**
+ * Returns the number of elements in this Utterance.
+ * @return The number of elements in this Utterance.
+ */
+ public int size() {
+ return says.size();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) return true;
+ if ( !(o instanceof Utterance) ) return false;
+ Utterance utt = (Utterance) o;
+
+ if (says.size() != utt.says.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < says.size(); i++) {
+ if (!says.get(i).getMarkup().equals(utt.says.get(i).getMarkup())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Can be set to true or false, true indicating that the user provided only a string to the API,
+ * at which the system will not issue a warning if the synthesizer falls back onto the plain
+ * text when the synthesizer does not support Markup.
+ */
+ public Utterance setNoWarningOnFallback(boolean noWarning) {
+ mNoWarningOnFallback = noWarning;
+ return this;
+ }
+}
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
deleted file mode 100644
index ac83356..0000000
--- a/core/java/android/tv/ITvInputClient.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.content.ComponentName;
-import android.tv.ITvInputSession;
-import android.view.InputChannel;
-
-/**
- * Interface a client of the ITvInputManager implements, to identify itself and receive information
- * about changes to the state of each TV input service.
- * @hide
- */
-oneway interface ITvInputClient {
- void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
- void onAvailabilityChanged(in String inputId, boolean isAvailable);
- void onSessionReleased(int seq);
-}
diff --git a/core/java/android/tv/ITvInputHardware.aidl b/core/java/android/tv/ITvInputHardware.aidl
deleted file mode 100644
index 7250453..0000000
--- a/core/java/android/tv/ITvInputHardware.aidl
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.tv.TvStreamConfig;
-import android.view.KeyEvent;
-import android.view.Surface;
-
-/**
- * TvInputService representing a physical port should connect to HAL through this interface.
- * Framework will take care of communication among system services including TvInputManagerService,
- * HdmiControlService, AudioService, etc.
- *
- * @hide
- */
-interface ITvInputHardware {
- /**
- * Make the input render on the surface according to the config. In case of HDMI, this will
- * trigger CEC commands for adjusting active HDMI source. Returns true on success.
- */
- boolean setSurface(in Surface surface, in TvStreamConfig config);
- /**
- * Set volume for this stream via AudioGain. (TBD)
- */
- void setVolume(float volume);
-
- /**
- * Dispatch key event to HDMI service. The events would be automatically converted to
- * HDMI CEC commands. If the hardware is not representing an HDMI port, this method will fail.
- */
- boolean dispatchKeyEventToHdmi(in KeyEvent event);
-}
diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl
deleted file mode 100644
index c6f8d79..0000000
--- a/core/java/android/tv/ITvInputManager.aidl
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.content.ComponentName;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.tv.ITvInputHardware;
-import android.tv.ITvInputHardwareCallback;
-import android.tv.ITvInputClient;
-import android.tv.TvInputHardwareInfo;
-import android.tv.TvInputInfo;
-import android.view.Surface;
-
-/**
- * Interface to the TV input manager service.
- * @hide
- */
-interface ITvInputManager {
- List<TvInputInfo> getTvInputList(int userId);
-
- boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
-
- void registerCallback(in ITvInputClient client, in String inputId, int userId);
- void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
-
- void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
- void releaseSession(in IBinder sessionToken, int userId);
-
- void setSurface(in IBinder sessionToken, in Surface surface, int userId);
- void setVolume(in IBinder sessionToken, float volume, int userId);
- void tune(in IBinder sessionToken, in Uri channelUri, int userId);
-
- void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
- int userId);
- void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId);
- void removeOverlayView(in IBinder sessionToken, int userId);
-
- // For TV input hardware binding
- List<TvInputHardwareInfo> getHardwareList();
- ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
- int userId);
- void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
-}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
deleted file mode 100644
index 4f1bc2b..0000000
--- a/core/java/android/tv/ITvInputService.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.tv.ITvInputServiceCallback;
-import android.tv.ITvInputSessionCallback;
-import android.view.InputChannel;
-
-/**
- * Top-level interface to a TV input component (implemented in a Service).
- * @hide
- */
-oneway interface ITvInputService {
- void registerCallback(ITvInputServiceCallback callback);
- void unregisterCallback(in ITvInputServiceCallback callback);
- void createSession(in InputChannel channel, ITvInputSessionCallback callback);
-}
diff --git a/core/java/android/tv/ITvInputServiceCallback.aidl b/core/java/android/tv/ITvInputServiceCallback.aidl
deleted file mode 100644
index 71fc780..0000000
--- a/core/java/android/tv/ITvInputServiceCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.content.ComponentName;
-
-/**
- * Helper interface for ITvInputService to allow the TV input to notify the client when its status
- * has been changed.
- * @hide
- */
-oneway interface ITvInputServiceCallback {
- void onAvailabilityChanged(in String inputId, boolean isAvailable);
-}
diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl
deleted file mode 100644
index 32fee4b..0000000
--- a/core/java/android/tv/ITvInputSession.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.graphics.Rect;
-import android.net.Uri;
-import android.view.Surface;
-
-/**
- * Sub-interface of ITvInputService which is created per session and has its own context.
- * @hide
- */
-oneway interface ITvInputSession {
- void release();
-
- void setSurface(in Surface surface);
- // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan
- // is to introduce some new concepts that will solve a number of problems in audio policy today.
- void setVolume(float volume);
- void tune(in Uri channelUri);
-
- void createOverlayView(in IBinder windowToken, in Rect frame);
- void relayoutOverlayView(in Rect frame);
- void removeOverlayView();
-}
diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl
deleted file mode 100644
index a2bd0d7..0000000
--- a/core/java/android/tv/ITvInputSessionCallback.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.tv.ITvInputSession;
-
-/**
- * Helper interface for ITvInputSession to allow the TV input to notify the system service when a
- * new session has been created.
- * @hide
- */
-oneway interface ITvInputSessionCallback {
- void onSessionCreated(ITvInputSession session);
-}
diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
deleted file mode 100644
index 3ccccf3..0000000
--- a/core/java/android/tv/ITvInputSessionWrapper.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.tv.TvInputManager.Session;
-import android.tv.TvInputService.TvInputSessionImpl;
-import android.util.Log;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.Surface;
-
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.SomeArgs;
-
-/**
- * Implements the internal ITvInputSession interface to convert incoming calls on to it back to
- * calls on the public TvInputSession interface, scheduling them on the main thread of the process.
- *
- * @hide
- */
-public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback {
- private static final String TAG = "TvInputSessionWrapper";
-
- private static final int DO_RELEASE = 1;
- private static final int DO_SET_SURFACE = 2;
- private static final int DO_SET_VOLUME = 3;
- private static final int DO_TUNE = 4;
- private static final int DO_CREATE_OVERLAY_VIEW = 5;
- private static final int DO_RELAYOUT_OVERLAY_VIEW = 6;
- private static final int DO_REMOVE_OVERLAY_VIEW = 7;
-
- private final HandlerCaller mCaller;
-
- private TvInputSessionImpl mTvInputSessionImpl;
- private InputChannel mChannel;
- private TvInputEventReceiver mReceiver;
-
- public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl,
- InputChannel channel) {
- mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
- mTvInputSessionImpl = sessionImpl;
- mChannel = channel;
- if (channel != null) {
- mReceiver = new TvInputEventReceiver(channel, context.getMainLooper());
- }
- }
-
- @Override
- public void executeMessage(Message msg) {
- if (mTvInputSessionImpl == null) {
- return;
- }
-
- switch (msg.what) {
- case DO_RELEASE: {
- mTvInputSessionImpl.release();
- mTvInputSessionImpl = null;
- if (mReceiver != null) {
- mReceiver.dispose();
- mReceiver = null;
- }
- if (mChannel != null) {
- mChannel.dispose();
- mChannel = null;
- }
- return;
- }
- case DO_SET_SURFACE: {
- mTvInputSessionImpl.setSurface((Surface) msg.obj);
- return;
- }
- case DO_SET_VOLUME: {
- mTvInputSessionImpl.setVolume((Float) msg.obj);
- return;
- }
- case DO_TUNE: {
- mTvInputSessionImpl.tune((Uri) msg.obj);
- return;
- }
- case DO_CREATE_OVERLAY_VIEW: {
- SomeArgs args = (SomeArgs) msg.obj;
- mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
- args.recycle();
- return;
- }
- case DO_RELAYOUT_OVERLAY_VIEW: {
- mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj);
- return;
- }
- case DO_REMOVE_OVERLAY_VIEW: {
- mTvInputSessionImpl.removeOverlayView(true);
- return;
- }
- default: {
- Log.w(TAG, "Unhandled message code: " + msg.what);
- return;
- }
- }
- }
-
- @Override
- public void release() {
- mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
- }
-
- @Override
- public void setSurface(Surface surface) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
- }
-
- @Override
- public final void setVolume(float volume) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume));
- }
-
- @Override
- public void tune(Uri channelUri) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri));
- }
-
- @Override
- public void createOverlayView(IBinder windowToken, Rect frame) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
- frame));
- }
-
- @Override
- public void relayoutOverlayView(Rect frame) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
- }
-
- @Override
- public void removeOverlayView() {
- mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
- }
-
- private final class TvInputEventReceiver extends InputEventReceiver {
- public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
- super(inputChannel, looper);
- }
-
- @Override
- public void onInputEvent(InputEvent event) {
- if (mTvInputSessionImpl == null) {
- // The session has been finished.
- finishInputEvent(event, false);
- return;
- }
-
- int handled = mTvInputSessionImpl.dispatchInputEvent(event, this);
- if (handled != Session.DISPATCH_IN_PROGRESS) {
- finishInputEvent(event, handled == Session.DISPATCH_HANDLED);
- }
- }
- }
-}
diff --git a/core/java/android/tv/TvInputHardwareInfo.aidl b/core/java/android/tv/TvInputHardwareInfo.aidl
deleted file mode 100644
index 484ab60..0000000
--- a/core/java/android/tv/TvInputHardwareInfo.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 2014, 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.tv;
-
-parcelable TvInputHardwareInfo;
diff --git a/core/java/android/tv/TvInputHardwareInfo.java b/core/java/android/tv/TvInputHardwareInfo.java
deleted file mode 100644
index b0dc58e..0000000
--- a/core/java/android/tv/TvInputHardwareInfo.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-
-/**
- * Simple container for information about TV input hardware.
- * Not for third-party developers.
- *
- * @hide
- */
-public final class TvInputHardwareInfo implements Parcelable {
- static final String TAG = "TvInputHardwareInfo";
-
- // Match hardware/libhardware/include/hardware/tv_input.h
- public static final int TV_INPUT_TYPE_HDMI = 1;
- public static final int TV_INPUT_TYPE_BUILT_IN_TUNER = 2;
- public static final int TV_INPUT_TYPE_PASSTHROUGH = 3;
-
- public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR =
- new Parcelable.Creator<TvInputHardwareInfo>() {
- @Override
- public TvInputHardwareInfo createFromParcel(Parcel source) {
- try {
- TvInputHardwareInfo info = new TvInputHardwareInfo();
- info.readFromParcel(source);
- return info;
- } catch (Exception e) {
- Log.e(TAG, "Exception creating TvInputHardwareInfo from parcel", e);
- return null;
- }
- }
-
- @Override
- public TvInputHardwareInfo[] newArray(int size) {
- return new TvInputHardwareInfo[size];
- }
- };
-
- private int mDeviceId;
- private int mType;
- // TODO: Add audio port & audio address for audio service.
- // TODO: Add HDMI handle for HDMI service.
-
- public TvInputHardwareInfo() { }
-
- public TvInputHardwareInfo(int deviceId, int type) {
- mDeviceId = deviceId;
- mType = type;
- }
-
- public int getDeviceId() {
- return mDeviceId;
- }
-
- public int getType() {
- return mType;
- }
-
- // Parcelable
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mDeviceId);
- dest.writeInt(mType);
- }
-
- public void readFromParcel(Parcel source) {
- mDeviceId = source.readInt();
- mType = source.readInt();
- }
-}
diff --git a/core/java/android/tv/TvInputInfo.java b/core/java/android/tv/TvInputInfo.java
deleted file mode 100644
index 50462cc..0000000
--- a/core/java/android/tv/TvInputInfo.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * This class is used to specify meta information of a TV input.
- */
-public final class TvInputInfo implements Parcelable {
- private final ResolveInfo mService;
- private final String mId;
-
- /**
- * Constructor.
- *
- * @param service The ResolveInfo returned from the package manager about this TV input service.
- * @hide
- */
- public TvInputInfo(ResolveInfo service) {
- mService = service;
- ServiceInfo si = service.serviceInfo;
- mId = generateInputIdForComponenetName(new ComponentName(si.packageName, si.name));
- }
-
- /**
- * Returns a unique ID for this TV input. The ID is generated from the package and class name
- * implementing the TV input service.
- */
- public String getId() {
- return mId;
- }
-
- /**
- * Returns the .apk package that implements this TV input service.
- */
- public String getPackageName() {
- return mService.serviceInfo.packageName;
- }
-
- /**
- * Returns the class name of the service component that implements this TV input service.
- */
- public String getServiceName() {
- return mService.serviceInfo.name;
- }
-
- /**
- * Returns the component of the service that implements this TV input.
- */
- public ComponentName getComponent() {
- return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
- }
-
- /**
- * Loads the user-displayed label for this TV input service.
- *
- * @param pm Supplies a PackageManager used to load the TV input's resources.
- * @return a CharSequence containing the TV input's label. If the TV input does not have
- * a label, its name is returned.
- */
- public CharSequence loadLabel(PackageManager pm) {
- return mService.loadLabel(pm);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public int hashCode() {
- return mId.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
-
- if (!(o instanceof TvInputInfo)) {
- return false;
- }
-
- TvInputInfo obj = (TvInputInfo) o;
- return mId.equals(obj.mId)
- && mService.serviceInfo.packageName.equals(obj.mService.serviceInfo.packageName)
- && mService.serviceInfo.name.equals(obj.mService.serviceInfo.name);
- }
-
- @Override
- public String toString() {
- return "TvInputInfo{id=" + mId
- + ", pkg=" + mService.serviceInfo.packageName
- + ", service=" + mService.serviceInfo.name + "}";
- }
-
- /**
- * Used to package this object into a {@link Parcel}.
- *
- * @param dest The {@link Parcel} to be written.
- * @param flags The flags used for parceling.
- */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mId);
- mService.writeToParcel(dest, flags);
- }
-
- /**
- * Used to generate an input id from a ComponentName.
- *
- * @param name the component name for generating an input id.
- * @return the generated input id for the given {@code name}.
- * @hide
- */
- public static final String generateInputIdForComponenetName(ComponentName name) {
- return name.flattenToShortString();
- }
-
- /**
- * Used to make this class parcelable.
- *
- * @hide
- */
- public static final Parcelable.Creator<TvInputInfo> CREATOR =
- new Parcelable.Creator<TvInputInfo>() {
- @Override
- public TvInputInfo createFromParcel(Parcel in) {
- return new TvInputInfo(in);
- }
-
- @Override
- public TvInputInfo[] newArray(int size) {
- return new TvInputInfo[size];
- }
- };
-
- private TvInputInfo(Parcel in) {
- mId = in.readString();
- mService = ResolveInfo.CREATOR.createFromParcel(in);
- }
-}
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
deleted file mode 100644
index dfa84f8..0000000
--- a/core/java/android/tv/TvInputManager.java
+++ /dev/null
@@ -1,784 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pools.Pool;
-import android.util.Pools.SimplePool;
-import android.util.SparseArray;
-import android.view.InputChannel;
-import android.view.InputEvent;
-import android.view.InputEventSender;
-import android.view.Surface;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
- * interaction between applications and the selected TV inputs.
- */
-public final class TvInputManager {
- private static final String TAG = "TvInputManager";
-
- private final ITvInputManager mService;
-
- // A mapping from an input to the list of its TvInputListenerRecords.
- private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
- new HashMap<String, List<TvInputListenerRecord>>();
-
- // A mapping from the sequence number of a session to its SessionCallbackRecord.
- private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
- new SparseArray<SessionCallbackRecord>();
-
- // A sequence number for the next session to be created. Should be protected by a lock
- // {@code mSessionCallbackRecordMap}.
- private int mNextSeq;
-
- private final ITvInputClient mClient;
-
- private final int mUserId;
-
- /**
- * Interface used to receive the created session.
- */
- public abstract static class SessionCallback {
- /**
- * This is called after {@link TvInputManager#createSession} has been processed.
- *
- * @param session A {@link TvInputManager.Session} instance created. This can be
- * {@code null} if the creation request failed.
- */
- public void onSessionCreated(Session session) {
- }
-
- /**
- * This is called when {@link TvInputManager.Session} is released.
- * This typically happens when the process hosting the session has crashed or been killed.
- *
- * @param session A {@link TvInputManager.Session} instance released.
- */
- public void onSessionReleased(Session session) {
- }
- }
-
- private static final class SessionCallbackRecord {
- private final SessionCallback mSessionCallback;
- private final Handler mHandler;
- private Session mSession;
-
- public SessionCallbackRecord(SessionCallback sessionCallback,
- Handler handler) {
- mSessionCallback = sessionCallback;
- mHandler = handler;
- }
-
- public void postSessionCreated(final Session session) {
- mSession = session;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mSessionCallback.onSessionCreated(session);
- }
- });
- }
-
- public void postSessionReleased() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mSessionCallback.onSessionReleased(mSession);
- }
- });
- }
- }
-
- /**
- * Interface used to monitor status of the TV input.
- */
- public abstract static class TvInputListener {
- /**
- * This is called when the availability status of a given TV input is changed.
- *
- * @param inputId the id of the TV input.
- * @param isAvailable {@code true} if the given TV input is available to show TV programs.
- * {@code false} otherwise.
- */
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
- }
- }
-
- private static final class TvInputListenerRecord {
- private final TvInputListener mListener;
- private final Handler mHandler;
-
- public TvInputListenerRecord(TvInputListener listener, Handler handler) {
- mListener = listener;
- mHandler = handler;
- }
-
- public TvInputListener getListener() {
- return mListener;
- }
-
- public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onAvailabilityChanged(inputId, isAvailable);
- }
- });
- }
- }
-
- /**
- * @hide
- */
- public TvInputManager(ITvInputManager service, int userId) {
- mService = service;
- mUserId = userId;
- mClient = new ITvInputClient.Stub() {
- @Override
- public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
- int seq) {
- synchronized (mSessionCallbackRecordMap) {
- SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
- if (record == null) {
- Log.e(TAG, "Callback not found for " + token);
- return;
- }
- Session session = null;
- if (token != null) {
- session = new Session(token, channel, mService, mUserId, seq,
- mSessionCallbackRecordMap);
- }
- record.postSessionCreated(session);
- }
- }
-
- @Override
- public void onSessionReleased(int seq) {
- synchronized (mSessionCallbackRecordMap) {
- SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
- mSessionCallbackRecordMap.delete(seq);
- if (record == null) {
- Log.e(TAG, "Callback not found for seq:" + seq);
- return;
- }
- record.mSession.releaseInternal();
- record.postSessionReleased();
- }
- }
-
- @Override
- public void onAvailabilityChanged(String inputId, boolean isAvailable) {
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- // Silently ignore - no listener is registered yet.
- return;
- }
- int recordsCount = records.size();
- for (int i = 0; i < recordsCount; i++) {
- records.get(i).postAvailabilityChanged(inputId, isAvailable);
- }
- }
- }
- };
- }
-
- /**
- * Returns the complete list of TV inputs on the system.
- *
- * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
- */
- public List<TvInputInfo> getTvInputList() {
- try {
- return mService.getTvInputList(mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Returns the availability of a given TV input.
- *
- * @param inputId the id of the TV input.
- * @throws IllegalArgumentException if the argument is {@code null}.
- * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
- * TV input.
- */
- public boolean getAvailability(String inputId) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null || records.size() == 0) {
- throw new IllegalStateException("At least one listener should be registered.");
- }
- }
- try {
- return mService.getAvailability(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Registers a {@link TvInputListener} for a given TV input.
- *
- * @param inputId the id of the TV input.
- * @param listener a listener used to monitor status of the given TV input.
- * @param handler a {@link Handler} that the status change will be delivered to.
- * @throws IllegalArgumentException if any of the arguments is {@code null}.
- */
- public void registerListener(String inputId, TvInputListener listener, Handler handler) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
- if (handler == null) {
- throw new IllegalArgumentException("handler cannot be null");
- }
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- records = new ArrayList<TvInputListenerRecord>();
- mTvInputListenerRecordsMap.put(inputId, records);
- try {
- mService.registerCallback(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
- records.add(new TvInputListenerRecord(listener, handler));
- }
- }
-
- /**
- * Unregisters the existing {@link TvInputListener} for a given TV input.
- *
- * @param inputId the id of the TV input.
- * @param listener the existing listener to remove for the given TV input.
- * @throws IllegalArgumentException if any of the arguments is {@code null}.
- */
- public void unregisterListener(String inputId, final TvInputListener listener) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
- if (listener == null) {
- throw new IllegalArgumentException("listener cannot be null");
- }
- synchronized (mTvInputListenerRecordsMap) {
- List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
- if (records == null) {
- Log.e(TAG, "No listener found for " + inputId);
- return;
- }
- for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
- TvInputListenerRecord record = it.next();
- if (record.getListener() == listener) {
- it.remove();
- }
- }
- if (records.isEmpty()) {
- try {
- mService.unregisterCallback(mClient, inputId, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- } finally {
- mTvInputListenerRecordsMap.remove(inputId);
- }
- }
- }
- }
-
- /**
- * Creates a {@link Session} for a given TV input.
- * <p>
- * The number of sessions that can be created at the same time is limited by the capability of
- * the given TV input.
- * </p>
- *
- * @param inputId the id of the TV input.
- * @param callback a callback used to receive the created session.
- * @param handler a {@link Handler} that the session creation will be delivered to.
- * @throws IllegalArgumentException if any of the arguments is {@code null}.
- */
- public void createSession(String inputId, final SessionCallback callback,
- Handler handler) {
- if (inputId == null) {
- throw new IllegalArgumentException("id cannot be null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback cannot be null");
- }
- if (handler == null) {
- throw new IllegalArgumentException("handler cannot be null");
- }
- SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
- synchronized (mSessionCallbackRecordMap) {
- int seq = mNextSeq++;
- mSessionCallbackRecordMap.put(seq, record);
- try {
- mService.createSession(mClient, inputId, seq, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- /** The Session provides the per-session functionality of TV inputs. */
- public static final class Session {
- static final int DISPATCH_IN_PROGRESS = -1;
- static final int DISPATCH_NOT_HANDLED = 0;
- static final int DISPATCH_HANDLED = 1;
-
- private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
-
- private final ITvInputManager mService;
- private final int mUserId;
- private final int mSeq;
-
- // For scheduling input event handling on the main thread. This also serves as a lock to
- // protect pending input events and the input channel.
- private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
-
- private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
- private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
- private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
-
- private IBinder mToken;
- private TvInputEventSender mSender;
- private InputChannel mChannel;
-
- /** @hide */
- private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
- int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
- mToken = token;
- mChannel = channel;
- mService = service;
- mUserId = userId;
- mSeq = seq;
- mSessionCallbackRecordMap = sessionCallbackRecordMap;
- }
-
- /**
- * Releases this session.
- *
- * @throws IllegalStateException if the session has been already released.
- */
- public void release() {
- if (mToken == null) {
- throw new IllegalStateException("the session has been already released");
- }
- try {
- mService.releaseSession(mToken, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
-
- releaseInternal();
- }
-
- /**
- * Sets the {@link android.view.Surface} for this session.
- *
- * @param surface A {@link android.view.Surface} used to render video.
- * @throws IllegalStateException if the session has been already released.
- * @hide
- */
- public void setSurface(Surface surface) {
- if (mToken == null) {
- throw new IllegalStateException("the session has been already released");
- }
- // surface can be null.
- try {
- mService.setSurface(mToken, surface, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Sets the relative volume of this session to handle a change of audio focus.
- *
- * @param volume A volume value between 0.0f to 1.0f.
- * @throws IllegalArgumentException if the volume value is out of range.
- * @throws IllegalStateException if the session has been already released.
- */
- public void setVolume(float volume) {
- if (mToken == null) {
- throw new IllegalStateException("the session has been already released");
- }
- try {
- if (volume < 0.0f || volume > 1.0f) {
- throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
- }
- mService.setVolume(mToken, volume, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Tunes to a given channel.
- *
- * @param channelUri The URI of a channel.
- * @throws IllegalArgumentException if the argument is {@code null}.
- * @throws IllegalStateException if the session has been already released.
- */
- public void tune(Uri channelUri) {
- if (channelUri == null) {
- throw new IllegalArgumentException("channelUri cannot be null");
- }
- if (mToken == null) {
- throw new IllegalStateException("the session has been already released");
- }
- try {
- mService.tune(mToken, channelUri, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
- * should be called whenever the layout of its containing view is changed.
- * {@link #removeOverlayView()} should be called to remove the overlay view.
- * Since a session can have only one overlay view, this method should be called only once
- * or it can be called again after calling {@link #removeOverlayView()}.
- *
- * @param view A view playing TV.
- * @param frame A position of the overlay view.
- * @throws IllegalArgumentException if any of the arguments is {@code null}.
- * @throws IllegalStateException if {@code view} is not attached to a window or
- * if the session has been already released.
- */
- void createOverlayView(View view, Rect frame) {
- if (view == null) {
- throw new IllegalArgumentException("view cannot be null");
- }
- if (frame == null) {
- throw new IllegalArgumentException("frame cannot be null");
- }
- if (view.getWindowToken() == null) {
- throw new IllegalStateException("view must be attached to a window");
- }
- if (mToken == null) {
- throw new IllegalStateException("the session has been already released");
- }
- try {
- mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Relayouts the current overlay view.
- *
- * @param frame A new position of the overlay view.
- * @throws IllegalArgumentException if the arguments is {@code null}.
- * @throws IllegalStateException if the session has been already released.
- */
- void relayoutOverlayView(Rect frame) {
- if (frame == null) {
- throw new IllegalArgumentException("frame cannot be null");
- }
- if (mToken == null) {
- throw new IllegalStateException("the session has been already released");
- }
- try {
- mService.relayoutOverlayView(mToken, frame, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Removes the current overlay view.
- *
- * @throws IllegalStateException if the session has been already released.
- */
- void removeOverlayView() {
- if (mToken == null) {
- throw new IllegalStateException("the session has been already released");
- }
- try {
- mService.removeOverlayView(mToken, mUserId);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Dispatches an input event to this session.
- *
- * @param event {@link InputEvent} to dispatch.
- * @param token A token used to identify the input event later in the callback.
- * @param callback A callback used to receive the dispatch result.
- * @param handler {@link Handler} that the dispatch result will be delivered to.
- * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
- * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
- * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
- * be invoked later.
- * @throws IllegalArgumentException if any of the necessary arguments is {@code null}.
- * @hide
- */
- public int dispatchInputEvent(InputEvent event, Object token,
- FinishedInputEventCallback callback, Handler handler) {
- if (event == null) {
- throw new IllegalArgumentException("event cannot be null");
- }
- if (callback != null && handler == null) {
- throw new IllegalArgumentException("handler cannot be null");
- }
- synchronized (mHandler) {
- if (mChannel == null) {
- return DISPATCH_NOT_HANDLED;
- }
- PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
- if (Looper.myLooper() == Looper.getMainLooper()) {
- // Already running on the main thread so we can send the event immediately.
- return sendInputEventOnMainLooperLocked(p);
- }
-
- // Post the event to the main thread.
- Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
- msg.setAsynchronous(true);
- mHandler.sendMessage(msg);
- return DISPATCH_IN_PROGRESS;
- }
- }
-
- /**
- * Callback that is invoked when an input event that was dispatched to this session has been
- * finished.
- *
- * @hide
- */
- public interface FinishedInputEventCallback {
- /**
- * Called when the dispatched input event is finished.
- *
- * @param token a token passed to {@link #dispatchInputEvent}.
- * @param handled {@code true} if the dispatched input event was handled properly.
- * {@code false} otherwise.
- */
- public void onFinishedInputEvent(Object token, boolean handled);
- }
-
- // Must be called on the main looper
- private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
- synchronized (mHandler) {
- int result = sendInputEventOnMainLooperLocked(p);
- if (result == DISPATCH_IN_PROGRESS) {
- return;
- }
- }
-
- invokeFinishedInputEventCallback(p, false);
- }
-
- private int sendInputEventOnMainLooperLocked(PendingEvent p) {
- if (mChannel != null) {
- if (mSender == null) {
- mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
- }
-
- final InputEvent event = p.mEvent;
- final int seq = event.getSequenceNumber();
- if (mSender.sendInputEvent(seq, event)) {
- mPendingEvents.put(seq, p);
- Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
- msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
- return DISPATCH_IN_PROGRESS;
- }
-
- Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
- + event);
- }
- return DISPATCH_NOT_HANDLED;
- }
-
- void finishedInputEvent(int seq, boolean handled, boolean timeout) {
- final PendingEvent p;
- synchronized (mHandler) {
- int index = mPendingEvents.indexOfKey(seq);
- if (index < 0) {
- return; // spurious, event already finished or timed out
- }
-
- p = mPendingEvents.valueAt(index);
- mPendingEvents.removeAt(index);
-
- if (timeout) {
- Log.w(TAG, "Timeout waiting for seesion to handle input event after "
- + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
- } else {
- mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
- }
- }
-
- invokeFinishedInputEventCallback(p, handled);
- }
-
- // Assumes the event has already been removed from the queue.
- void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
- p.mHandled = handled;
- if (p.mHandler.getLooper().isCurrentThread()) {
- // Already running on the callback handler thread so we can send the callback
- // immediately.
- p.run();
- } else {
- // Post the event to the callback handler thread.
- // In this case, the callback will be responsible for recycling the event.
- Message msg = Message.obtain(p.mHandler, p);
- msg.setAsynchronous(true);
- msg.sendToTarget();
- }
- }
-
- private void flushPendingEventsLocked() {
- mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
-
- final int count = mPendingEvents.size();
- for (int i = 0; i < count; i++) {
- int seq = mPendingEvents.keyAt(i);
- Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
- msg.setAsynchronous(true);
- msg.sendToTarget();
- }
- }
-
- private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
- FinishedInputEventCallback callback, Handler handler) {
- PendingEvent p = mPendingEventPool.acquire();
- if (p == null) {
- p = new PendingEvent();
- }
- p.mEvent = event;
- p.mToken = token;
- p.mCallback = callback;
- p.mHandler = handler;
- return p;
- }
-
- private void recyclePendingEventLocked(PendingEvent p) {
- p.recycle();
- mPendingEventPool.release(p);
- }
-
- private void releaseInternal() {
- mToken = null;
- synchronized (mHandler) {
- if (mChannel != null) {
- if (mSender != null) {
- flushPendingEventsLocked();
- mSender.dispose();
- mSender = null;
- }
- mChannel.dispose();
- mChannel = null;
- }
- }
- synchronized (mSessionCallbackRecordMap) {
- mSessionCallbackRecordMap.remove(mSeq);
- }
- }
-
- private final class InputEventHandler extends Handler {
- public static final int MSG_SEND_INPUT_EVENT = 1;
- public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
- public static final int MSG_FLUSH_INPUT_EVENT = 3;
-
- InputEventHandler(Looper looper) {
- super(looper, null, true);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SEND_INPUT_EVENT: {
- sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
- return;
- }
- case MSG_TIMEOUT_INPUT_EVENT: {
- finishedInputEvent(msg.arg1, false, true);
- return;
- }
- case MSG_FLUSH_INPUT_EVENT: {
- finishedInputEvent(msg.arg1, false, false);
- return;
- }
- }
- }
- }
-
- private final class TvInputEventSender extends InputEventSender {
- public TvInputEventSender(InputChannel inputChannel, Looper looper) {
- super(inputChannel, looper);
- }
-
- @Override
- public void onInputEventFinished(int seq, boolean handled) {
- finishedInputEvent(seq, handled, false);
- }
- }
-
- private final class PendingEvent implements Runnable {
- public InputEvent mEvent;
- public Object mToken;
- public FinishedInputEventCallback mCallback;
- public Handler mHandler;
- public boolean mHandled;
-
- public void recycle() {
- mEvent = null;
- mToken = null;
- mCallback = null;
- mHandler = null;
- mHandled = false;
- }
-
- @Override
- public void run() {
- mCallback.onFinishedInputEvent(mToken, mHandled);
-
- synchronized (mHandler) {
- recyclePendingEventLocked(this);
- }
- }
- }
- }
-}
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
deleted file mode 100644
index eeb738d..0000000
--- a/core/java/android/tv/TvInputService.java
+++ /dev/null
@@ -1,551 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.RemoteCallbackList;
-import android.os.RemoteException;
-import android.tv.TvInputManager.Session;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.InputChannel;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.InputEventReceiver;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
-
-/**
- * A base class for implementing television input service.
- */
-public abstract class TvInputService extends Service {
- // STOPSHIP: Turn debugging off.
- private static final boolean DEBUG = true;
- private static final String TAG = "TvInputService";
-
- /**
- * This is the interface name that a service implementing a TV input should say that it support
- * -- that is, this is the action it uses for its intent filter. To be supported, the service
- * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
- * other applications cannot abuse it.
- */
- public static final String SERVICE_INTERFACE = "android.tv.TvInputService";
-
- private String mId;
- private final Handler mHandler = new ServiceHandler();
- private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
- new RemoteCallbackList<ITvInputServiceCallback>();
- private boolean mAvailable;
-
- @Override
- public void onCreate() {
- super.onCreate();
- mId = TvInputInfo.generateInputIdForComponenetName(
- new ComponentName(getPackageName(), getClass().getName()));
- }
-
- @Override
- public final IBinder onBind(Intent intent) {
- return new ITvInputService.Stub() {
- @Override
- public void registerCallback(ITvInputServiceCallback cb) {
- if (cb != null) {
- mCallbacks.register(cb);
- // The first time a callback is registered, the service needs to report its
- // availability status so that the system can know its initial value.
- try {
- cb.onAvailabilityChanged(mId, mAvailable);
- } catch (RemoteException e) {
- Log.e(TAG, "error in onAvailabilityChanged", e);
- }
- }
- }
-
- @Override
- public void unregisterCallback(ITvInputServiceCallback cb) {
- if (cb != null) {
- mCallbacks.unregister(cb);
- }
- }
-
- @Override
- public void createSession(InputChannel channel, ITvInputSessionCallback cb) {
- if (channel == null) {
- Log.w(TAG, "Creating session without input channel");
- }
- if (cb == null) {
- return;
- }
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = channel;
- args.arg2 = cb;
- mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
- }
- };
- }
-
- /**
- * Convenience method to notify an availability change of this TV input service.
- *
- * @param available {@code true} if the input service is available to show TV programs.
- */
- public final void setAvailable(boolean available) {
- if (available != mAvailable) {
- mAvailable = available;
- mHandler.obtainMessage(ServiceHandler.DO_BROADCAST_AVAILABILITY_CHANGE, available)
- .sendToTarget();
- }
- }
-
- /**
- * Get the number of callbacks that are registered.
- *
- * @hide
- */
- @VisibleForTesting
- public final int getRegisteredCallbackCount() {
- return mCallbacks.getRegisteredCallbackCount();
- }
-
- /**
- * Returns a concrete implementation of {@link TvInputSessionImpl}.
- * <p>
- * May return {@code null} if this TV input service fails to create a session for some reason.
- * </p>
- */
- public abstract TvInputSessionImpl onCreateSession();
-
- /**
- * Base class for derived classes to implement to provide {@link TvInputManager.Session}.
- */
- public abstract class TvInputSessionImpl implements KeyEvent.Callback {
- private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
- private final WindowManager mWindowManager;
- private WindowManager.LayoutParams mWindowParams;
- private Surface mSurface;
- private View mOverlayView;
- private boolean mOverlayViewEnabled;
- private IBinder mWindowToken;
- private Rect mOverlayFrame;
-
- public TvInputSessionImpl() {
- mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
- }
-
- /**
- * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
- * called explicitly after the session is created to enable the overlay view.
- *
- * @param enable {@code true} if you want to enable the overlay view. {@code false}
- * otherwise.
- */
- public void setOverlayViewEnabled(final boolean enable) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (enable == mOverlayViewEnabled) {
- return;
- }
- mOverlayViewEnabled = enable;
- if (enable) {
- if (mWindowToken != null) {
- createOverlayView(mWindowToken, mOverlayFrame);
- }
- } else {
- removeOverlayView(false);
- }
- }
- });
- }
-
- /**
- * Called when the session is released.
- */
- public abstract void onRelease();
-
- /**
- * Sets the {@link Surface} for the current input session on which the TV input renders
- * video.
- *
- * @param surface {@link Surface} an application passes to this TV input session.
- * @return {@code true} if the surface was set, {@code false} otherwise.
- */
- public abstract boolean onSetSurface(Surface surface);
-
- /**
- * Sets the relative volume of the current TV input session to handle the change of audio
- * focus by setting.
- *
- * @param volume Volume scale from 0.0 to 1.0.
- */
- public abstract void onSetVolume(float volume);
-
- /**
- * Tunes to a given channel.
- *
- * @param channelUri The URI of the channel.
- * @return {@code true} the tuning was successful, {@code false} otherwise.
- */
- public abstract boolean onTune(Uri channelUri);
-
- /**
- * Called when an application requests to create an overlay view. Each session
- * implementation can override this method and return its own view.
- *
- * @return a view attached to the overlay window
- */
- public View onCreateOverlayView() {
- return null;
- }
-
- /**
- * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
- * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
- * <p>
- * Override this to intercept key down events before they are processed by the application.
- * If you return true, the application will not process the event itself. If you return
- * false, the normal application processing will occur as if the TV input had not seen the
- * event at all.
- *
- * @param keyCode The value in event.getKeyCode().
- * @param event Description of the key event.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- return false;
- }
-
- /**
- * Default implementation of
- * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
- * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
- * <p>
- * Override this to intercept key long press events before they are processed by the
- * application. If you return true, the application will not process the event itself. If
- * you return false, the normal application processing will occur as if the TV input had not
- * seen the event at all.
- *
- * @param keyCode The value in event.getKeyCode().
- * @param event Description of the key event.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- */
- @Override
- public boolean onKeyLongPress(int keyCode, KeyEvent event) {
- return false;
- }
-
- /**
- * Default implementation of
- * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
- * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
- * <p>
- * Override this to intercept special key multiple events before they are processed by the
- * application. If you return true, the application will not itself process the event. If
- * you return false, the normal application processing will occur as if the TV input had not
- * seen the event at all.
- *
- * @param keyCode The value in event.getKeyCode().
- * @param count The number of times the action was made.
- * @param event Description of the key event.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- */
- @Override
- public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
- return false;
- }
-
- /**
- * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
- * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
- * <p>
- * Override this to intercept key up events before they are processed by the application. If
- * you return true, the application will not itself process the event. If you return false,
- * the normal application processing will occur as if the TV input had not seen the event at
- * all.
- *
- * @param keyCode The value in event.getKeyCode().
- * @param event Description of the key event.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- */
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- return false;
- }
-
- /**
- * Implement this method to handle touch screen motion events on the current input session.
- *
- * @param event The motion event being received.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- * @see View#onTouchEvent
- */
- public boolean onTouchEvent(MotionEvent event) {
- return false;
- }
-
- /**
- * Implement this method to handle trackball events on the current input session.
- *
- * @param event The motion event being received.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- * @see View#onTrackballEvent
- */
- public boolean onTrackballEvent(MotionEvent event) {
- return false;
- }
-
- /**
- * Implement this method to handle generic motion events on the current input session.
- *
- * @param event The motion event being received.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- * @see View#onGenericMotionEvent
- */
- public boolean onGenericMotionEvent(MotionEvent event) {
- return false;
- }
-
- /**
- * This method is called when the application would like to stop using the current input
- * session.
- */
- void release() {
- onRelease();
- if (mSurface != null) {
- mSurface.release();
- mSurface = null;
- }
- removeOverlayView(true);
- }
-
- /**
- * Calls {@link #onSetSurface}.
- */
- void setSurface(Surface surface) {
- onSetSurface(surface);
- if (mSurface != null) {
- mSurface.release();
- }
- mSurface = surface;
- // TODO: Handle failure.
- }
-
- /**
- * Calls {@link #onSetVolume}.
- */
- void setVolume(float volume) {
- onSetVolume(volume);
- }
-
- /**
- * Calls {@link #onTune}.
- */
- void tune(Uri channelUri) {
- onTune(channelUri);
- // TODO: Handle failure.
- }
-
- /**
- * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
- * to the overlay window.
- *
- * @param windowToken A window token of an application.
- * @param frame A position of the overlay view.
- */
- void createOverlayView(IBinder windowToken, Rect frame) {
- if (mOverlayView != null) {
- mWindowManager.removeView(mOverlayView);
- mOverlayView = null;
- }
- if (DEBUG) {
- Log.d(TAG, "create overlay view(" + frame + ")");
- }
- mWindowToken = windowToken;
- mOverlayFrame = frame;
- if (!mOverlayViewEnabled) {
- return;
- }
- mOverlayView = onCreateOverlayView();
- if (mOverlayView == null) {
- return;
- }
- // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
- // an overlay window above the media window but below the application window.
- int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
- // We make the overlay view non-focusable and non-touchable so that
- // the application that owns the window token can decide whether to consume or
- // dispatch the input events.
- int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- mWindowParams = new WindowManager.LayoutParams(
- frame.right - frame.left, frame.bottom - frame.top,
- frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
- mWindowParams.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
- mWindowParams.gravity = Gravity.START | Gravity.TOP;
- mWindowParams.token = windowToken;
- mWindowManager.addView(mOverlayView, mWindowParams);
- }
-
- /**
- * Relayouts the current overlay view.
- *
- * @param frame A new position of the overlay view.
- */
- void relayoutOverlayView(Rect frame) {
- if (DEBUG) {
- Log.d(TAG, "relayout overlay view(" + frame + ")");
- }
- mOverlayFrame = frame;
- if (!mOverlayViewEnabled || mOverlayView == null) {
- return;
- }
- mWindowParams.x = frame.left;
- mWindowParams.y = frame.top;
- mWindowParams.width = frame.right - frame.left;
- mWindowParams.height = frame.bottom - frame.top;
- mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
- }
-
- /**
- * Removes the current overlay view.
- */
- void removeOverlayView(boolean clearWindowToken) {
- if (DEBUG) {
- Log.d(TAG, "remove overlay view(" + mOverlayView + ")");
- }
- if (clearWindowToken) {
- mWindowToken = null;
- mOverlayFrame = null;
- }
- if (mOverlayView != null) {
- mWindowManager.removeView(mOverlayView);
- mOverlayView = null;
- mWindowParams = null;
- }
- }
-
- /**
- * Takes care of dispatching incoming input events and tells whether the event was handled.
- */
- int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
- if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
- if (event instanceof KeyEvent) {
- if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) {
- return Session.DISPATCH_HANDLED;
- }
- } else if (event instanceof MotionEvent) {
- MotionEvent motionEvent = (MotionEvent) event;
- final int source = motionEvent.getSource();
- if (motionEvent.isTouchEvent()) {
- if (onTouchEvent(motionEvent)) {
- return Session.DISPATCH_HANDLED;
- }
- } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- if (onTrackballEvent(motionEvent)) {
- return Session.DISPATCH_HANDLED;
- }
- } else {
- if (onGenericMotionEvent(motionEvent)) {
- return Session.DISPATCH_HANDLED;
- }
- }
- }
- if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
- return Session.DISPATCH_NOT_HANDLED;
- }
- if (!mOverlayView.hasWindowFocus()) {
- mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
- }
- mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
- return Session.DISPATCH_IN_PROGRESS;
- }
- }
-
- private final class ServiceHandler extends Handler {
- private static final int DO_CREATE_SESSION = 1;
- private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
-
- @Override
- public final void handleMessage(Message msg) {
- switch (msg.what) {
- case DO_CREATE_SESSION: {
- SomeArgs args = (SomeArgs) msg.obj;
- InputChannel channel = (InputChannel) args.arg1;
- ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
- try {
- TvInputSessionImpl sessionImpl = onCreateSession();
- if (sessionImpl == null) {
- // Failed to create a session.
- cb.onSessionCreated(null);
- } else {
- ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
- sessionImpl, channel);
- cb.onSessionCreated(stub);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "error in onSessionCreated");
- }
- args.recycle();
- return;
- }
- case DO_BROADCAST_AVAILABILITY_CHANGE: {
- boolean isAvailable = (Boolean) msg.obj;
- int n = mCallbacks.beginBroadcast();
- try {
- for (int i = 0; i < n; i++) {
- mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Unexpected exception", e);
- } finally {
- mCallbacks.finishBroadcast();
- }
- return;
- }
- default: {
- Log.w(TAG, "Unhandled message code: " + msg.what);
- return;
- }
- }
- }
- }
-}
diff --git a/core/java/android/tv/TvStreamConfig.aidl b/core/java/android/tv/TvStreamConfig.aidl
deleted file mode 100644
index 4d0add4..0000000
--- a/core/java/android/tv/TvStreamConfig.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 2014, 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.tv;
-
-parcelable TvStreamConfig; \ No newline at end of file
diff --git a/core/java/android/tv/TvStreamConfig.java b/core/java/android/tv/TvStreamConfig.java
deleted file mode 100644
index 03e63b1..0000000
--- a/core/java/android/tv/TvStreamConfig.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
-
-/**
- * @hide
- */
-public class TvStreamConfig implements Parcelable {
- static final String TAG = TvStreamConfig.class.getSimpleName();
-
- public final static int STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE = 1;
- public final static int STREAM_TYPE_BUFFER_PRODUCER = 2;
-
- private int mStreamId;
- private int mType;
- // TODO: Revisit if max widht/height really make sense.
- private int mMaxWidth;
- private int mMaxHeight;
- /**
- * Generations are incremented once framework receives STREAM_CONFIGURATION_CHANGED event from
- * HAL module. Framework should throw away outdated configurations and get new configurations
- * via tv_input_device::get_stream_configurations().
- */
- private int mGeneration;
-
- public static final Parcelable.Creator<TvStreamConfig> CREATOR =
- new Parcelable.Creator<TvStreamConfig>() {
- @Override
- public TvStreamConfig createFromParcel(Parcel source) {
- try {
- return new Builder().
- streamId(source.readInt()).
- type(source.readInt()).
- maxWidth(source.readInt()).
- maxHeight(source.readInt()).
- generation(source.readInt()).build();
- } catch (Exception e) {
- Log.e(TAG, "Exception creating TvStreamConfig from parcel", e);
- return null;
- }
- }
-
- @Override
- public TvStreamConfig[] newArray(int size) {
- return new TvStreamConfig[size];
- }
- };
-
- private TvStreamConfig() {}
-
- public int getStreamId() {
- return mStreamId;
- }
-
- public int getType() {
- return mType;
- }
-
- public int getMaxWidth() {
- return mMaxWidth;
- }
-
- public int getMaxHeight() {
- return mMaxHeight;
- }
-
- public int getGeneration() {
- return mGeneration;
- }
-
- // Parcelable
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mStreamId);
- dest.writeInt(mType);
- dest.writeInt(mMaxWidth);
- dest.writeInt(mMaxHeight);
- dest.writeInt(mGeneration);
- }
-
- /**
- * A helper class for creating a TvStreamConfig object.
- */
- public static final class Builder {
- private Integer mStreamId;
- private Integer mType;
- private Integer mMaxWidth;
- private Integer mMaxHeight;
- private Integer mGeneration;
-
- public Builder() {
- }
-
- public Builder streamId(int streamId) {
- mStreamId = streamId;
- return this;
- }
-
- public Builder type(int type) {
- mType = type;
- return this;
- }
-
- public Builder maxWidth(int maxWidth) {
- mMaxWidth = maxWidth;
- return this;
- }
-
- public Builder maxHeight(int maxHeight) {
- mMaxHeight = maxHeight;
- return this;
- }
-
- public Builder generation(int generation) {
- mGeneration = generation;
- return this;
- }
-
- public TvStreamConfig build() {
- if (mStreamId == null || mType == null || mMaxWidth == null || mMaxHeight == null
- || mGeneration == null) {
- throw new UnsupportedOperationException();
- }
-
- TvStreamConfig config = new TvStreamConfig();
- config.mStreamId = mStreamId;
- config.mType = mType;
- config.mMaxWidth = mMaxWidth;
- config.mMaxHeight = mMaxHeight;
- config.mGeneration = mGeneration;
- return config;
- }
- }
-} \ No newline at end of file
diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java
deleted file mode 100644
index 59b6386..0000000
--- a/core/java/android/tv/TvView.java
+++ /dev/null
@@ -1,383 +0,0 @@
-/*
- * Copyright (C) 2014 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.tv;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.tv.TvInputManager.Session;
-import android.tv.TvInputManager.Session.FinishedInputEventCallback;
-import android.tv.TvInputManager.SessionCallback;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.ViewRootImpl;
-
-/**
- * View playing TV
- */
-public class TvView extends SurfaceView {
- // STOPSHIP: Turn debugging off.
- private static final boolean DEBUG = true;
- private static final String TAG = "TvView";
-
- private final Handler mHandler = new Handler();
- private TvInputManager.Session mSession;
- private Surface mSurface;
- private boolean mOverlayViewCreated;
- private Rect mOverlayViewFrame;
- private final TvInputManager mTvInputManager;
- private SessionCallback mSessionCallback;
- private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
-
- private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
- + ", height=" + height + ")");
- if (holder.getSurface() == mSurface) {
- return;
- }
- mSurface = holder.getSurface();
- setSessionSurface(mSurface);
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- mSurface = holder.getSurface();
- setSessionSurface(mSurface);
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- mSurface = null;
- setSessionSurface(null);
- }
- };
-
- private final FinishedInputEventCallback mFinishedInputEventCallback =
- new FinishedInputEventCallback() {
- @Override
- public void onFinishedInputEvent(Object token, boolean handled) {
- if (DEBUG) {
- Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
- }
- if (handled) {
- return;
- }
- // TODO: Re-order unhandled events.
- InputEvent event = (InputEvent) token;
- if (dispatchUnhandledInputEvent(event)) {
- return;
- }
- ViewRootImpl viewRootImpl = getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.dispatchUnhandledInputEvent(event);
- }
- }
- };
-
- public TvView(Context context) {
- this(context, null, 0);
- }
-
- public TvView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- getHolder().addCallback(mSurfaceHolderCallback);
- mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
- }
-
- /**
- * Binds a TV input to this view. {@link SessionCallback#onSessionCreated} will be
- * called to send the result of this binding with {@link TvInputManager.Session}.
- * If a TV input is already bound, the input will be unbound from this view and its session
- * will be released.
- *
- * @param inputId the id of TV input which will be bound to this view.
- * @param callback called when TV input is bound. The callback sends
- * {@link TvInputManager.Session}
- * @throws IllegalArgumentException if any of the arguments is {@code null}.
- */
- public void bindTvInput(String inputId, SessionCallback callback) {
- if (TextUtils.isEmpty(inputId)) {
- throw new IllegalArgumentException("inputId cannot be null or an empty string");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback cannot be null");
- }
- if (mSession != null) {
- release();
- }
- // When bindTvInput is called multiple times before the callback is called,
- // only the callback of the last bindTvInput call will be actually called back.
- // The previous callbacks will be ignored. For the logic, mSessionCallback
- // is newly assigned for every bindTvInput call and compared with
- // MySessionCreateCallback.this.
- mSessionCallback = new MySessionCallback(callback);
- mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
- }
-
- /**
- * Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session}
- * is released.
- */
- public void unbindTvInput() {
- if (mSession != null) {
- release();
- }
- mSessionCallback = null;
- }
-
- /**
- * Dispatches an unhandled input event to the next receiver.
- * <p>
- * Except system keys, TvView always consumes input events in the normal flow. This is called
- * asynchronously from where the event is dispatched. It gives the host application a chance to
- * dispatch the unhandled input events.
- *
- * @param event The input event.
- * @return {@code true} if the event was handled by the view, {@code false} otherwise.
- */
- public boolean dispatchUnhandledInputEvent(InputEvent event) {
- if (mOnUnhandledInputEventListener != null) {
- if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
- return true;
- }
- }
- return onUnhandledInputEvent(event);
- }
-
- /**
- * Called when an unhandled input event was also not handled by the user provided callback. This
- * is the last chance to handle the unhandled input event in the TvView.
- *
- * @param event The input event.
- * @return If you handled the event, return {@code true}. If you want to allow the event to be
- * handled by the next receiver, return {@code false}.
- */
- public boolean onUnhandledInputEvent(InputEvent event) {
- return false;
- }
-
- /**
- * Registers a callback to be invoked when an input event was not handled by the bound TV input.
- *
- * @param listener The callback to invoke when the unhandled input event was received.
- */
- public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
- mOnUnhandledInputEventListener = listener;
- }
-
- @Override
- public boolean dispatchKeyEvent(KeyEvent event) {
- if (super.dispatchKeyEvent(event)) {
- return true;
- }
- if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
- if (mSession == null) {
- return false;
- }
- InputEvent copiedEvent = event.copy();
- int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
- mHandler);
- return ret != Session.DISPATCH_NOT_HANDLED;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (super.dispatchTouchEvent(event)) {
- return true;
- }
- if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
- if (mSession == null) {
- return false;
- }
- InputEvent copiedEvent = event.copy();
- int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
- mHandler);
- return ret != Session.DISPATCH_NOT_HANDLED;
- }
-
- @Override
- public boolean dispatchTrackballEvent(MotionEvent event) {
- if (super.dispatchTrackballEvent(event)) {
- return true;
- }
- if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
- if (mSession == null) {
- return false;
- }
- InputEvent copiedEvent = event.copy();
- int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
- mHandler);
- return ret != Session.DISPATCH_NOT_HANDLED;
- }
-
- @Override
- public boolean dispatchGenericMotionEvent(MotionEvent event) {
- if (super.dispatchGenericMotionEvent(event)) {
- return true;
- }
- if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
- if (mSession == null) {
- return false;
- }
- InputEvent copiedEvent = event.copy();
- int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
- mHandler);
- return ret != Session.DISPATCH_NOT_HANDLED;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- createSessionOverlayView();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- removeSessionOverlayView();
- super.onDetachedFromWindow();
- }
-
- /** @hide */
- @Override
- protected void updateWindow(boolean force, boolean redrawNeeded) {
- super.updateWindow(force, redrawNeeded);
- relayoutSessionOverlayView();
- }
-
- private void release() {
- setSessionSurface(null);
- removeSessionOverlayView();
- mSession.release();
- mSession = null;
- }
-
- private void setSessionSurface(Surface surface) {
- if (mSession == null) {
- return;
- }
- mSession.setSurface(surface);
- }
-
- private void createSessionOverlayView() {
- if (mSession == null || !isAttachedToWindow()
- || mOverlayViewCreated) {
- return;
- }
- mOverlayViewFrame = getViewFrameOnScreen();
- mSession.createOverlayView(this, mOverlayViewFrame);
- mOverlayViewCreated = true;
- }
-
- private void removeSessionOverlayView() {
- if (mSession == null || !mOverlayViewCreated) {
- return;
- }
- mSession.removeOverlayView();
- mOverlayViewCreated = false;
- mOverlayViewFrame = null;
- }
-
- private void relayoutSessionOverlayView() {
- if (mSession == null || !isAttachedToWindow()
- || !mOverlayViewCreated) {
- return;
- }
- Rect viewFrame = getViewFrameOnScreen();
- if (viewFrame.equals(mOverlayViewFrame)) {
- return;
- }
- mSession.relayoutOverlayView(viewFrame);
- mOverlayViewFrame = viewFrame;
- }
-
- private Rect getViewFrameOnScreen() {
- int[] location = new int[2];
- getLocationOnScreen(location);
- return new Rect(location[0], location[1],
- location[0] + getWidth(), location[1] + getHeight());
- }
-
- /**
- * Interface definition for a callback to be invoked when the unhandled input event is received.
- */
- public interface OnUnhandledInputEventListener {
- /**
- * Called when an input event was not handled by the bound TV input.
- * <p>
- * This is called asynchronously from where the event is dispatched. It gives the host
- * application a chance to handle the unhandled input events.
- *
- * @param event The input event.
- * @return If you handled the event, return {@code true}. If you want to allow the event to
- * be handled by the next receiver, return {@code false}.
- */
- boolean onUnhandledInputEvent(InputEvent event);
- }
-
- private class MySessionCallback extends SessionCallback {
- final SessionCallback mExternalCallback;
-
- MySessionCallback(SessionCallback externalCallback) {
- mExternalCallback = externalCallback;
- }
-
- @Override
- public void onSessionCreated(Session session) {
- if (this != mSessionCallback) {
- // This callback is obsolete.
- if (session != null) {
- session.release();
- }
- return;
- }
- mSession = session;
- if (session != null) {
- // mSurface may not be ready yet as soon as starting an application.
- // In the case, we don't send Session.setSurface(null) unnecessarily.
- // setSessionSurface will be called in surfaceCreated.
- if (mSurface != null) {
- setSessionSurface(mSurface);
- }
- createSessionOverlayView();
- }
- if (mExternalCallback != null) {
- mExternalCallback.onSessionCreated(session);
- }
- }
-
- @Override
- public void onSessionReleased(Session session) {
- mSession = null;
- if (mExternalCallback != null) {
- mExternalCallback.onSessionReleased(session);
- }
- }
- }
-}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 424d860..5056097 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -75,22 +75,10 @@ class GLES20Canvas extends HardwareCanvas {
// Constructors
///////////////////////////////////////////////////////////////////////////
- /**
- * Creates a canvas to render directly on screen.
- */
- GLES20Canvas(boolean translucent) {
- this(false, translucent);
- }
-
- protected GLES20Canvas(boolean record, boolean translucent) {
- mOpaque = !translucent;
-
- if (record) {
- mRenderer = nCreateDisplayListRenderer();
- } else {
- mRenderer = nCreateRenderer();
- }
-
+ // TODO: Merge with GLES20RecordingCanvas
+ protected GLES20Canvas() {
+ mOpaque = false;
+ mRenderer = nCreateDisplayListRenderer();
setupFinalizer();
}
@@ -102,7 +90,6 @@ class GLES20Canvas extends HardwareCanvas {
}
}
- private static native long nCreateRenderer();
private static native long nCreateDisplayListRenderer();
private static native void nResetDisplayListRenderer(long renderer);
private static native void nDestroyRenderer(long renderer);
@@ -131,36 +118,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nSetProperty(String name, String value);
///////////////////////////////////////////////////////////////////////////
- // Hardware layers
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- void pushLayerUpdate(HardwareLayer layer) {
- nPushLayerUpdate(mRenderer, layer.getLayer());
- }
-
- @Override
- void cancelLayerUpdate(HardwareLayer layer) {
- nCancelLayerUpdate(mRenderer, layer.getLayer());
- }
-
- @Override
- void flushLayerUpdates() {
- nFlushLayerUpdates(mRenderer);
- }
-
- @Override
- void clearLayerUpdates() {
- nClearLayerUpdates(mRenderer);
- }
-
- static native boolean nCopyLayer(long layerId, long bitmap);
- private static native void nClearLayerUpdates(long renderer);
- private static native void nFlushLayerUpdates(long renderer);
- private static native void nPushLayerUpdate(long renderer, long layer);
- private static native void nCancelLayerUpdate(long renderer, long layer);
-
- ///////////////////////////////////////////////////////////////////////////
// Canvas management
///////////////////////////////////////////////////////////////////////////
@@ -234,20 +191,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nFinish(long renderer);
- /**
- * Returns the size of the stencil buffer required by the underlying
- * implementation.
- *
- * @return The minimum number of bits the stencil buffer must. Always >= 0.
- *
- * @hide
- */
- public static int getStencilSize() {
- return nGetStencilSize();
- }
-
- private static native int nGetStencilSize();
-
///////////////////////////////////////////////////////////////////////////
// Functor
///////////////////////////////////////////////////////////////////////////
@@ -284,49 +227,6 @@ class GLES20Canvas extends HardwareCanvas {
*/
static final int FLUSH_CACHES_FULL = 2;
- /**
- * Flush caches to reclaim as much memory as possible. The amount of memory
- * to reclaim is indicate by the level parameter.
- *
- * The level can be one of {@link #FLUSH_CACHES_MODERATE} or
- * {@link #FLUSH_CACHES_FULL}.
- *
- * @param level Hint about the amount of memory to reclaim
- */
- static void flushCaches(int level) {
- nFlushCaches(level);
- }
-
- private static native void nFlushCaches(int level);
-
- /**
- * Release all resources associated with the underlying caches. This should
- * only be called after a full flushCaches().
- *
- * @hide
- */
- static void terminateCaches() {
- nTerminateCaches();
- }
-
- private static native void nTerminateCaches();
-
- static boolean initCaches() {
- return nInitCaches();
- }
-
- private static native boolean nInitCaches();
-
- ///////////////////////////////////////////////////////////////////////////
- // Atlas
- ///////////////////////////////////////////////////////////////////////////
-
- static void initAtlas(GraphicBuffer buffer, long[] map) {
- nInitAtlas(buffer, map, map.length);
- }
-
- private static native void nInitAtlas(GraphicBuffer buffer, long[] map, int count);
-
///////////////////////////////////////////////////////////////////////////
// Display list
///////////////////////////////////////////////////////////////////////////
@@ -899,12 +799,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawPath(long renderer, long path, long paint);
private static native void nDrawRects(long renderer, long region, long paint);
- void drawRects(float[] rects, int count, Paint paint) {
- nDrawRects(mRenderer, rects, count, paint.mNativePaint);
- }
-
- private static native void nDrawRects(long renderer, float[] rects, int count, long paint);
-
@Override
public void drawPicture(Picture picture) {
if (picture.createdFromStream) {
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index a94ec3a..b2961e5 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -36,7 +36,7 @@ class GLES20RecordingCanvas extends GLES20Canvas {
RenderNode mNode;
private GLES20RecordingCanvas() {
- super(true, true);
+ super();
}
static GLES20RecordingCanvas obtain(@NonNull RenderNode node) {
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
deleted file mode 100644
index 6dd7c00..0000000
--- a/core/java/android/view/GLRenderer.java
+++ /dev/null
@@ -1,1534 +0,0 @@
-/*
- * 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 static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW;
-import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT;
-import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY;
-import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_DRAW;
-import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT;
-import static javax.microedition.khronos.egl.EGL10.EGL_NONE;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE;
-import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE;
-import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES;
-import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS;
-import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS;
-import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE;
-import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH;
-import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT;
-
-import android.content.ComponentCallbacks2;
-import android.graphics.Bitmap;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
-import android.opengl.EGL14;
-import android.opengl.GLUtils;
-import android.opengl.ManagedEGLContext;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Surface.OutOfResourcesException;
-
-import com.google.android.gles_jni.EGLImpl;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGL11;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL;
-
-/**
- * Hardware renderer using OpenGL
- *
- * @hide
- */
-public class GLRenderer extends HardwareRenderer {
- static final int SURFACE_STATE_ERROR = 0;
- static final int SURFACE_STATE_SUCCESS = 1;
- static final int SURFACE_STATE_UPDATED = 2;
-
- static final int FUNCTOR_PROCESS_DELAY = 4;
-
- /**
- * Number of frames to profile.
- */
- private static final int PROFILE_MAX_FRAMES = 128;
-
- /**
- * Number of floats per profiled frame.
- */
- private static final int PROFILE_FRAME_DATA_COUNT = 3;
-
- 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;
-
- private static final String[] VISUALIZERS = {
- PROFILE_PROPERTY_VISUALIZE_BARS,
- PROFILE_PROPERTY_VISUALIZE_LINES
- };
-
- private static final String[] OVERDRAW = {
- OVERDRAW_PROPERTY_SHOW,
- };
- private static final int GL_VERSION = 2;
-
- static EGL10 sEgl;
- static EGLDisplay sEglDisplay;
- static EGLConfig sEglConfig;
- static final Object[] sEglLock = new Object[0];
- int mWidth = -1, mHeight = -1;
-
- static final ThreadLocal<ManagedEGLContext> sEglContextStorage
- = new ThreadLocal<ManagedEGLContext>();
-
- EGLContext mEglContext;
- Thread mEglThread;
-
- EGLSurface mEglSurface;
-
- GL mGl;
- HardwareCanvas mCanvas;
-
- String mName;
-
- long mFrameCount;
- Paint mDebugPaint;
-
- static boolean sDirtyRegions;
- static final boolean sDirtyRegionsRequested;
- static {
- String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
- //noinspection PointlessBooleanExpression,ConstantConditions
- sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty);
- sDirtyRegionsRequested = sDirtyRegions;
- }
-
- boolean mDirtyRegionsEnabled;
- boolean mUpdateDirtyRegions;
-
- boolean mProfileEnabled;
- int mProfileVisualizerType = -1;
- float[] mProfileData;
- ReentrantLock mProfileLock;
- int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
-
- GraphDataProvider mDebugDataProvider;
- float[][] mProfileShapes;
- Paint mProfilePaint;
-
- boolean mDebugDirtyRegions;
- int mDebugOverdraw = -1;
-
- final boolean mTranslucent;
-
- private boolean mDestroyed;
-
- private final Rect mRedrawClip = new Rect();
-
- private final int[] mSurfaceSize = new int[2];
-
- private long mDrawDelta = Long.MAX_VALUE;
-
- private GLES20Canvas mGlCanvas;
-
- private DisplayMetrics mDisplayMetrics;
-
- private static EGLSurface sPbuffer;
- private static final Object[] sPbufferLock = new Object[0];
-
- private List<HardwareLayer> mAttachedLayers = new ArrayList<HardwareLayer>();
-
- private static class GLRendererEglContext extends ManagedEGLContext {
- final Handler mHandler = new Handler();
-
- public GLRendererEglContext(EGLContext context) {
- super(context);
- }
-
- @Override
- public void onTerminate(final EGLContext eglContext) {
- // Make sure we do this on the correct thread.
- if (mHandler.getLooper() != Looper.myLooper()) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onTerminate(eglContext);
- }
- });
- return;
- }
-
- synchronized (sEglLock) {
- if (sEgl == null) return;
-
- if (EGLImpl.getInitCount(sEglDisplay) == 1) {
- usePbufferSurface(eglContext);
- GLES20Canvas.terminateCaches();
-
- sEgl.eglDestroyContext(sEglDisplay, eglContext);
- sEglContextStorage.set(null);
- sEglContextStorage.remove();
-
- sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
- sEgl.eglReleaseThread();
- sEgl.eglTerminate(sEglDisplay);
-
- sEgl = null;
- sEglDisplay = null;
- sEglConfig = null;
- sPbuffer = null;
- }
- }
- }
- }
-
- HardwareCanvas createCanvas() {
- return mGlCanvas = new GLES20Canvas(mTranslucent);
- }
-
- ManagedEGLContext createManagedContext(EGLContext eglContext) {
- return new GLRendererEglContext(mEglContext);
- }
-
- 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,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
- EGL_DEPTH_SIZE, 0,
- EGL_CONFIG_CAVEAT, EGL_NONE,
- EGL_STENCIL_SIZE, stencilSize,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
- EGL_NONE
- };
- }
-
- void initCaches() {
- if (GLES20Canvas.initCaches()) {
- // Caches were (re)initialized, rebind atlas
- initAtlas();
- }
- }
-
- void initAtlas() {
- IBinder binder = ServiceManager.getService("assetatlas");
- if (binder == null) return;
-
- IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);
- try {
- if (atlas.isCompatible(android.os.Process.myPpid())) {
- GraphicBuffer buffer = atlas.getBuffer();
- if (buffer != null) {
- long[] map = atlas.getMap();
- if (map != null) {
- GLES20Canvas.initAtlas(buffer, map);
- }
- // If IAssetAtlas is not the same class as the IBinder
- // we are using a remote service and we can safely
- // destroy the graphic buffer
- if (atlas.getClass() != binder.getClass()) {
- buffer.destroy();
- }
- }
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Could not acquire atlas", e);
- }
- }
-
- boolean canDraw() {
- return mGl != null && mCanvas != null && mGlCanvas != null;
- }
-
- int onPreDraw(Rect dirty) {
- return mGlCanvas.onPreDraw(dirty);
- }
-
- void onPostDraw() {
- mGlCanvas.onPostDraw();
- }
-
- 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 {
- if (full && mCanvas != null) {
- mCanvas = null;
- }
-
- if (!isEnabled() || mDestroyed) {
- setEnabled(false);
- return;
- }
-
- destroySurface();
- setEnabled(false);
-
- mDestroyed = true;
- mGl = null;
- } finally {
- if (full && mGlCanvas != null) {
- mGlCanvas = null;
- }
- }
- }
-
- @Override
- void pushLayerUpdate(HardwareLayer layer) {
- mGlCanvas.pushLayerUpdate(layer);
- }
-
- @Override
- void flushLayerUpdates() {
- if (validate()) {
- flushLayerChanges();
- mGlCanvas.flushLayerUpdates();
- }
- }
-
- @Override
- HardwareLayer createTextureLayer() {
- validate();
- return HardwareLayer.createTextureLayer(this);
- }
-
- @Override
- public HardwareLayer createDisplayListLayer(int width, int height) {
- validate();
- return HardwareLayer.createDisplayListLayer(this, width, height);
- }
-
- @Override
- void onLayerCreated(HardwareLayer hardwareLayer) {
- mAttachedLayers.add(hardwareLayer);
- }
-
- boolean hasContext() {
- return sEgl != null && mEglContext != null
- && mEglContext.equals(sEgl.eglGetCurrentContext());
- }
-
- @Override
- void onLayerDestroyed(HardwareLayer layer) {
- if (mGlCanvas != null) {
- mGlCanvas.cancelLayerUpdate(layer);
- }
- if (hasContext()) {
- long backingLayer = layer.detachBackingLayer();
- nDestroyLayer(backingLayer);
- }
- mAttachedLayers.remove(layer);
- }
-
- @Override
- public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
- return layer.createSurfaceTexture();
- }
-
- @Override
- boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) {
- if (!validate()) {
- throw new IllegalStateException("Could not acquire hardware rendering context");
- }
- layer.flushChanges();
- return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap);
- }
-
- @Override
- boolean safelyRun(Runnable action) {
- boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
-
- if (needsContext) {
- GLRendererEglContext managedContext =
- (GLRendererEglContext) sEglContextStorage.get();
- if (managedContext == null) return false;
- usePbufferSurface(managedContext.getContext());
- }
-
- try {
- action.run();
- } finally {
- if (needsContext) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
-
- return true;
- }
-
- @Override
- void invokeFunctor(long functor, boolean waitForCompletion) {
- boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
- boolean hasContext = !needsContext;
-
- if (needsContext) {
- GLRendererEglContext managedContext =
- (GLRendererEglContext) sEglContextStorage.get();
- if (managedContext != null) {
- usePbufferSurface(managedContext.getContext());
- hasContext = true;
- }
- }
-
- try {
- nInvokeFunctor(functor, hasContext);
- } finally {
- if (needsContext) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
- }
-
- private static native void nInvokeFunctor(long functor, boolean hasContext);
-
- @Override
- void destroyHardwareResources(final View view) {
- if (view != null) {
- safelyRun(new Runnable() {
- @Override
- public void run() {
- if (mCanvas != null) {
- mCanvas.clearLayerUpdates();
- }
- destroyResources(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
- }
- });
- }
- }
-
- private static void destroyResources(View view) {
- view.destroyHardwareResources();
-
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
-
- int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- destroyResources(group.getChildAt(i));
- }
- }
- }
-
- static void startTrimMemory(int level) {
- if (sEgl == null || sEglConfig == null) return;
-
- GLRendererEglContext managedContext =
- (GLRendererEglContext) sEglContextStorage.get();
- // We do not have OpenGL objects
- if (managedContext == null) {
- return;
- } else {
- usePbufferSurface(managedContext.getContext());
- }
-
- if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);
- } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
- }
- }
-
- static void endTrimMemory() {
- if (sEgl != null && sEglDisplay != null) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
-
- private static void usePbufferSurface(EGLContext eglContext) {
- synchronized (sPbufferLock) {
- // Create a temporary 1x1 pbuffer so we have a context
- // to clear our OpenGL objects
- if (sPbuffer == null) {
- sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
- EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
- });
- }
- }
- sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
- }
-
- GLRenderer(boolean translucent) {
- mTranslucent = translucent;
-
- loadSystemProperties();
- }
-
- @Override
- void setOpaque(boolean opaque) {
- // Not supported
- }
-
- @Override
- boolean loadSystemProperties() {
- boolean value;
- boolean changed = false;
-
- String profiling = SystemProperties.get(PROFILE_PROPERTY);
- int graphType = search(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;
- }
- }
-
- // If on-screen profiling is not enabled, we need to check whether
- // console profiling only is enabled
- if (!value) {
- value = Boolean.parseBoolean(profiling);
- }
-
- 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;
- }
-
- mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
- }
-
- value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false);
- if (value != mDebugDirtyRegions) {
- changed = true;
- mDebugDirtyRegions = value;
-
- if (mDebugDirtyRegions) {
- Log.d(LOG_TAG, "Debugging dirty regions");
- }
- }
-
- String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY);
- int debugOverdraw = search(OVERDRAW, overdraw);
- if (debugOverdraw != mDebugOverdraw) {
- changed = true;
- mDebugOverdraw = debugOverdraw;
- }
-
- if (loadProperties()) {
- changed = true;
- }
-
- return changed;
- }
-
- private static int search(String[] values, String value) {
- for (int i = 0; i < values.length; i++) {
- if (values[i].equals(value)) return i;
- }
- return -1;
- }
-
- @Override
- void dumpGfxInfo(PrintWriter pw) {
- if (mProfileEnabled) {
- pw.printf("\n\tDraw\tProcess\tExecute\n");
-
- mProfileLock.lock();
- try {
- for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
- if (mProfileData[i] < 0) {
- break;
- }
- pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1],
- mProfileData[i + 2]);
- mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
- }
- mProfileCurrentFrame = mProfileData.length;
- } finally {
- mProfileLock.unlock();
- }
- }
- }
-
- @Override
- long getFrameCount() {
- return mFrameCount;
- }
-
- /**
- * Indicates whether this renderer instance can track and update dirty regions.
- */
- boolean hasDirtyRegions() {
- return mDirtyRegionsEnabled;
- }
-
- /**
- * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
- * is invoked and the requested flag is turned off. The error code is
- * also logged as a warning.
- */
- void checkEglErrors() {
- if (isEnabled()) {
- checkEglErrorsForced();
- }
- }
-
- private void checkEglErrorsForced() {
- int error = sEgl.eglGetError();
- if (error != EGL_SUCCESS) {
- // something bad has happened revert to
- // normal rendering.
- Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
- fallback(error != EGL11.EGL_CONTEXT_LOST);
- }
- }
-
- private void fallback(boolean fallback) {
- destroy(true);
- if (fallback) {
- // we'll try again if it was context lost
- setRequested(false);
- Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
- + "Switching back to software rendering.");
- }
- }
-
- @Override
- boolean initialize(Surface surface) throws OutOfResourcesException {
- if (isRequested() && !isEnabled()) {
- boolean contextCreated = initializeEgl();
- mGl = createEglSurface(surface);
- mDestroyed = false;
-
- if (mGl != null) {
- int err = sEgl.eglGetError();
- if (err != EGL_SUCCESS) {
- destroy(true);
- setRequested(false);
- } else {
- if (mCanvas == null) {
- mCanvas = createCanvas();
- }
- setEnabled(true);
-
- if (contextCreated) {
- initAtlas();
- }
- }
-
- return mCanvas != null;
- }
- }
- return false;
- }
-
- @Override
- void updateSurface(Surface surface) throws OutOfResourcesException {
- if (isRequested() && isEnabled()) {
- createEglSurface(surface);
- }
- }
-
- @Override
- void pauseSurface(Surface surface) {
- // No-op
- }
-
- boolean initializeEgl() {
- synchronized (sEglLock) {
- if (sEgl == null && sEglConfig == null) {
- sEgl = (EGL10) EGLContext.getEGL();
-
- // Get to the default display.
- sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
-
- if (sEglDisplay == EGL_NO_DISPLAY) {
- throw new RuntimeException("eglGetDisplay failed "
- + GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- // We can now initialize EGL for that display
- int[] version = new int[2];
- if (!sEgl.eglInitialize(sEglDisplay, version)) {
- throw new RuntimeException("eglInitialize failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- checkEglErrorsForced();
-
- sEglConfig = loadEglConfig();
- }
- }
-
- ManagedEGLContext managedContext = sEglContextStorage.get();
- mEglContext = managedContext != null ? managedContext.getContext() : null;
- mEglThread = Thread.currentThread();
-
- if (mEglContext == null) {
- mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
- sEglContextStorage.set(createManagedContext(mEglContext));
- return true;
- }
-
- return false;
- }
-
- 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;
- }
-
- private EGLConfig chooseEglConfig() {
- EGLConfig[] configs = new EGLConfig[1];
- int[] configsCount = new int[1];
- int[] configSpec = getConfig(sDirtyRegions);
-
- // Debug
- final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, "");
- if ("all".equalsIgnoreCase(debug)) {
- sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount);
-
- EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]];
- sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs,
- configsCount[0], configsCount);
-
- for (EGLConfig config : debugConfigs) {
- printConfig(config);
- }
- }
-
- if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
- throw new IllegalArgumentException("eglChooseConfig failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- } else if (configsCount[0] > 0) {
- if ("choice".equalsIgnoreCase(debug)) {
- printConfig(configs[0]);
- }
- return configs[0];
- }
-
- return null;
- }
-
- private static void printConfig(EGLConfig config) {
- int[] value = new int[1];
-
- Log.d(LOG_TAG, "EGL configuration " + config + ":");
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value);
- Log.d(LOG_TAG, " RED_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value);
- Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value);
- Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value);
- Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value);
- Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value);
- Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value);
- Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value);
- Log.d(LOG_TAG, " SAMPLES = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value);
- Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value);
- Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0]));
- }
-
- GL createEglSurface(Surface surface) throws OutOfResourcesException {
- // Check preconditions.
- if (sEgl == null) {
- throw new RuntimeException("egl not initialized");
- }
- if (sEglDisplay == null) {
- throw new RuntimeException("eglDisplay not initialized");
- }
- if (sEglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
- if (Thread.currentThread() != mEglThread) {
- throw new IllegalStateException("HardwareRenderer cannot be used "
- + "from multiple threads");
- }
-
- // In case we need to destroy an existing surface
- destroySurface();
-
- // Create an EGL surface we can render into.
- if (!createSurface(surface)) {
- return null;
- }
-
- initCaches();
-
- return mEglContext.getGL();
- }
-
- private void enableDirtyRegions() {
- // If mDirtyRegions is set, this means we have an EGL configuration
- // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
- if (sDirtyRegions) {
- if (!(mDirtyRegionsEnabled = preserveBackBuffer())) {
- Log.w(LOG_TAG, "Backbuffer cannot be preserved");
- }
- } else if (sDirtyRegionsRequested) {
- // If mDirtyRegions is not set, our EGL configuration does not
- // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
- // swap behavior might be EGL_BUFFER_PRESERVED, which means we
- // want to set mDirtyRegions. We try to do this only if dirty
- // regions were initially requested as part of the device
- // configuration (see RENDER_DIRTY_REGIONS)
- mDirtyRegionsEnabled = isBackBufferPreserved();
- }
- }
-
- EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE };
-
- EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
- attribs);
- if (context == null || context == EGL_NO_CONTEXT) {
- //noinspection ConstantConditions
- throw new IllegalStateException(
- "Could not create an EGL context. eglCreateContext failed with error: " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- return context;
- }
-
- void destroySurface() {
- if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
- if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) {
- sEgl.eglMakeCurrent(sEglDisplay,
- EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
- mEglSurface = null;
- }
- }
-
- @Override
- void invalidate(Surface surface) {
- // Cancels any existing buffer to ensure we'll get a buffer
- // of the right size before we call eglSwapBuffers
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
- if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
- sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
- mEglSurface = null;
- setEnabled(false);
- }
-
- if (surface.isValid()) {
- if (!createSurface(surface)) {
- return;
- }
-
- mUpdateDirtyRegions = true;
-
- if (mCanvas != null) {
- setEnabled(true);
- }
- }
- }
-
- private boolean createSurface(Surface surface) {
- mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null);
-
- if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
- int error = sEgl.eglGetError();
- if (error == EGL_BAD_NATIVE_WINDOW) {
- Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
- return false;
- }
- throw new RuntimeException("createWindowSurface failed "
- + GLUtils.getEGLErrorString(error));
- }
-
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- throw new IllegalStateException("eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- enableDirtyRegions();
-
- return true;
- }
-
- boolean validate() {
- return checkRenderContext() != SURFACE_STATE_ERROR;
- }
-
- @Override
- void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) {
- if (validate()) {
- mCanvas.setViewport(width, height);
- mCanvas.initializeLight(lightX, lightY, lightZ, lightRadius);
- mWidth = width;
- mHeight = height;
- }
- }
-
- @Override
- int getWidth() {
- return mWidth;
- }
-
- @Override
- int getHeight() {
- return mHeight;
- }
-
- @Override
- void setName(String name) {
- mName = name;
- }
-
- @Override
- void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
- Rect dirty) {
- if (canDraw()) {
- if (!hasDirtyRegions()) {
- dirty = null;
- }
- attachInfo.mIgnoreDirtyState = true;
- attachInfo.mDrawingTime = SystemClock.uptimeMillis();
-
- view.mPrivateFlags |= View.PFLAG_DRAWN;
-
- // We are already on the correct thread
- final int surfaceState = checkRenderContextUnsafe();
- if (surfaceState != SURFACE_STATE_ERROR) {
- HardwareCanvas canvas = mCanvas;
-
- if (mProfileEnabled) {
- mProfileLock.lock();
- }
-
- dirty = beginFrame(canvas, dirty, surfaceState);
-
- RenderNode displayList = buildDisplayList(view, canvas);
-
- flushLayerChanges();
-
- // buildDisplayList() calls into user code which can cause
- // an eglMakeCurrent to happen with a different surface/context.
- // We must therefore check again here.
- if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) {
- return;
- }
-
- int saveCount = 0;
- int status = RenderNode.STATUS_DONE;
-
- long start = getSystemTime();
- try {
- status = prepareFrame(dirty);
-
- saveCount = canvas.save();
- callbacks.onHardwarePreDraw(canvas);
-
- if (displayList != null) {
- status |= drawDisplayList(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);
- view.mRecreateDisplayList = false;
-
- mDrawDelta = getSystemTime() - start;
-
- if (mDrawDelta > 0) {
- mFrameCount++;
-
- debugDirtyRegions(dirty, canvas);
- drawProfileData(attachInfo);
- }
- }
-
- onPostDraw();
-
- swapBuffers(status);
-
- if (mProfileEnabled) {
- mProfileLock.unlock();
- }
-
- attachInfo.mIgnoreDirtyState = false;
- }
- }
- }
-
- private void flushLayerChanges() {
- // Loop through and apply any pending layer changes
- for (int i = 0; i < mAttachedLayers.size(); i++) {
- HardwareLayer layer = mAttachedLayers.get(i);
- layer.flushChanges();
- if (!layer.isValid()) {
- // The layer was removed from mAttachedLayers, rewind i by 1
- // Note that this shouldn't actually happen as View.getHardwareLayer()
- // is already flushing for error checking reasons
- i--;
- }
- }
- }
-
- @Override
- void fence() {
- // Everything is immediate, so this is a no-op
- }
-
- private RenderNode 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");
- RenderNode renderNode = view.getDisplayList();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-
- endBuildDisplayListProfiling(buildDisplayListStartTime);
-
- return renderNode;
- }
-
- 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(HardwareCanvas canvas, RenderNode displayList,
- int status) {
-
- long drawDisplayListStartTime = 0;
- if (mProfileEnabled) {
- drawDisplayListStartTime = System.nanoTime();
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
- nPrepareTree(displayList.getNativeDisplayList());
- try {
- status |= canvas.drawDisplayList(displayList, mRedrawClip,
- RenderNode.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;
- }
-
- return status;
- }
-
- private void swapBuffers(int status) {
- if ((status & RenderNode.STATUS_DREW) == RenderNode.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);
- }
- }
- }
-
- /**
- * Ensures the current EGL context and surface are the ones we expect.
- * This method throws an IllegalStateException if invoked from a thread
- * that did not initialize EGL.
- *
- * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
- * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
- * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
- *
- * @see #checkRenderContextUnsafe()
- */
- int checkRenderContext() {
- if (mEglThread != Thread.currentThread()) {
- throw new IllegalStateException("Hardware acceleration can only be used with a " +
- "single UI thread.\nOriginal thread: " + mEglThread + "\n" +
- "Current thread: " + Thread.currentThread());
- }
-
- return checkRenderContextUnsafe();
- }
-
- /**
- * Ensures the current EGL context and surface are the ones we expect.
- * This method does not check the current thread.
- *
- * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
- * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
- * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
- *
- * @see #checkRenderContext()
- */
- private int checkRenderContextUnsafe() {
- if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) ||
- !mEglContext.equals(sEgl.eglGetCurrentContext())) {
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- Log.e(LOG_TAG, "eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- fallback(true);
- return SURFACE_STATE_ERROR;
- } else {
- if (mUpdateDirtyRegions) {
- enableDirtyRegions();
- mUpdateDirtyRegions = false;
- }
- return SURFACE_STATE_UPDATED;
- }
- }
- return SURFACE_STATE_SUCCESS;
- }
-
- private static int dpToPx(int dp, float density) {
- return (int) (dp * density + 0.5f);
- }
-
- static native boolean loadProperties();
-
- static native void setupShadersDiskCache(String cacheFile);
-
- /**
- * Notifies EGL that the frame is about to be rendered.
- * @param size
- */
- static native void beginFrame(int[] size);
-
- /**
- * Returns the current system time according to the renderer.
- * This method is used for debugging only and should not be used
- * as a clock.
- */
- static native long getSystemTime();
-
- /**
- * Preserves the back buffer of the current surface after a buffer swap.
- * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current
- * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL
- * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
- *
- * @return True if the swap behavior was successfully changed,
- * false otherwise.
- */
- static native boolean preserveBackBuffer();
-
- /**
- * Indicates whether the current surface preserves its back buffer
- * after a buffer swap.
- *
- * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED,
- * false otherwise
- */
- static native boolean isBackBufferPreserved();
-
- static native void nDestroyLayer(long layerPtr);
-
- private static native void nPrepareTree(long displayListPtr);
-
- 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);
- }
- }
-}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 9568760..b8e7d8c 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -110,48 +110,6 @@ public abstract class HardwareCanvas extends Canvas {
return RenderNode.STATUS_DONE;
}
- /**
- * Indicates that the specified layer must be updated as soon as possible.
- *
- * @param layer The layer to update
- *
- * @see #clearLayerUpdates()
- *
- * @hide
- */
- abstract void pushLayerUpdate(HardwareLayer layer);
-
- /**
- * Cancels a queued layer update. If the specified layer was not
- * queued for update, this method has no effect.
- *
- * @param layer The layer whose update to cancel
- *
- * @see #pushLayerUpdate(HardwareLayer)
- * @see #clearLayerUpdates()
- *
- * @hide
- */
- abstract void cancelLayerUpdate(HardwareLayer layer);
-
- /**
- * Immediately executes all enqueued layer updates.
- *
- * @see #pushLayerUpdate(HardwareLayer)
- *
- * @hide
- */
- abstract void flushLayerUpdates();
-
- /**
- * Removes all enqueued layer updates.
- *
- * @see #pushLayerUpdate(HardwareLayer)
- *
- * @hide
- */
- abstract void clearLayerUpdates();
-
public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 4d78733..6acb134 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -22,6 +22,8 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import com.android.internal.util.VirtualRefBasePtr;
+
/**
* A hardware layer can be used to render graphics operations into a hardware
* friendly buffer. For instance, with an OpenGL backend a hardware layer
@@ -36,7 +38,7 @@ final class HardwareLayer {
private static final int LAYER_TYPE_DISPLAY_LIST = 2;
private HardwareRenderer mRenderer;
- private Finalizer mFinalizer;
+ private VirtualRefBasePtr mFinalizer;
private RenderNode mDisplayList;
private final int mLayerType;
@@ -47,10 +49,7 @@ final class HardwareLayer {
}
mRenderer = renderer;
mLayerType = type;
- mFinalizer = new Finalizer(deferredUpdater);
-
- // Layer is considered initialized at this point, notify the HardwareRenderer
- mRenderer.onLayerCreated(this);
+ mFinalizer = new VirtualRefBasePtr(deferredUpdater);
}
private void assertType(int type) {
@@ -59,6 +58,10 @@ final class HardwareLayer {
}
}
+ boolean hasDisplayList() {
+ return mDisplayList != null;
+ }
+
/**
* Update the paint used when drawing this layer.
*
@@ -66,7 +69,8 @@ final class HardwareLayer {
* @see View#setLayerPaint(android.graphics.Paint)
*/
public void setLayerPaint(Paint paint) {
- nSetLayerPaint(mFinalizer.mDeferredUpdater, paint.mNativePaint);
+ nSetLayerPaint(mFinalizer.get(), paint.mNativePaint);
+ mRenderer.pushLayerUpdate(this);
}
/**
@@ -75,7 +79,7 @@ final class HardwareLayer {
* @return True if the layer can be rendered into, false otherwise
*/
public boolean isValid() {
- return mFinalizer != null && mFinalizer.mDeferredUpdater != 0;
+ return mFinalizer != null && mFinalizer.get() != 0;
}
/**
@@ -91,35 +95,14 @@ final class HardwareLayer {
mDisplayList.destroyDisplayListData();
mDisplayList = null;
}
- if (mRenderer != null) {
- mRenderer.onLayerDestroyed(this);
- mRenderer = null;
- }
- doDestroyLayerUpdater();
+ mRenderer.onLayerDestroyed(this);
+ mRenderer = null;
+ mFinalizer.release();
+ mFinalizer = null;
}
public long getDeferredLayerUpdater() {
- return mFinalizer.mDeferredUpdater;
- }
-
- /**
- * Destroys the deferred layer updater but not the backing layer. The
- * backing layer is instead returned and is the caller's responsibility
- * to destroy/recycle as appropriate.
- *
- * It is safe to call this in onLayerDestroyed only
- */
- public long detachBackingLayer() {
- long backingLayer = nDetachBackingLayer(mFinalizer.mDeferredUpdater);
- doDestroyLayerUpdater();
- return backingLayer;
- }
-
- private void doDestroyLayerUpdater() {
- if (mFinalizer != null) {
- mFinalizer.destroy();
- mFinalizer = null;
- }
+ return mFinalizer.get();
}
public RenderNode startRecording() {
@@ -132,7 +115,7 @@ final class HardwareLayer {
}
public void endRecording(Rect dirtyRect) {
- nUpdateRenderLayer(mFinalizer.mDeferredUpdater, mDisplayList.getNativeDisplayList(),
+ nUpdateRenderLayer(mFinalizer.get(), mDisplayList.getNativeDisplayList(),
dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom);
mRenderer.pushLayerUpdate(this);
}
@@ -160,7 +143,7 @@ final class HardwareLayer {
* match the desired values.
*/
public boolean prepare(int width, int height, boolean isOpaque) {
- return nPrepare(mFinalizer.mDeferredUpdater, width, height, isOpaque);
+ return nPrepare(mFinalizer.get(), width, height, isOpaque);
}
/**
@@ -169,7 +152,8 @@ final class HardwareLayer {
* @param matrix The transform to apply to the layer.
*/
public void setTransform(Matrix matrix) {
- nSetTransform(mFinalizer.mDeferredUpdater, matrix.native_instance);
+ nSetTransform(mFinalizer.get(), matrix.native_instance);
+ mRenderer.pushLayerUpdate(this);
}
/**
@@ -183,41 +167,25 @@ final class HardwareLayer {
surface.detachFromGLContext();
// SurfaceTexture owns the texture name and detachFromGLContext
// should have deleted it
- nOnTextureDestroyed(mFinalizer.mDeferredUpdater);
+ nOnTextureDestroyed(mFinalizer.get());
}
});
}
- /**
- * This exists to minimize impact into the current HardwareLayer paths as
- * some of the specifics of how to handle error cases in the fully
- * deferred model will work
- */
- @Deprecated
- public void flushChanges() {
- if (HardwareRenderer.sUseRenderThread) {
- // Not supported, don't try.
- return;
- }
-
- boolean success = nFlushChanges(mFinalizer.mDeferredUpdater);
- if (!success) {
- destroy();
- }
- }
-
public long getLayer() {
- return nGetLayer(mFinalizer.mDeferredUpdater);
+ return nGetLayer(mFinalizer.get());
}
public void setSurfaceTexture(SurfaceTexture surface) {
assertType(LAYER_TYPE_TEXTURE);
- nSetSurfaceTexture(mFinalizer.mDeferredUpdater, surface, false);
+ nSetSurfaceTexture(mFinalizer.get(), surface, false);
+ mRenderer.pushLayerUpdate(this);
}
public void updateSurfaceTexture() {
assertType(LAYER_TYPE_TEXTURE);
- nUpdateSurfaceTexture(mFinalizer.mDeferredUpdater);
+ nUpdateSurfaceTexture(mFinalizer.get());
+ mRenderer.pushLayerUpdate(this);
}
/**
@@ -225,48 +193,20 @@ final class HardwareLayer {
*/
SurfaceTexture createSurfaceTexture() {
assertType(LAYER_TYPE_TEXTURE);
- SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.mDeferredUpdater));
- nSetSurfaceTexture(mFinalizer.mDeferredUpdater, st, true);
+ SurfaceTexture st = new SurfaceTexture(nGetTexName(mFinalizer.get()));
+ nSetSurfaceTexture(mFinalizer.get(), st, true);
return st;
}
- /**
- * This should only be used by HardwareRenderer! Do not call directly
- */
- static HardwareLayer createTextureLayer(HardwareRenderer renderer) {
- return new HardwareLayer(renderer, nCreateTextureLayer(), LAYER_TYPE_TEXTURE);
- }
-
static HardwareLayer adoptTextureLayer(HardwareRenderer renderer, long layer) {
return new HardwareLayer(renderer, layer, LAYER_TYPE_TEXTURE);
}
- /**
- * This should only be used by HardwareRenderer! Do not call directly
- */
- static HardwareLayer createDisplayListLayer(HardwareRenderer renderer,
- int width, int height) {
- return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST);
- }
-
static HardwareLayer adoptDisplayListLayer(HardwareRenderer renderer, long layer) {
return new HardwareLayer(renderer, layer, LAYER_TYPE_DISPLAY_LIST);
}
- /** This also creates the underlying layer */
- private static native long nCreateTextureLayer();
- private static native long nCreateRenderLayer(int width, int height);
-
private static native void nOnTextureDestroyed(long layerUpdater);
- private static native long nDetachBackingLayer(long layerUpdater);
-
- /** This also destroys the underlying layer if it is still attached.
- * Note it does not recycle the underlying layer, but instead queues it
- * for deferred deletion.
- * The HardwareRenderer should use detachBackingLayer() in the
- * onLayerDestroyed() callback to do recycling if desired.
- */
- private static native void nDestroyLayerUpdater(long layerUpdater);
private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque);
private static native void nSetLayerPaint(long layerUpdater, long paint);
@@ -281,28 +221,4 @@ final class HardwareLayer {
private static native long nGetLayer(long layerUpdater);
private static native int nGetTexName(long layerUpdater);
-
- private static class Finalizer {
- private long mDeferredUpdater;
-
- public Finalizer(long deferredUpdater) {
- mDeferredUpdater = deferredUpdater;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- destroy();
- } finally {
- super.finalize();
- }
- }
-
- void destroy() {
- if (mDeferredUpdater != 0) {
- nDestroyLayerUpdater(mDeferredUpdater);
- mDeferredUpdater = 0;
- }
- }
- }
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 3c4d83f..d67c974 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,13 +17,13 @@
package android.view;
import android.graphics.Bitmap;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.util.DisplayMetrics;
import android.view.Surface.OutOfResourcesException;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
@@ -61,11 +61,9 @@ 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
*/
@@ -80,14 +78,6 @@ public abstract class HardwareRenderer {
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.
@@ -181,9 +171,6 @@ public abstract class HardwareRenderer {
*/
public static boolean sSystemRendererDisabled = false;
- /** @hide */
- public static boolean sUseRenderThread = true;
-
private boolean mEnabled;
private boolean mRequested = true;
@@ -298,16 +285,8 @@ public abstract class HardwareRenderer {
/**
* Outputs extra debugging information in the specified file descriptor.
- * @param pw
*/
- abstract void dumpGfxInfo(PrintWriter pw);
-
- /**
- * Outputs the total number of frames rendered (used for fps calculations)
- *
- * @return the number of frames rendered
- */
- abstract long getFrameCount();
+ abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd);
/**
* Loads system properties used by the renderer. This method is invoked
@@ -327,7 +306,7 @@ public abstract class HardwareRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
- GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
}
/**
@@ -341,12 +320,6 @@ public abstract class HardwareRenderer {
abstract void pushLayerUpdate(HardwareLayer layer);
/**
- * Tells the HardwareRenderer that a layer was created. The renderer should
- * make sure to apply any pending layer changes at the start of a new frame
- */
- abstract void onLayerCreated(HardwareLayer hardwareLayer);
-
- /**
* Tells the HardwareRenderer that the layer is destroyed. The renderer
* should remove the layer from any update queues.
*/
@@ -493,11 +466,7 @@ public abstract class HardwareRenderer {
static HardwareRenderer create(boolean translucent) {
HardwareRenderer renderer = null;
if (GLES20Canvas.isAvailable()) {
- if (sUseRenderThread) {
- renderer = new ThreadedRenderer(translucent);
- } else {
- renderer = new GLRenderer(translucent);
- }
+ renderer = new ThreadedRenderer(translucent);
}
return renderer;
}
@@ -524,7 +493,7 @@ public abstract class HardwareRenderer {
* see {@link android.content.ComponentCallbacks}
*/
static void startTrimMemory(int level) {
- GLRenderer.startTrimMemory(level);
+ ThreadedRenderer.startTrimMemory(level);
}
/**
@@ -532,7 +501,7 @@ public abstract class HardwareRenderer {
* cleanup special resources used by the memory trimming process.
*/
static void endTrimMemory() {
- GLRenderer.endTrimMemory();
+ ThreadedRenderer.endTrimMemory();
}
/**
@@ -583,98 +552,4 @@ public abstract class HardwareRenderer {
*/
public void notifyFramePending() {
}
-
- /**
- * 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);
- }
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7d13399..af16185 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -79,7 +79,7 @@ interface IWindowManager
void removeWindowToken(IBinder token);
void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
- int configChanges);
+ int configChanges, boolean voiceInteraction);
void setAppGroupId(IBinder token, int groupId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
@@ -120,6 +120,7 @@ interface IWindowManager
boolean isKeyguardSecure();
boolean inKeyguardRestrictedInputMode();
void dismissKeyguard();
+ void keyguardGoingAway();
void closeSystemDialogs(String reason);
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index cf125bc..e63829e 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -850,6 +850,13 @@ public class RenderNode {
nOutput(mNativeRenderNode);
}
+ /**
+ * Gets the size of the DisplayList for debug purposes.
+ */
+ public int getDebugSize() {
+ return nGetDebugSize(mNativeRenderNode);
+ }
+
///////////////////////////////////////////////////////////////////////////
// Animations
///////////////////////////////////////////////////////////////////////////
@@ -941,6 +948,7 @@ public class RenderNode {
private static native float nGetPivotX(long renderNode);
private static native float nGetPivotY(long renderNode);
private static native void nOutput(long renderNode);
+ private static native int nGetDebugSize(long renderNode);
///////////////////////////////////////////////////////////////////////////
// Animations
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index e918119..4979059 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -219,6 +219,15 @@ public final class RenderNodeAnimator extends Animator {
return mTarget;
}
+ /**
+ * WARNING: May only be called once!!!
+ * TODO: Fix above -_-
+ */
+ public void setStartValue(float startValue) {
+ checkMutable();
+ nSetStartValue(mNativePtr.get(), startValue);
+ }
+
@Override
public void setStartDelay(long startDelay) {
checkMutable();
@@ -282,11 +291,12 @@ public final class RenderNodeAnimator extends Animator {
}
private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis,
- int property, float deltaValue);
+ int property, float finalValue);
private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis,
- long canvasProperty, float deltaValue);
+ long canvasProperty, float finalValue);
private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
- long canvasProperty, int paintField, float deltaValue);
+ long canvasProperty, int paintField, float finalValue);
+ private static native void nSetStartValue(long nativePtr, float startValue);
private static native void nSetDuration(long nativePtr, long duration);
private static native long nGetDuration(long nativePtr);
private static native void nSetStartDelay(long nativePtr, long startDelay);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c15ce44..5cd3d62 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -38,11 +38,11 @@ public class SurfaceControl {
private static native void nativeDestroy(long nativeObject);
private static native Bitmap nativeScreenshot(IBinder displayToken,
- int width, int height, int minLayer, int maxLayer, boolean allLayers,
- boolean useIdentityTransform);
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform);
private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
- int width, int height, int minLayer, int maxLayer, boolean allLayers,
- boolean useIdentityTransform);
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform);
private static native void nativeOpenTransaction();
private static native void nativeCloseTransaction();
@@ -597,8 +597,8 @@ public class SurfaceControl {
public static void screenshot(IBinder display, Surface consumer,
int width, int height, int minLayer, int maxLayer,
boolean useIdentityTransform) {
- screenshot(display, consumer, width, height, minLayer, maxLayer, false,
- useIdentityTransform);
+ screenshot(display, consumer, new Rect(), width, height, minLayer, maxLayer,
+ false, useIdentityTransform);
}
/**
@@ -613,7 +613,7 @@ public class SurfaceControl {
*/
public static void screenshot(IBinder display, Surface consumer,
int width, int height) {
- screenshot(display, consumer, width, height, 0, 0, true, false);
+ screenshot(display, consumer, new Rect(), width, height, 0, 0, true, false);
}
/**
@@ -623,7 +623,7 @@ public class SurfaceControl {
* @param consumer The {@link Surface} to take the screenshot into.
*/
public static void screenshot(IBinder display, Surface consumer) {
- screenshot(display, consumer, 0, 0, 0, 0, true, false);
+ screenshot(display, consumer, new Rect(), 0, 0, 0, 0, true, false);
}
/**
@@ -634,6 +634,8 @@ public class SurfaceControl {
* the versions that use a {@link Surface} instead, such as
* {@link SurfaceControl#screenshot(IBinder, Surface)}.
*
+ * @param sourceCrop The portion of the screen to capture into the Bitmap;
+ * caller may pass in 'new Rect()' if no cropping is desired.
* @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
@@ -649,13 +651,13 @@ public class SurfaceControl {
* if an error occurs. Make sure to call Bitmap.recycle() as soon as
* possible, once its content is not needed anymore.
*/
- public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer,
- boolean useIdentityTransform) {
+ public static Bitmap screenshot(Rect sourceCrop, int width, int height,
+ int minLayer, int maxLayer, boolean useIdentityTransform) {
// 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,
- useIdentityTransform);
+ return nativeScreenshot(displayToken, sourceCrop, width, height,
+ minLayer, maxLayer, false, useIdentityTransform);
}
/**
@@ -674,10 +676,10 @@ public class SurfaceControl {
// 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, false);
+ return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, false);
}
- private static void screenshot(IBinder display, Surface consumer,
+ private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop,
int width, int height, int minLayer, int maxLayer, boolean allLayers,
boolean useIdentityTransform) {
if (display == null) {
@@ -686,7 +688,7 @@ public class SurfaceControl {
if (consumer == null) {
throw new IllegalArgumentException("consumer must not be null");
}
- nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers,
- useIdentityTransform);
+ nativeScreenshot(display, consumer, sourceCrop, width, height,
+ minLayer, maxLayer, allLayers, useIdentityTransform);
}
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 1765c43..2a9f7d5 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -405,7 +405,9 @@ public class TextureView extends View {
// To cancel updates, the easiest thing to do is simply to remove the
// updates listener
if (visibility == VISIBLE) {
- mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ if (mLayer != null) {
+ mSurface.setOnFrameAvailableListener(mUpdateListener, mAttachInfo.mHandler);
+ }
updateLayerAndInvalidate();
} else {
mSurface.setOnFrameAvailableListener(null);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 8417887..9b3ef7f 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -22,19 +22,19 @@ import android.graphics.SurfaceTexture;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.Trace;
import android.util.Log;
import android.util.TimeUtils;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Hardware renderer that proxies the rendering to a render thread. Most calls
* are currently synchronous.
- * TODO: Make draw() async.
- * TODO: Figure out how to share the DisplayList between two threads (global lock?)
*
* The UI thread can block on the RenderThread, but RenderThread must never
* block on the UI thread.
@@ -62,11 +62,16 @@ public class ThreadedRenderer extends HardwareRenderer {
// Needs a ViewRoot invalidate
private static final int SYNC_INVALIDATE_REQUIRED = 0x1;
+ private static final String[] VISUALIZERS = {
+ PROFILE_PROPERTY_VISUALIZE_BARS,
+ };
+
private int mWidth, mHeight;
private long mNativeProxy;
private boolean mInitialized = false;
private RenderNode mRootNode;
private Choreographer mChoreographer;
+ private boolean mProfilingEnabled;
ThreadedRenderer(boolean translucent) {
AtlasInitializer.sInstance.init();
@@ -79,6 +84,8 @@ public class ThreadedRenderer extends HardwareRenderer {
// Setup timing
mChoreographer = Choreographer.getInstance();
nSetFrameInterval(mNativeProxy, mChoreographer.getFrameIntervalNanos());
+
+ loadSystemProperties();
}
@Override
@@ -117,7 +124,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void destroyHardwareResources(View view) {
destroyResources(view);
- // TODO: GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ nFlushCaches(mNativeProxy, GLES20Canvas.FLUSH_CACHES_LAYERS);
}
private static void destroyResources(View view) {
@@ -168,19 +175,33 @@ public class ThreadedRenderer extends HardwareRenderer {
}
@Override
- void dumpGfxInfo(PrintWriter pw) {
- // TODO Auto-generated method stub
+ void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
+ pw.flush();
+ nDumpProfileInfo(mNativeProxy, fd);
}
- @Override
- long getFrameCount() {
- // TODO Auto-generated method stub
- return 0;
+ private static int search(String[] values, String value) {
+ for (int i = 0; i < values.length; i++) {
+ if (values[i].equals(value)) return i;
+ }
+ return -1;
+ }
+
+ private static boolean checkIfProfilingRequested() {
+ String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY);
+ int graphType = search(VISUALIZERS, profiling);
+ return (graphType >= 0) || Boolean.parseBoolean(profiling);
}
@Override
boolean loadSystemProperties() {
- return nLoadSystemProperties(mNativeProxy);
+ boolean changed = nLoadSystemProperties(mNativeProxy);
+ boolean wantProfiling = checkIfProfilingRequested();
+ if (wantProfiling != mProfilingEnabled) {
+ mProfilingEnabled = wantProfiling;
+ changed = true;
+ }
+ return changed;
}
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
@@ -212,14 +233,24 @@ public class ThreadedRenderer extends HardwareRenderer {
long frameTimeNanos = mChoreographer.getFrameTimeNanos();
attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
+ long recordDuration = 0;
+ if (mProfilingEnabled) {
+ recordDuration = System.nanoTime();
+ }
+
updateRootDisplayList(view, callbacks);
+ if (mProfilingEnabled) {
+ recordDuration = System.nanoTime() - recordDuration;
+ }
+
attachInfo.mIgnoreDirtyState = false;
if (dirty == null) {
dirty = NULL_RECT;
}
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
+ recordDuration, view.getResources().getDisplayMetrics().density,
dirty.left, dirty.top, dirty.right, dirty.bottom);
if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) {
attachInfo.mViewRootImpl.invalidate();
@@ -263,12 +294,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void pushLayerUpdate(HardwareLayer layer) {
- // TODO: Remove this, it's not needed outside of GLRenderer
- }
-
- @Override
- void onLayerCreated(HardwareLayer layer) {
- // TODO: Is this actually useful?
+ nPushLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}
@Override
@@ -278,7 +304,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void onLayerDestroyed(HardwareLayer layer) {
- nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater());
+ nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}
@Override
@@ -305,6 +331,14 @@ public class ThreadedRenderer extends HardwareRenderer {
}
}
+ static void startTrimMemory(int level) {
+ // TODO
+ }
+
+ static void endTrimMemory() {
+ // TODO
+ }
+
private static class AtlasInitializer {
static AtlasInitializer sInstance = new AtlasInitializer();
@@ -341,6 +375,8 @@ public class ThreadedRenderer extends HardwareRenderer {
}
}
+ static native void setupShadersDiskCache(String cacheFile);
+
private static native void nSetAtlas(GraphicBuffer buffer, long[] map);
private static native long nCreateRootRenderNode();
@@ -356,7 +392,8 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native void nSetup(long nativeProxy, int width, int height,
float lightX, float lightY, float lightZ, float lightRadius);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
- private static native int nSyncAndDrawFrame(long nativeProxy, long frameTimeNanos,
+ private static native int nSyncAndDrawFrame(long nativeProxy,
+ long frameTimeNanos, long recordDuration, float density,
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
private static native void nDestroyCanvasAndSurface(long nativeProxy);
@@ -366,8 +403,13 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native long nCreateDisplayListLayer(long nativeProxy, int width, int height);
private static native long nCreateTextureLayer(long nativeProxy);
private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap);
- private static native void nDestroyLayer(long nativeProxy, long layer);
+ private static native void nPushLayerUpdate(long nativeProxy, long layer);
+ private static native void nCancelLayerUpdate(long nativeProxy, long layer);
+
+ private static native void nFlushCaches(long nativeProxy, int flushMode);
private static native void nFence(long nativeProxy);
private static native void nNotifyFramePending(long nativeProxy);
+
+ private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 6396781..622fa8c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10711,8 +10711,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@link Drawable#getOutline(Outline)}. Manually setting the Outline with this method allows
* this behavior to be overridden.
* <p>
- * If the outline is empty or is null, shadows will be cast from the
- * bounds of the View.
+ * If the outline is {@link Outline#isEmpty()} or is <code>null</code>,
+ * shadows will not be cast.
* <p>
* Only outlines that return true from {@link Outline#canClip()} may be used for clipping.
*
@@ -13593,12 +13593,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- // The layer is not valid if the underlying GPU resources cannot be allocated
- mHardwareLayer.flushChanges();
- if (!mHardwareLayer.isValid()) {
- return null;
- }
-
mHardwareLayer.setLayerPaint(mLayerPaint);
RenderNode displayList = mHardwareLayer.startRecording();
updateDisplayListIfDirty(displayList, true);
@@ -16197,6 +16191,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Set this view's optical insets.
+ *
+ * <p>This method should be treated similarly to setMeasuredDimension and not as a general
+ * property. Views that compute their own optical insets should call it as part of measurement.
+ * This method does not request layout. If you are setting optical insets outside of
+ * measure/layout itself you will want to call requestLayout() yourself.
+ * </p>
+ * @hide
+ */
+ public void setOpticalInsets(Insets insets) {
+ mLayoutInsets = insets;
+ }
+
+ /**
* 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;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b821a3e..0f40ee7 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -456,6 +456,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// views during a transition when they otherwise would have become gone/invisible
private ArrayList<View> mVisibilityChangingChildren;
+ // Temporary holder of presorted children, only used for
+ // input/software draw dispatch for correctly Z ordering.
+ private ArrayList<View> mPreSortedChildren;
+
// Indicates how many of this container's child subtrees contain transient state
@ViewDebug.ExportedProperty(category = "layout")
private int mChildCountWithTransientState = 0;
@@ -1499,13 +1503,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float y = event.getY();
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
- final boolean customChildOrder = isChildrenDrawingOrderEnabled();
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
HoverTarget lastHoverTarget = null;
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customChildOrder
- ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = children[childIndex];
+ int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -1572,6 +1578,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
break;
}
}
+ if (preorderedList != null) preorderedList.clear();
}
}
@@ -1778,23 +1785,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Send the event to the child under the pointer.
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
- final View[] children = mChildren;
final float x = event.getX();
final float y = event.getY();
- final boolean customOrder = isChildrenDrawingOrderEnabled();
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
- final View child = children[childIndex];
+ int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
if (dispatchTransformedGenericPointerEvent(event, child)) {
+ if (preorderedList != null) preorderedList.clear();
return true;
}
}
+ if (preorderedList != null) preorderedList.clear();
}
// No child handled the event. Send it to this view group.
@@ -1910,13 +1922,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
-
- final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
- final int childIndex = customOrder ?
- getChildDrawingOrder(childrenCount, i) : i;
- final View child = children[childIndex];
+ final int childIndex = customOrder
+ ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
@@ -1934,7 +1948,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = childIndex;
+ if (preorderedList != null) {
+ // childIndex points into presorted list, find original index
+ for (int j = 0; j < childrenCount; j++) {
+ if (children[childIndex] == mChildren[j]) {
+ mLastTouchDownIndex = j;
+ break;
+ }
+ }
+ } else {
+ mLastTouchDownIndex = childIndex;
+ }
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
@@ -1942,6 +1966,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
break;
}
}
+ if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
@@ -2928,7 +2953,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
@Override
protected void dispatchDraw(Canvas canvas) {
- final int count = mChildrenCount;
+ final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
@@ -2936,15 +2961,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
final boolean buildCache = !isHardwareAccelerated();
- for (int i = 0; i < count; i++) {
+ for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
- attachLayoutAnimationParameters(child, params, i, count);
+ attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
if (cache) {
child.setDrawingCacheEnabled(true);
- if (buildCache) {
+ if (buildCache) {
child.buildDrawingCache(true);
}
}
@@ -2997,21 +3022,22 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
boolean more = false;
final long drawingTime = getDrawingTime();
- if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- } else {
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, i)];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
+
+ // Only use the preordered list if not HW accelerated, since the HW pipeline will do the
+ // draw reordering internally
+ final ArrayList<View> preorderedList = canvas.isHardwareAccelerated()
+ ? null : buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < childrenCount; i++) {
+ int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
+ more |= drawChild(canvas, child, drawingTime);
}
}
+ if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
@@ -3096,6 +3122,47 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return i;
}
+ private boolean hasChildWithZ() {
+ for (int i = 0; i < mChildrenCount; i++) {
+ if (mChildren[i].getZ() != 0) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Populates (and returns) mPreSortedChildren with a pre-ordered list of the View's children,
+ * sorted first by Z, then by child drawing order (if applicable).
+ *
+ * Uses a stable, insertion sort which is commonly O(n) for ViewGroups with very few elevated
+ * children.
+ */
+ private ArrayList<View> buildOrderedChildList() {
+ final int count = mChildrenCount;
+ if (count <= 1 || !hasChildWithZ()) return null;
+
+ if (mPreSortedChildren == null) {
+ mPreSortedChildren = new ArrayList<View>(count);
+ } else {
+ mPreSortedChildren.ensureCapacity(count);
+ }
+
+ final boolean useCustomOrder = isChildrenDrawingOrderEnabled();
+ for (int i = 0; i < mChildrenCount; i++) {
+ // add next child (in child order) to end of list
+ int childIndex = useCustomOrder ? getChildDrawingOrder(mChildrenCount, i) : i;
+ View nextChild = mChildren[childIndex];
+ float currentZ = nextChild.getZ();
+
+ // insert ahead of any Views with greater Z
+ int insertIndex = i;
+ while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
+ insertIndex--;
+ }
+ mPreSortedChildren.add(insertIndex, nextChild);
+ }
+ return mPreSortedChildren;
+ }
+
private void notifyAnimationListener() {
mGroupFlags &= ~FLAG_NOTIFY_ANIMATION_LISTENER;
mGroupFlags |= FLAG_ANIMATION_DONE;
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 3104862..af1de78 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -253,9 +253,10 @@ public class ViewPropertyAnimator {
ViewPropertyAnimator(View view) {
mView = view;
view.ensureTransformationInfo();
- if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
- mRTBackend = new ViewPropertyAnimatorRT(view);
- }
+ // TODO: Disabled because of b/15287046
+ //if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+ // mRTBackend = new ViewPropertyAnimatorRT(view);
+ //}
}
/**
@@ -1142,7 +1143,8 @@ public class ViewPropertyAnimator {
// Shouldn't happen, but just to play it safe
return;
}
- boolean useRenderNodeProperties = mView.mRenderNode != null;
+
+ boolean hardwareAccelerated = mView.isHardwareAccelerated();
// alpha requires slightly different treatment than the other (transform) properties.
// The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
@@ -1150,13 +1152,13 @@ public class ViewPropertyAnimator {
// We track what kinds of properties are set, and how alpha is handled when it is
// set, and perform the invalidation steps appropriately.
boolean alphaHandled = false;
- if (!useRenderNodeProperties) {
+ if (!hardwareAccelerated) {
mView.invalidateParentCaches();
}
float fraction = animation.getAnimatedFraction();
int propertyMask = propertyBundle.mPropertyMask;
if ((propertyMask & TRANSFORM_MASK) != 0) {
- mView.invalidateViewProperty(false, false);
+ mView.invalidateViewProperty(hardwareAccelerated, false);
}
ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
if (valueList != null) {
@@ -1172,7 +1174,7 @@ public class ViewPropertyAnimator {
}
}
if ((propertyMask & TRANSFORM_MASK) != 0) {
- if (!useRenderNodeProperties) {
+ if (!hardwareAccelerated) {
mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c3bf295..f3d1e3c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -716,17 +716,6 @@ public final class ViewRootImpl implements ViewParent,
if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
&& forceHwAccelerated)) {
- if (!HardwareRenderer.sUseRenderThread) {
- // TODO: Delete
- // Don't enable hardware acceleration when we're not on the main thread
- if (!HardwareRenderer.sSystemRendererDisabled &&
- Looper.getMainLooper() != Looper.myLooper()) {
- Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
- + "acceleration outside of the main thread, aborting");
- return;
- }
- }
-
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroy(true);
}
@@ -5351,7 +5340,7 @@ public final class ViewRootImpl implements ViewParent,
RenderNode renderNode = view.mRenderNode;
info[0]++;
if (renderNode != null) {
- info[1] += 0; /* TODO: Memory used by RenderNodes (properties + DisplayLists) */
+ info[1] += renderNode.getDebugSize();
}
if (view instanceof ViewGroup) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 031ad80..4eecc6a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -218,7 +218,8 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_NAVIGATION_BAR_PANEL, to = "TYPE_NAVIGATION_BAR_PANEL"),
@ViewDebug.IntToString(from = TYPE_DISPLAY_OVERLAY, to = "TYPE_DISPLAY_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"),
- @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION")
+ @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION"),
+ @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"),
})
public int type;
@@ -541,6 +542,12 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
/**
+ * Window type: Windows in the voice interaction layer.
+ * @hide
+ */
+ public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 96c0ed2..b4779f4 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -430,7 +430,7 @@ public final class WindowManagerGlobal {
HardwareRenderer renderer =
root.getView().mAttachInfo.mHardwareRenderer;
if (renderer != null) {
- renderer.dumpGfxInfo(pw);
+ renderer.dumpGfxInfo(pw, fd);
}
}
@@ -447,11 +447,6 @@ public final class WindowManagerGlobal {
String name = getWindowName(root);
pw.printf(" %s\n %d views, %.2f kB of display lists",
name, info[0], info[1] / 1024.0f);
- HardwareRenderer renderer =
- root.getView().mAttachInfo.mHardwareRenderer;
- if (renderer != null) {
- pw.printf(", %d frames rendered", renderer.getFrameCount());
- }
pw.printf("\n\n");
viewsCount += info[0];
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 1bb20c9..2b4677c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -274,6 +274,11 @@ public interface WindowManagerPolicy {
public IApplicationToken getAppToken();
/**
+ * Return true if this window is participating in voice interaction.
+ */
+ public boolean isVoiceInteraction();
+
+ /**
* Return true if, at any point, the application token associated with
* this window has actually displayed any windows. This is most useful
* with the "starting up" window to determine if any windows were
@@ -603,8 +608,15 @@ public interface WindowManagerPolicy {
* Return whether the given window should forcibly hide everything
* behind it. Typically returns true for the keyguard.
*/
- public boolean doesForceHide(WindowState win, WindowManager.LayoutParams attrs);
-
+ public boolean doesForceHide(WindowManager.LayoutParams attrs);
+
+
+ /**
+ * Return whether the given window can become one that passes doesForceHide() test.
+ * Typically returns true for the StatusBar.
+ */
+ public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
+
/**
* Determine if a window that is behind one that is force hiding
* (as determined by {@link #doesForceHide}) should actually be hidden.
@@ -613,7 +625,7 @@ public interface WindowManagerPolicy {
* will conflict with what you set.
*/
public boolean canBeForceHidden(WindowState win, WindowManager.LayoutParams attrs);
-
+
/**
* Called when the system would like to show a UI to indicate that an
* application is starting. You can use this to add a
@@ -1144,12 +1156,6 @@ public interface WindowManagerPolicy {
public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
/**
- * Show the recents task list app.
- * @hide
- */
- public void showRecentApps();
-
- /**
* @return The current height of the input method window.
*/
public int getInputMethodWindowVisibleHeightLw();
@@ -1190,4 +1196,12 @@ public interface WindowManagerPolicy {
* @return True if the window is a top level one.
*/
public boolean isTopLevelWindow(int windowType);
+
+ /**
+ * Notifies the keyguard to start fading out.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
+ */
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
}
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 628da3c..84f395a 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -427,8 +427,12 @@ public class SpellCheckerSession {
@Override
public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
- mHandler.sendMessage(
- Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.sendMessage(Message.obtain(mHandler,
+ MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+ }
+ }
}
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 7c32c5b..d14c19b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1460,4 +1460,36 @@ public abstract class WebSettings {
* {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}.
*/
public abstract int getMixedContentMode();
+
+ /**
+ * Sets whether to use a video overlay for embedded encrypted video.
+ * In API levels prior to {@link android.os.Build.VERSION_CODES#L}, encrypted video can
+ * only be rendered directly on a secure video surface, so it had been a hard problem to play
+ * encrypted video in HTML. When this flag is on, WebView can play encrypted video (MSE/EME)
+ * by using a video overlay (aka hole-punching) for videos embedded using HTML &lt;video&gt;
+ * tag.<br>
+ * Caution: This setting is intended for use only in a narrow set of circumstances and apps
+ * should only enable it if they require playback of encrypted video content. It will impose
+ * the following limitations on the WebView:
+ * <ul>
+ * <li> Only one video overlay can be played at a time.
+ * <li> Changes made to position or dimensions of a video element may be propagated to the
+ * corresponding video overlay with a noticeable delay.
+ * <li> The video overlay is not visible to web APIs and as such may not interact with
+ * script or styling. For example, CSS styles applied to the &lt;video&gt; tag may be ignored.
+ * </ul>
+ * This is not an exhaustive set of constraints and it may vary with new versions of the
+ * WebView.
+ * @hide
+ */
+ public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean flag);
+
+ /**
+ * Gets whether a video overlay will be used for embedded encrypted video.
+ *
+ * @return true if WebView uses a video overlay for embedded encrypted video.
+ * @see #setVideoOverlayForEmbeddedEncryptedVideoEnabled
+ * @hide
+ */
+ public abstract boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled();
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index c9eb130..9a46052 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2495,17 +2495,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
- * Positions the selector in a way that mimics keyboard focus. If the
- * selector drawable supports hotspots, this manages the focus hotspot.
+ * Positions the selector in a way that mimics keyboard focus.
*/
void positionSelectorLikeFocus(int position, View sel) {
+ // If we're changing position, update the visibility since the selector
+ // is technically being detached from the previous selection.
+ final Drawable selector = mSelector;
+ final boolean manageState = selector != null && mSelectorPosition != position
+ && position != INVALID_POSITION;
+ if (manageState) {
+ selector.setVisible(false, false);
+ }
+
positionSelector(position, sel);
- final Drawable selector = mSelector;
- if (selector != null && position != INVALID_POSITION) {
+ if (manageState) {
final Rect bounds = mSelectorRect;
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
+ selector.setVisible(getVisibility() == VISIBLE, false);
selector.setHotspot(x, y);
}
}
@@ -2520,8 +2528,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (sel instanceof SelectionBoundsAdjuster) {
((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
}
- positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
- selectorRect.bottom);
+
+ // Adjust for selection padding.
+ selectorRect.left -= mSelectionLeftPadding;
+ selectorRect.top -= mSelectionTopPadding;
+ selectorRect.right += mSelectionRightPadding;
+ selectorRect.bottom += mSelectionBottomPadding;
+
+ // Update the selector drawable.
+ final Drawable selector = mSelector;
+ if (selector != null) {
+ selector.setBounds(selectorRect);
+ }
final boolean isChildViewEnabled = mIsChildViewEnabled;
if (sel.isEnabled() != isChildViewEnabled) {
@@ -2532,11 +2550,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
- private void positionSelector(int l, int t, int r, int b) {
- mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
- + mSelectionRightPadding, b + mSelectionBottomPadding);
- }
-
@Override
protected void dispatchDraw(Canvas canvas) {
int saveCount = 0;
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 51759c5..1fddf3e 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -544,6 +544,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
public void setMenuView(ActionMenuView menuView) {
mMenuView = menuView;
+ menuView.initialize(mMenu);
}
private static class SavedState implements Parcelable {
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 3975edf..acee592 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -69,6 +69,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
/** @hide */
public void setPresenter(ActionMenuPresenter presenter) {
mPresenter = presenter;
+ mPresenter.setMenuView(this);
}
@Override
@@ -488,7 +489,7 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mPresenter.dismissPopupMenus();
+ dismissPopupMenus();
}
/** @hide */
@@ -569,15 +570,65 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
mMenu = new MenuBuilder(context);
mMenu.setCallback(new MenuBuilderCallback());
mPresenter = new ActionMenuPresenter(context);
- mPresenter.setMenuView(this);
mPresenter.setCallback(new ActionMenuPresenterCallback());
mMenu.addMenuPresenter(mPresenter);
+ mPresenter.setMenuView(this);
}
return mMenu;
}
/**
+ * Returns the current menu or null if one has not yet been configured.
+ * @hide Internal use only for action bar integration
+ */
+ public MenuBuilder peekMenu() {
+ return mMenu;
+ }
+
+ /**
+ * Show the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be shown, false otherwise
+ */
+ public boolean showOverflowMenu() {
+ return mPresenter != null && mPresenter.showOverflowMenu();
+ }
+
+ /**
+ * Hide the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be hidden, false otherwise
+ */
+ public boolean hideOverflowMenu() {
+ return mPresenter != null && mPresenter.hideOverflowMenu();
+ }
+
+ /**
+ * Check whether the overflow menu is currently showing. This may not reflect
+ * a pending show operation in progress.
+ *
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mPresenter != null && mPresenter.isOverflowMenuShowing();
+ }
+
+ /** @hide */
+ public boolean isOverflowMenuShowPending() {
+ return mPresenter != null && mPresenter.isOverflowMenuShowPending();
+ }
+
+ /**
+ * Dismiss any popups associated with this menu view.
+ */
+ public void dismissPopupMenus() {
+ if (mPresenter != null) {
+ mPresenter.dismissPopupMenus();
+ }
+ }
+
+ /**
* @hide Private LinearLayout (superclass) API. Un-hide if LinearLayout API is made public.
*/
@Override
@@ -601,6 +652,11 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return false;
}
+ /** @hide */
+ public void setExpandedActionViewsExclusive(boolean exclusive) {
+ mPresenter.setExpandedActionViewsExclusive(exclusive);
+ }
+
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 265dbcd..2c1a77c 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -24,6 +24,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.InputType;
+import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -814,8 +815,7 @@ public class DatePicker extends FrameLayout {
mSpinners.removeAllViews();
// We use numeric spinners for year and day, but textual months. Ask icu4c what
// order the user's locale uses for that combination. http://b/7207103.
- String pattern = ICU.getBestDateTimePattern("yyyyMMMdd",
- Locale.getDefault().toString());
+ String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
char[] order = ICU.getDateFormatOrder(pattern);
final int spinnerCount = order.length;
for (int i = 0; i < spinnerCount; i++) {
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index eedacb5..572302a 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -664,7 +664,7 @@ public class ImageView extends View {
InputStream stream = null;
try {
stream = mContext.getContentResolver().openInputStream(mUri);
- d = Drawable.createFromStreamThemed(stream, null, mContext.getTheme());
+ d = Drawable.createFromStream(stream, null);
} catch (Exception e) {
Log.w("ImageView", "Unable to open content: " + mUri, e);
} finally {
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 0c3715d..b49938c 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -357,9 +357,8 @@ public class ProgressBar extends View {
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
- // Ensure the color filter and tint are propagated.
- shapeDrawable.setTint(bitmap.getTint());
- shapeDrawable.setTintMode(bitmap.getTintMode());
+ // Ensure the tint and filter are propagated in the correct order.
+ shapeDrawable.setTint(bitmap.getTint(), bitmap.getTintMode());
shapeDrawable.setColorFilter(bitmap.getColorFilter());
return clip ? new ClipDrawable(
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 0203301..c8917e0 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -574,7 +574,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
throw new FileNotFoundException("Failed to open " + uri);
}
try {
- return Drawable.createFromStreamThemed(stream, null, mContext.getTheme());
+ return Drawable.createFromStream(stream, null);
} finally {
try {
stream.close();
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index f903346..419c582 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -18,13 +18,17 @@
package android.widget;
import android.annotation.NonNull;
+import android.app.ActionBar;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.CollapsibleActionView;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
@@ -32,7 +36,15 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
+import android.view.Window;
import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.SubMenuBuilder;
+import com.android.internal.widget.DecorToolbar;
+import com.android.internal.widget.ToolbarWidgetWrapper;
import java.util.ArrayList;
import java.util.List;
@@ -80,14 +92,25 @@ import java.util.List;
* layout is discouraged on API 21 devices and newer.</p>
*/
public class Toolbar extends ViewGroup {
+ private static final String TAG = "Toolbar";
+
private ActionMenuView mMenuView;
private TextView mTitleTextView;
private TextView mSubtitleTextView;
private ImageButton mNavButtonView;
private ImageView mLogoView;
+ private Drawable mCollapseIcon;
+ private ImageButton mCollapseButtonView;
+ View mExpandedActionView;
+
private int mTitleTextAppearance;
private int mSubtitleTextAppearance;
+ private int mNavButtonStyle;
+
+ private int mButtonGravity;
+
+ private int mMaxButtonHeight;
private int mTitleMarginStart;
private int mTitleMarginEnd;
@@ -104,6 +127,8 @@ public class Toolbar extends ViewGroup {
// Clear me after use.
private final ArrayList<View> mTempViews = new ArrayList<View>();
+ private final int[] mTempMargins = new int[2];
+
private OnMenuItemClickListener mOnMenuItemClickListener;
private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
@@ -117,6 +142,10 @@ public class Toolbar extends ViewGroup {
}
};
+ private ToolbarWidgetWrapper mWrapper;
+ private ActionMenuPresenter mOuterActionMenuPresenter;
+ private ExpandedActionViewMenuPresenter mExpandedMenuPresenter;
+
public Toolbar(Context context) {
this(context, null);
}
@@ -137,7 +166,9 @@ public class Toolbar extends ViewGroup {
mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
+ mNavButtonStyle = a.getResourceId(R.styleable.Toolbar_navigationButtonStyle, 0);
mGravity = a.getInteger(R.styleable.Toolbar_gravity, mGravity);
+ mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
@@ -162,6 +193,8 @@ public class Toolbar extends ViewGroup {
mTitleMarginBottom = marginBottom;
}
+ mMaxButtonHeight = a.getDimensionPixelSize(R.styleable.Toolbar_maxButtonHeight, -1);
+
final int contentInsetStart =
a.getDimensionPixelOffset(R.styleable.Toolbar_contentInsetStart,
RtlSpacingHelper.UNDEFINED);
@@ -180,6 +213,8 @@ public class Toolbar extends ViewGroup {
mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
}
+ mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
+
final CharSequence title = a.getText(R.styleable.Toolbar_title);
if (!TextUtils.isEmpty(title)) {
setTitle(title);
@@ -187,7 +222,7 @@ public class Toolbar extends ViewGroup {
final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
if (!TextUtils.isEmpty(subtitle)) {
- setSubtitle(title);
+ setSubtitle(subtitle);
}
a.recycle();
}
@@ -211,6 +246,110 @@ public class Toolbar extends ViewGroup {
setLogo(getContext().getDrawable(resId));
}
+ /** @hide */
+ public boolean canShowOverflowMenu() {
+ return getVisibility() == VISIBLE && mMenuView != null && mMenuView.isOverflowReserved();
+ }
+
+ /**
+ * Check whether the overflow menu is currently showing. This may not reflect
+ * a pending show operation in progress.
+ *
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mMenuView != null && mMenuView.isOverflowMenuShowing();
+ }
+
+ /** @hide */
+ public boolean isOverflowMenuShowPending() {
+ return mMenuView != null && mMenuView.isOverflowMenuShowPending();
+ }
+
+ /**
+ * Show the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be shown, false otherwise
+ */
+ public boolean showOverflowMenu() {
+ return mMenuView != null && mMenuView.showOverflowMenu();
+ }
+
+ /**
+ * Hide the overflow items from the associated menu.
+ *
+ * @return true if the menu was able to be hidden, false otherwise
+ */
+ public boolean hideOverflowMenu() {
+ return mMenuView != null && mMenuView.hideOverflowMenu();
+ }
+
+ /** @hide */
+ public void setMenu(MenuBuilder menu, ActionMenuPresenter outerPresenter) {
+ if (menu == null && mMenuView == null) {
+ return;
+ }
+
+ ensureMenuView();
+ final MenuBuilder oldMenu = mMenuView.peekMenu();
+ if (oldMenu == menu) {
+ return;
+ }
+
+ if (oldMenu != null) {
+ oldMenu.removeMenuPresenter(mOuterActionMenuPresenter);
+ oldMenu.removeMenuPresenter(mExpandedMenuPresenter);
+ }
+
+ final Context context = getContext();
+
+ if (mExpandedMenuPresenter == null) {
+ mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+ }
+
+ outerPresenter.setExpandedActionViewsExclusive(true);
+ if (menu != null) {
+ menu.addMenuPresenter(outerPresenter);
+ menu.addMenuPresenter(mExpandedMenuPresenter);
+ } else {
+ outerPresenter.initForMenu(context, null);
+ mExpandedMenuPresenter.initForMenu(context, null);
+ outerPresenter.updateMenuView(true);
+ mExpandedMenuPresenter.updateMenuView(true);
+ }
+ mMenuView.setPresenter(outerPresenter);
+ mOuterActionMenuPresenter = outerPresenter;
+ }
+
+ /**
+ * Dismiss all currently showing popup menus, including overflow or submenus.
+ */
+ public void dismissPopupMenus() {
+ if (mMenuView != null) {
+ mMenuView.dismissPopupMenus();
+ }
+ }
+
+ /** @hide */
+ public boolean isTitleTruncated() {
+ if (mTitleTextView == null) {
+ return false;
+ }
+
+ final Layout titleLayout = mTitleTextView.getLayout();
+ if (titleLayout == null) {
+ return false;
+ }
+
+ final int lineCount = titleLayout.getLineCount();
+ for (int i = 0; i < lineCount; i++) {
+ if (titleLayout.getEllipsisCount(i) > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Set a logo drawable.
*
@@ -222,9 +361,7 @@ public class Toolbar extends ViewGroup {
*/
public void setLogo(Drawable drawable) {
if (drawable != null) {
- if (mLogoView == null) {
- mLogoView = new ImageView(getContext());
- }
+ ensureLogoView();
if (mLogoView.getParent() == null) {
addSystemView(mLogoView);
}
@@ -268,8 +405,8 @@ public class Toolbar extends ViewGroup {
* @param description Description to set
*/
public void setLogoDescription(CharSequence description) {
- if (!TextUtils.isEmpty(description) && mLogoView == null) {
- mLogoView = new ImageView(getContext());
+ if (!TextUtils.isEmpty(description)) {
+ ensureLogoView();
}
if (mLogoView != null) {
mLogoView.setContentDescription(description);
@@ -285,10 +422,48 @@ public class Toolbar extends ViewGroup {
return mLogoView != null ? mLogoView.getContentDescription() : null;
}
+ private void ensureLogoView() {
+ if (mLogoView == null) {
+ mLogoView = new ImageView(getContext());
+ }
+ }
+
+ /**
+ * Check whether this Toolbar is currently hosting an expanded action view.
+ *
+ * <p>An action view may be expanded either directly from the
+ * {@link android.view.MenuItem MenuItem} it belongs to or by user action. If the Toolbar
+ * has an expanded action view it can be collapsed using the {@link #collapseActionView()}
+ * method.</p>
+ *
+ * @return true if the Toolbar has an expanded action view
+ */
+ public boolean hasExpandedActionView() {
+ return mExpandedMenuPresenter != null &&
+ mExpandedMenuPresenter.mCurrentExpandedItem != null;
+ }
+
/**
- * Return the current title displayed in the toolbar.
+ * Collapse a currently expanded action view. If this Toolbar does not have an
+ * expanded action view this method has no effect.
+ *
+ * <p>An action view may be expanded either directly from the
+ * {@link android.view.MenuItem MenuItem} it belongs to or by user action.</p>
*
- * @return The current title
+ * @see #hasExpandedActionView()
+ */
+ public void collapseActionView() {
+ final MenuItemImpl item = mExpandedMenuPresenter == null ? null :
+ mExpandedMenuPresenter.mCurrentExpandedItem;
+ if (item != null) {
+ item.collapseActionView();
+ }
+ }
+
+ /**
+ * Returns the title of this toolbar.
+ *
+ * @return The current title.
*/
public CharSequence getTitle() {
return mTitleText;
@@ -319,6 +494,8 @@ public class Toolbar extends ViewGroup {
if (mTitleTextView == null) {
final Context context = getContext();
mTitleTextView = new TextView(context);
+ mTitleTextView.setSingleLine();
+ mTitleTextView.setEllipsize(TextUtils.TruncateAt.END);
mTitleTextView.setTextAppearance(context, mTitleTextAppearance);
}
if (mTitleTextView.getParent() == null) {
@@ -365,6 +542,8 @@ public class Toolbar extends ViewGroup {
if (mSubtitleTextView == null) {
final Context context = getContext();
mSubtitleTextView = new TextView(context);
+ mSubtitleTextView.setSingleLine();
+ mSubtitleTextView.setEllipsize(TextUtils.TruncateAt.END);
mSubtitleTextView.setTextAppearance(context, mSubtitleTextAppearance);
}
if (mSubtitleTextView.getParent() == null) {
@@ -380,6 +559,28 @@ public class Toolbar extends ViewGroup {
}
/**
+ * Sets the text color, size, style, hint color, and highlight color
+ * from the specified TextAppearance resource.
+ */
+ public void setTitleTextAppearance(Context context, int resId) {
+ mTitleTextAppearance = resId;
+ if (mTitleTextView != null) {
+ mTitleTextView.setTextAppearance(context, resId);
+ }
+ }
+
+ /**
+ * Sets the text color, size, style, hint color, and highlight color
+ * from the specified TextAppearance resource.
+ */
+ public void setSubtitleTextAppearance(Context context, int resId) {
+ mSubtitleTextAppearance = resId;
+ if (mSubtitleTextView != null) {
+ mSubtitleTextView.setTextAppearance(context, resId);
+ }
+ }
+
+ /**
* Set the icon to use for the toolbar's navigation button.
*
* <p>The navigation button appears at the start of the toolbar if present. Setting an icon
@@ -395,6 +596,30 @@ public class Toolbar extends ViewGroup {
}
/**
+ * Set a content description for the navigation button if one is present. The content
+ * description will be read via screen readers or other accessibility systems to explain
+ * the action of the navigation button.
+ *
+ * @param description Content description to set
+ */
+ public void setNavigationContentDescription(CharSequence description) {
+ ensureNavButtonView();
+ mNavButtonView.setContentDescription(description);
+ }
+
+ /**
+ * Set a content description for the navigation button if one is present. The content
+ * description will be read via screen readers or other accessibility systems to explain
+ * the action of the navigation button.
+ *
+ * @param resId Resource ID of a content description string to set
+ */
+ public void setNavigationContentDescription(int resId) {
+ ensureNavButtonView();
+ mNavButtonView.setContentDescription(resId != 0 ? getContext().getText(resId) : null);
+ }
+
+ /**
* Set the icon to use for the toolbar's navigation button.
*
* <p>The navigation button appears at the start of the toolbar if present. Setting an icon
@@ -480,12 +705,32 @@ public class Toolbar extends ViewGroup {
* @return The toolbar's Menu
*/
public Menu getMenu() {
+ ensureMenu();
+ return mMenuView.getMenu();
+ }
+
+ private void ensureMenu() {
+ ensureMenuView();
+ if (mMenuView.peekMenu() == null) {
+ // Initialize a new menu for the first time.
+ final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu();
+ if (mExpandedMenuPresenter == null) {
+ mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+ }
+ mMenuView.setExpandedActionViewsExclusive(true);
+ menu.addMenuPresenter(mExpandedMenuPresenter);
+ }
+ }
+
+ private void ensureMenuView() {
if (mMenuView == null) {
mMenuView = new ActionMenuView(getContext());
mMenuView.setOnMenuItemClickListener(mMenuViewItemClickListener);
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.END | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ mMenuView.setLayoutParams(lp);
addSystemView(mMenuView);
}
- return mMenuView.getMenu();
}
private MenuInflater getMenuInflater() {
@@ -634,7 +879,27 @@ public class Toolbar extends ViewGroup {
private void ensureNavButtonView() {
if (mNavButtonView == null) {
- mNavButtonView = new ImageButton(getContext(), null, R.attr.borderlessButtonStyle);
+ mNavButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ mNavButtonView.setLayoutParams(lp);
+ }
+ }
+
+ private void ensureCollapseButtonView() {
+ if (mCollapseButtonView == null) {
+ mCollapseButtonView = new ImageButton(getContext(), null, 0, mNavButtonStyle);
+ mCollapseButtonView.setImageDrawable(mCollapseIcon);
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ lp.mViewType = LayoutParams.EXPANDED;
+ mCollapseButtonView.setLayoutParams(lp);
+ mCollapseButtonView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ collapseActionView();
+ }
+ });
}
}
@@ -657,39 +922,121 @@ public class Toolbar extends ViewGroup {
super.onRestoreInstanceState(ss.getSuperState());
}
+ private void measureChildConstrained(View child, int parentWidthSpec, int widthUsed,
+ int parentHeightSpec, int heightUsed, int heightConstraint) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
+ mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ + widthUsed, lp.width);
+ int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
+ mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ + heightUsed, lp.height);
+
+ final int childHeightMode = MeasureSpec.getMode(childHeightSpec);
+ if (childHeightMode != MeasureSpec.EXACTLY && heightConstraint >= 0) {
+ final int size = childHeightMode != MeasureSpec.UNSPECIFIED ?
+ Math.min(MeasureSpec.getSize(childHeightSpec), heightConstraint) :
+ heightConstraint;
+ childHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+ }
+ child.measure(childWidthSpec, childHeightSpec);
+ }
+
+ /**
+ * Returns the width + uncollapsed margins
+ */
+ private int measureChildCollapseMargins(View child,
+ int parentWidthMeasureSpec, int widthUsed,
+ int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ final int leftDiff = lp.leftMargin - collapsingMargins[0];
+ final int rightDiff = lp.rightMargin - collapsingMargins[1];
+ final int leftMargin = Math.max(0, leftDiff);
+ final int rightMargin = Math.max(0, rightDiff);
+ final int hMargins = leftMargin + rightMargin;
+ collapsingMargins[0] = Math.max(0, -leftDiff);
+ collapsingMargins[1] = Math.max(0, -rightDiff);
+
+ final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+ mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width);
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+ mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ + heightUsed, lp.height);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ return child.getMeasuredWidth() + hMargins;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
int childState = 0;
+ final int[] collapsingMargins = mTempMargins;
+ final int marginStartIndex;
+ final int marginEndIndex;
+ if (isLayoutRtl()) {
+ marginStartIndex = 1;
+ marginEndIndex = 0;
+ } else {
+ marginStartIndex = 0;
+ marginEndIndex = 1;
+ }
+
// System views measure first.
int navWidth = 0;
if (shouldLayout(mNavButtonView)) {
- measureChildWithMargins(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0);
+ measureChildConstrained(mNavButtonView, widthMeasureSpec, width, heightMeasureSpec, 0,
+ mMaxButtonHeight);
navWidth = mNavButtonView.getMeasuredWidth() + getHorizontalMargins(mNavButtonView);
height = Math.max(height, mNavButtonView.getMeasuredHeight() +
getVerticalMargins(mNavButtonView));
childState = combineMeasuredStates(childState, mNavButtonView.getMeasuredState());
}
- width += Math.max(getContentInsetStart(), navWidth);
+ if (shouldLayout(mCollapseButtonView)) {
+ measureChildConstrained(mCollapseButtonView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, mMaxButtonHeight);
+ navWidth = mCollapseButtonView.getMeasuredWidth() +
+ getHorizontalMargins(mCollapseButtonView);
+ height = Math.max(height, mCollapseButtonView.getMeasuredHeight() +
+ getVerticalMargins(mCollapseButtonView));
+ childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
+ }
+
+ final int contentInsetStart = getContentInsetStart();
+ width += Math.max(contentInsetStart, navWidth);
+ collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
int menuWidth = 0;
if (shouldLayout(mMenuView)) {
- measureChildWithMargins(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0);
+ measureChildConstrained(mMenuView, widthMeasureSpec, width, heightMeasureSpec, 0,
+ mMaxButtonHeight);
menuWidth = mMenuView.getMeasuredWidth() + getHorizontalMargins(mMenuView);
height = Math.max(height, mMenuView.getMeasuredHeight() +
getVerticalMargins(mMenuView));
childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
}
- width += Math.max(getContentInsetEnd(), menuWidth);
+ final int contentInsetEnd = getContentInsetEnd();
+ width += Math.max(contentInsetEnd, menuWidth);
+ collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
+
+ if (shouldLayout(mExpandedActionView)) {
+ width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
+ height = Math.max(height, mExpandedActionView.getMeasuredHeight() +
+ getVerticalMargins(mExpandedActionView));
+ childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState());
+ }
if (shouldLayout(mLogoView)) {
- measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0);
- width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView);
+ width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
height = Math.max(height, mLogoView.getMeasuredHeight() +
getVerticalMargins(mLogoView));
childState = combineMeasuredStates(childState, mLogoView.getMeasuredState());
@@ -700,17 +1047,18 @@ public class Toolbar extends ViewGroup {
final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
if (shouldLayout(mTitleTextView)) {
- measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins,
- heightMeasureSpec, titleVertMargins);
+ titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec,
+ width + titleHorizMargins, heightMeasureSpec, titleVertMargins,
+ collapsingMargins);
titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView);
childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState());
}
if (shouldLayout(mSubtitleTextView)) {
- measureChildWithMargins(mSubtitleTextView, widthMeasureSpec, width + titleHorizMargins,
- heightMeasureSpec, titleHeight + titleVertMargins);
- titleWidth = Math.max(titleWidth, mSubtitleTextView.getMeasuredWidth() +
- getHorizontalMargins(mSubtitleTextView));
+ titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView,
+ widthMeasureSpec, width + titleHorizMargins,
+ heightMeasureSpec, titleHeight + titleVertMargins,
+ collapsingMargins));
titleHeight += mSubtitleTextView.getMeasuredHeight() +
getVerticalMargins(mSubtitleTextView);
childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState());
@@ -723,13 +1071,13 @@ public class Toolbar extends ViewGroup {
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp.mViewType == LayoutParams.SYSTEM || !shouldLayout(child)) {
+ if (lp.mViewType != LayoutParams.CUSTOM || !shouldLayout(child)) {
// We already got all system views above. Skip them and GONE views.
continue;
}
- measureChildWithMargins(child, widthMeasureSpec, width, heightMeasureSpec, 0);
- width += child.getMeasuredWidth() + getHorizontalMargins(child);
+ width += measureChildCollapseMargins(child, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
@@ -760,30 +1108,51 @@ public class Toolbar extends ViewGroup {
int left = paddingLeft;
int right = width - paddingRight;
+ final int[] collapsingMargins = mTempMargins;
+ collapsingMargins[0] = collapsingMargins[1] = 0;
+
if (shouldLayout(mNavButtonView)) {
if (isRtl) {
- right = layoutChildRight(mNavButtonView, right);
+ right = layoutChildRight(mNavButtonView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mNavButtonView, left);
+ left = layoutChildLeft(mNavButtonView, left, collapsingMargins);
+ }
+ }
+
+ if (shouldLayout(mCollapseButtonView)) {
+ if (isRtl) {
+ right = layoutChildRight(mCollapseButtonView, right, collapsingMargins);
+ } else {
+ left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins);
}
}
if (shouldLayout(mMenuView)) {
if (isRtl) {
- left = layoutChildLeft(mMenuView, left);
+ left = layoutChildLeft(mMenuView, left, collapsingMargins);
} else {
- right = layoutChildRight(mMenuView, right);
+ right = layoutChildRight(mMenuView, right, collapsingMargins);
}
}
+ collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
+ collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
left = Math.max(left, getContentInsetLeft());
right = Math.min(right, width - paddingRight - getContentInsetRight());
+ if (shouldLayout(mExpandedActionView)) {
+ if (isRtl) {
+ right = layoutChildRight(mExpandedActionView, right, collapsingMargins);
+ } else {
+ left = layoutChildLeft(mExpandedActionView, left, collapsingMargins);
+ }
+ }
+
if (shouldLayout(mLogoView)) {
if (isRtl) {
- right = layoutChildRight(mLogoView, right);
+ right = layoutChildRight(mLogoView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mLogoView, left);
+ left = layoutChildLeft(mLogoView, left, collapsingMargins);
}
}
@@ -801,79 +1170,83 @@ public class Toolbar extends ViewGroup {
if (layoutTitle || layoutSubtitle) {
int titleTop;
+ final View topChild = layoutTitle ? mTitleTextView : mSubtitleTextView;
+ final View bottomChild = layoutSubtitle ? mSubtitleTextView : mTitleTextView;
+ final LayoutParams toplp = (LayoutParams) topChild.getLayoutParams();
+ final LayoutParams bottomlp = (LayoutParams) bottomChild.getLayoutParams();
+
switch (mGravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.TOP:
- titleTop = getPaddingTop();
+ titleTop = getPaddingTop() + toplp.topMargin + mTitleMarginTop;
break;
default:
case Gravity.CENTER_VERTICAL:
- final View child = layoutTitle ? mTitleTextView : mSubtitleTextView;
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int space = height - paddingTop - paddingBottom;
int spaceAbove = (space - titleHeight) / 2;
- if (spaceAbove < lp.topMargin + mTitleMarginTop) {
- spaceAbove = lp.topMargin + mTitleMarginTop;
+ if (spaceAbove < toplp.topMargin + mTitleMarginTop) {
+ spaceAbove = toplp.topMargin + mTitleMarginTop;
} else {
final int spaceBelow = height - paddingBottom - titleHeight -
spaceAbove - paddingTop;
- if (spaceBelow < lp.bottomMargin + mTitleMarginBottom) {
+ if (spaceBelow < toplp.bottomMargin + mTitleMarginBottom) {
spaceAbove = Math.max(0, spaceAbove -
- (lp.bottomMargin + mTitleMarginBottom - spaceBelow));
+ (bottomlp.bottomMargin + mTitleMarginBottom - spaceBelow));
}
}
titleTop = paddingTop + spaceAbove;
break;
case Gravity.BOTTOM:
- titleTop = height - paddingBottom - titleHeight;
+ titleTop = height - paddingBottom - bottomlp.bottomMargin - mTitleMarginBottom -
+ titleHeight;
break;
}
if (isRtl) {
+ final int rd = mTitleMarginStart - collapsingMargins[1];
+ right -= Math.max(0, rd);
+ collapsingMargins[1] = Math.max(0, -rd);
int titleRight = right;
int subtitleRight = right;
- titleTop += mTitleMarginTop;
+
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
- titleRight -= lp.rightMargin + mTitleMarginStart;
- titleTop += lp.topMargin;
final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth();
final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
- titleRight = titleLeft - lp.leftMargin - mTitleMarginEnd;
+ titleRight = titleLeft - mTitleMarginEnd;
titleTop = titleBottom + lp.bottomMargin;
}
if (layoutSubtitle) {
final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
- subtitleRight -= lp.rightMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth();
final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
- subtitleRight = subtitleRight - lp.leftMargin - mTitleMarginEnd;
+ subtitleRight = subtitleRight - mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
right = Math.max(titleRight, subtitleRight);
} else {
+ final int ld = mTitleMarginStart - collapsingMargins[0];
+ left += Math.max(0, ld);
+ collapsingMargins[0] = Math.max(0, -ld);
int titleLeft = left;
int subtitleLeft = left;
- titleTop += mTitleMarginTop;
+
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
- titleLeft += lp.leftMargin + mTitleMarginStart;
- titleTop += lp.topMargin;
final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth();
final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
- titleLeft = titleRight + lp.rightMargin + mTitleMarginEnd;
+ titleLeft = titleRight + mTitleMarginEnd;
titleTop = titleBottom + lp.bottomMargin;
}
if (layoutSubtitle) {
final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
- subtitleLeft += lp.leftMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth();
final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
- subtitleLeft = subtitleRight + lp.rightMargin + mTitleMarginEnd;
+ subtitleLeft = subtitleRight + mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
left = Math.max(titleLeft, subtitleLeft);
@@ -886,19 +1259,19 @@ public class Toolbar extends ViewGroup {
addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
final int leftViewsCount = mTempViews.size();
for (int i = 0; i < leftViewsCount; i++) {
- left = layoutChildLeft(mTempViews.get(i), left);
+ left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins);
}
addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
final int rightViewsCount = mTempViews.size();
for (int i = 0; i < rightViewsCount; i++) {
- right = layoutChildRight(mTempViews.get(i), right);
+ right = layoutChildRight(mTempViews.get(i), right, collapsingMargins);
}
// Centered views try to center with respect to the whole bar, but views pinned
// to the left or right can push the mass of centered views to one side or the other.
- addCustomViewsWithGravity(mTempViews, Gravity.CENTER);
- final int centerViewsWidth = getViewListMeasuredWidth(mTempViews);
+ addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);
+ final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins);
final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
final int halfCenterViewsWidth = centerViewsWidth / 2;
int centerLeft = parentCenter - halfCenterViewsWidth;
@@ -911,25 +1284,35 @@ public class Toolbar extends ViewGroup {
final int centerViewsCount = mTempViews.size();
for (int i = 0; i < centerViewsCount; i++) {
- centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft);
+ centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins);
}
mTempViews.clear();
}
- private int getViewListMeasuredWidth(List<View> views) {
+ private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) {
+ int collapseLeft = collapsingMargins[0];
+ int collapseRight = collapsingMargins[1];
int width = 0;
final int count = views.size();
for (int i = 0; i < count; i++) {
final View v = views.get(i);
final LayoutParams lp = (LayoutParams) v.getLayoutParams();
- width += lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+ final int l = lp.leftMargin - collapseLeft;
+ final int r = lp.rightMargin - collapseRight;
+ final int leftMargin = Math.max(0, l);
+ final int rightMargin = Math.max(0, r);
+ collapseLeft = Math.max(0, -l);
+ collapseRight = Math.max(0, -r);
+ width += leftMargin + v.getMeasuredWidth() + rightMargin;
}
return width;
}
- private int layoutChildLeft(View child, int left) {
+ private int layoutChildLeft(View child, int left, int[] collapsingMargins) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- left += lp.leftMargin;
+ final int l = lp.leftMargin - collapsingMargins[0];
+ left += Math.max(0, l);
+ collapsingMargins[0] = Math.max(0, -l);
final int top = getChildTop(child);
final int childWidth = child.getMeasuredWidth();
child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
@@ -937,9 +1320,11 @@ public class Toolbar extends ViewGroup {
return left;
}
- private int layoutChildRight(View child, int right) {
+ private int layoutChildRight(View child, int right, int[] collapsingMargins) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- right -= lp.rightMargin;
+ final int r = lp.rightMargin - collapsingMargins[1];
+ right -= Math.max(0, r);
+ collapsingMargins[1] = Math.max(0, -r);
final int top = getChildTop(child);
final int childWidth = child.getMeasuredWidth();
child.layout(right - childWidth, top, right, top + child.getMeasuredHeight());
@@ -1007,17 +1392,16 @@ public class Toolbar extends ViewGroup {
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) &&
+ if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) &&
getChildHorizontalGravity(lp.gravity) == absGrav) {
views.add(child);
}
-
}
} else {
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (lp.mViewType != LayoutParams.SYSTEM && shouldLayout(child) &&
+ if (lp.mViewType == LayoutParams.CUSTOM && shouldLayout(child) &&
getChildHorizontalGravity(lp.gravity) == absGrav) {
views.add(child);
}
@@ -1054,14 +1438,16 @@ public class Toolbar extends ViewGroup {
}
@Override
- public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return super.generateLayoutParams(attrs);
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new LayoutParams(getContext(), attrs);
}
@Override
- protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
if (p instanceof LayoutParams) {
return new LayoutParams((LayoutParams) p);
+ } else if (p instanceof ActionBar.LayoutParams) {
+ return new LayoutParams((ActionBar.LayoutParams) p);
} else if (p instanceof MarginLayoutParams) {
return new LayoutParams((MarginLayoutParams) p);
} else {
@@ -1070,7 +1456,7 @@ public class Toolbar extends ViewGroup {
}
@Override
- protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
+ protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
@@ -1083,6 +1469,25 @@ public class Toolbar extends ViewGroup {
return ((LayoutParams) child.getLayoutParams()).mViewType == LayoutParams.CUSTOM;
}
+ /** @hide */
+ public DecorToolbar getWrapper() {
+ if (mWrapper == null) {
+ mWrapper = new ToolbarWidgetWrapper(this);
+ }
+ return mWrapper;
+ }
+
+ private void setChildVisibilityForExpandedActionView(boolean expand) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.mViewType != LayoutParams.EXPANDED && child != mMenuView) {
+ child.setVisibility(expand ? GONE : VISIBLE);
+ }
+ }
+ }
+
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
@@ -1103,44 +1508,15 @@ public class Toolbar extends ViewGroup {
*
* @attr ref android.R.styleable#Toolbar_LayoutParams_layout_gravity
*/
- public static class LayoutParams extends MarginLayoutParams {
- /**
- * Gravity for the view associated with these LayoutParams.
- *
- * @see android.view.Gravity
- */
- @ViewDebug.ExportedProperty(category = "layout", mapping = {
- @ViewDebug.IntToString(from = -1, to = "NONE"),
- @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"),
- @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"),
- @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"),
- @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"),
- @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"),
- @ViewDebug.IntToString(from = Gravity.START, to = "START"),
- @ViewDebug.IntToString(from = Gravity.END, to = "END"),
- @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"),
- @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"),
- @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
- @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"),
- @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"),
- @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL")
- })
- public int gravity = Gravity.NO_GRAVITY;
-
+ public static class LayoutParams extends ActionBar.LayoutParams {
static final int CUSTOM = 0;
static final int SYSTEM = 1;
+ static final int EXPANDED = 2;
int mViewType = CUSTOM;
public LayoutParams(@NonNull Context c, AttributeSet attrs) {
super(c, attrs);
-
- TypedArray a = c.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.Toolbar_LayoutParams);
- gravity = a.getInt(
- com.android.internal.R.styleable.Toolbar_LayoutParams_layout_gravity,
- Gravity.NO_GRAVITY);
- a.recycle();
}
public LayoutParams(int width, int height) {
@@ -1160,7 +1536,11 @@ public class Toolbar extends ViewGroup {
public LayoutParams(LayoutParams source) {
super(source);
- this.gravity = source.gravity;
+ mViewType = source.mViewType;
+ }
+
+ public LayoutParams(ActionBar.LayoutParams source) {
+ super(source);
}
public LayoutParams(MarginLayoutParams source) {
@@ -1199,4 +1579,126 @@ public class Toolbar extends ViewGroup {
}
};
}
+
+ private class ExpandedActionViewMenuPresenter implements MenuPresenter {
+ MenuBuilder mMenu;
+ MenuItemImpl mCurrentExpandedItem;
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Clear the expanded action view when menus change.
+ if (mMenu != null && mCurrentExpandedItem != null) {
+ mMenu.collapseItemActionView(mCurrentExpandedItem);
+ }
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ return null;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ // Make sure the expanded item we have is still there.
+ if (mCurrentExpandedItem != null) {
+ boolean found = false;
+
+ if (mMenu != null) {
+ final int count = mMenu.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItem item = mMenu.getItem(i);
+ if (item == mCurrentExpandedItem) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ // The item we had expanded disappeared. Collapse.
+ collapseItemActionView(mMenu, mCurrentExpandedItem);
+ }
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ ensureCollapseButtonView();
+ if (mCollapseButtonView.getParent() != Toolbar.this) {
+ addView(mCollapseButtonView);
+ }
+ mExpandedActionView = item.getActionView();
+ mCurrentExpandedItem = item;
+ if (mExpandedActionView.getParent() != Toolbar.this) {
+ final LayoutParams lp = generateDefaultLayoutParams();
+ lp.gravity = Gravity.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
+ lp.mViewType = LayoutParams.EXPANDED;
+ mExpandedActionView.setLayoutParams(lp);
+ addView(mExpandedActionView);
+ }
+
+ setChildVisibilityForExpandedActionView(true);
+ requestLayout();
+ item.setActionViewExpanded(true);
+
+ if (mExpandedActionView instanceof CollapsibleActionView) {
+ ((CollapsibleActionView) mExpandedActionView).onActionViewExpanded();
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ // Do this before detaching the actionview from the hierarchy, in case
+ // it needs to dismiss the soft keyboard, etc.
+ if (mExpandedActionView instanceof CollapsibleActionView) {
+ ((CollapsibleActionView) mExpandedActionView).onActionViewCollapsed();
+ }
+
+ removeView(mExpandedActionView);
+ removeView(mCollapseButtonView);
+ mExpandedActionView = null;
+
+ setChildVisibilityForExpandedActionView(false);
+ mCurrentExpandedItem = null;
+ requestLayout();
+ item.setActionViewExpanded(false);
+
+ return true;
+ }
+
+ @Override
+ public int getId() {
+ return 0;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ }
+ }
}