summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/app/Activity.java27
-rw-r--r--core/java/android/app/ActivityManager.java21
-rw-r--r--core/java/android/app/ContextImpl.java14
-rw-r--r--core/java/android/app/Notification.java11
-rw-r--r--core/java/android/app/VoiceInteractor.java140
-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.java759
-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/bluetooth/BluetoothLeScanner.aidl)5
-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.java36
-rw-r--r--core/java/android/content/ContextWrapper.java5
-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/ddm/DdmHandleHello.java22
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java4
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java24
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java28
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java2
-rw-r--r--core/java/android/hardware/camera2/marshal/impl/MarshalQueryablePair.java158
-rw-r--r--core/java/android/hardware/camera2/params/MeteringRectangle.java56
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java7
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java4
-rw-r--r--core/java/android/net/Network.java19
-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/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/preference/SeekBarVolumizer.java91
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java33
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java243
-rw-r--r--core/java/android/view/GLRenderer.java22
-rw-r--r--core/java/android/view/HardwareLayer.java105
-rw-r--r--core/java/android/view/HardwareRenderer.java6
-rw-r--r--core/java/android/view/TextureView.java4
-rw-r--r--core/java/android/view/ThreadedRenderer.java12
-rw-r--r--core/java/android/view/WindowManagerPolicy.java5
-rw-r--r--core/java/android/widget/Toolbar.java173
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractor.aidl4
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractorCallback.aidl1
-rw-r--r--core/java/com/android/internal/app/WindowDecorActionBar.java6
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java4
-rw-r--r--core/java/com/android/internal/policy/IKeyguardService.aidl5
-rw-r--r--core/java/com/android/internal/util/Preconditions.java27
-rw-r--r--core/java/com/android/internal/util/VirtualRefBasePtr.java10
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java17
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtilsCache.java25
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java42
-rw-r--r--core/java/com/android/internal/widget/ToolbarWidgetWrapper.java10
-rw-r--r--core/jni/android_media_AudioFormat.h54
-rw-r--r--core/jni/android_media_AudioSystem.cpp1115
-rw-r--r--core/jni/android_view_HardwareLayer.cpp18
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp14
-rw-r--r--core/res/res/anim/input_method_exit.xml2
-rw-r--r--core/res/res/anim/lock_screen_behind_enter.xml15
-rw-r--r--core/res/res/anim/lock_screen_wallpaper_behind_enter.xml28
-rw-r--r--core/res/res/values/config.xml1
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--core/res/res/values/styles.xml6
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/res/res/values/themes_quantum.xml18
-rw-r--r--core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java (renamed from core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java)95
-rw-r--r--core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java (renamed from core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java)12
-rw-r--r--core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java (renamed from core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java)14
81 files changed, 4993 insertions, 3487 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b5281ff..4c32989 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,15 @@ 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);
}
/**
@@ -5632,8 +5638,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),
@@ -5842,6 +5854,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/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ff8688d..8ffa6fe 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -249,6 +249,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 = {};
@@ -1032,6 +1034,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);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 90aeaae..8dba1dc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1671,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;
/**
@@ -1694,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
@@ -1702,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();
}
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 6dc48b0..f332c9d 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 evetually 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 actvitity 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 resonsponse 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/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.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/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/bluetooth/BluetoothLeScanner.aidl b/core/java/android/bluetooth/le/ScanResult.aidl
index 8cecdd7..3943035 100644
--- a/core/java/android/bluetooth/BluetoothLeScanner.aidl
+++ b/core/java/android/bluetooth/le/ScanResult.aidl
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
-parcelable BluetoothLeScanner.ScanResult;
-parcelable BluetoothLeScanner.Settings;
+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..d3a979c 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";
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/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/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index 220b40d..2dce425 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -22,6 +22,7 @@ import org.apache.harmony.dalvik.ddmc.DdmServer;
import android.util.Log;
import android.os.Debug;
import android.os.UserHandle;
+import dalvik.system.VMRuntime;
import java.nio.ByteBuffer;
@@ -126,8 +127,21 @@ public class DdmHandleHello extends ChunkHandler {
// appName = "unknown";
String appName = DdmHandleAppName.getAppName();
- ByteBuffer out = ByteBuffer.allocate(20
- + vmIdent.length()*2 + appName.length()*2);
+ VMRuntime vmRuntime = VMRuntime.getRuntime();
+ String instructionSetDescription =
+ vmRuntime.is64Bit() ? "64-bit" : "32-bit";
+ String vmInstructionSet = vmRuntime.vmInstructionSet();
+ if (vmInstructionSet != null && vmInstructionSet.length() > 0) {
+ instructionSetDescription += " (" + vmInstructionSet + ")";
+ }
+ String vmFlags = "CheckJNI="
+ + (vmRuntime.isCheckJniEnabled() ? "true" : "false");
+
+ ByteBuffer out = ByteBuffer.allocate(28
+ + vmIdent.length() * 2
+ + appName.length() * 2
+ + instructionSetDescription.length() * 2
+ + vmFlags.length() * 2);
out.order(ChunkHandler.CHUNK_ORDER);
out.putInt(DdmServer.CLIENT_PROTOCOL_VERSION);
out.putInt(android.os.Process.myPid());
@@ -136,6 +150,10 @@ public class DdmHandleHello extends ChunkHandler {
putString(out, vmIdent);
putString(out, appName);
out.putInt(UserHandle.myUserId());
+ out.putInt(instructionSetDescription.length());
+ putString(out, instructionSetDescription);
+ out.putInt(vmFlags.length());
+ putString(out, vmFlags);
Chunk reply = new Chunk(CHUNK_HELO, out);
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index cea68d2..2f5b4fe 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -822,8 +822,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @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
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 6aa24e6..d4dfdd5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -691,8 +691,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* 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,
@@ -763,8 +767,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* 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,
@@ -846,8 +854,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* 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,
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 42020eb..7d07c92 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -537,8 +537,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* 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,
@@ -807,8 +811,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* 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,
@@ -1287,8 +1295,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* 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,
@@ -1792,8 +1804,8 @@ public 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<android.util.Range<Float>> LENS_FOCUS_RANGE =
- new Key<android.util.Range<Float>>("android.lens.focusRange", new TypeReference<android.util.Range<Float>>() {{ }});
+ 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)
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index dc0c652..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;
@@ -1006,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/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java
index a7a3b59..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,8 +84,8 @@ public final class MeteringRectangle {
* @param y coordinate >= 0
* @param width width >= 0
* @param height height >= 0
- * @param meteringWeight weight >= 0
- *
+ * @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) {
@@ -64,7 +93,8 @@ public final class MeteringRectangle {
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");
}
/**
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/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 38a65c5..795117e 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -115,6 +115,10 @@ public class SoftInputWindow extends Dialog {
getWindow().setAttributes(lp);
}
+ public int getGravity() {
+ return getWindow().getAttributes().gravity;
+ }
+
private void updateWidthHeight(WindowManager.LayoutParams lp) {
if (lp.gravity == Gravity.TOP || lp.gravity == Gravity.BOTTOM) {
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
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/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/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/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 5e005d0..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;
@@ -45,11 +48,14 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
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 int mOriginalStreamVolume;
private Ringtone mRingtone;
@@ -63,17 +69,6 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
- private ContentObserver mVolumeObserver = new ContentObserver(new Handler()) {
- @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, int streamType, Uri defaultUri,
Callback callback) {
mContext = context;
@@ -85,10 +80,11 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
mHandler = new Handler(thread.getLooper(), this);
mCallback = callback;
mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
+ 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;
@@ -103,6 +99,9 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba
}
public void setSeekBar(SeekBar seekBar) {
+ if (mSeekBar != null) {
+ mSeekBar.setOnSeekBarChangeListener(null);
+ }
mSeekBar = seekBar;
mSeekBar.setOnSeekBarChangeListener(null);
mSeekBar.setMax(mMaxStreamVolume);
@@ -150,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);
+ }
}
}
}
@@ -172,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() {
@@ -252,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/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 cd357b7..2e9077a 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -20,7 +20,6 @@ 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;
@@ -48,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;
@@ -81,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));
@@ -93,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));
@@ -144,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) {
}
@@ -175,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) {
}
@@ -192,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;
@@ -210,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
@@ -330,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;
}
}
@@ -426,6 +493,27 @@ 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()");
@@ -440,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()");
@@ -459,6 +556,12 @@ 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;
@@ -473,9 +576,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
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;
}
@@ -508,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();
}
@@ -531,15 +645,98 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
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/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
index 64a4c41..f1163e2 100644
--- a/core/java/android/view/GLRenderer.java
+++ b/core/java/android/view/GLRenderer.java
@@ -178,7 +178,7 @@ public class GLRenderer extends HardwareRenderer {
private static EGLSurface sPbuffer;
private static final Object[] sPbufferLock = new Object[0];
- private List<HardwareLayer> mAttachedLayers = new ArrayList<HardwareLayer>();
+ private List<HardwareLayer> mLayerUpdates = new ArrayList<HardwareLayer>();
private static class GLRendererEglContext extends ManagedEGLContext {
final Handler mHandler = new Handler();
@@ -471,7 +471,7 @@ public class GLRenderer extends HardwareRenderer {
@Override
void pushLayerUpdate(HardwareLayer layer) {
- mGlCanvas.pushLayerUpdate(layer);
+ mLayerUpdates.add(layer);
}
@Override
@@ -494,11 +494,6 @@ public class GLRenderer extends HardwareRenderer {
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());
@@ -509,11 +504,7 @@ public class GLRenderer extends HardwareRenderer {
if (mGlCanvas != null) {
mGlCanvas.cancelLayerUpdate(layer);
}
- if (hasContext()) {
- long backingLayer = layer.detachBackingLayer();
- nDestroyLayer(backingLayer);
- }
- mAttachedLayers.remove(layer);
+ mLayerUpdates.remove(layer);
}
@Override
@@ -1198,16 +1189,19 @@ public class GLRenderer extends HardwareRenderer {
private void flushLayerChanges() {
// Loop through and apply any pending layer changes
- for (int i = 0; i < mAttachedLayers.size(); i++) {
- HardwareLayer layer = mAttachedLayers.get(i);
+ for (int i = 0; i < mLayerUpdates.size(); i++) {
+ HardwareLayer layer = mLayerUpdates.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--;
+ } else if (layer.hasDisplayList()) {
+ mCanvas.pushLayerUpdate(layer);
}
}
+ mLayerUpdates.clear();
}
@Override
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 4d78733..652bcd2 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,7 +167,7 @@ final class HardwareLayer {
surface.detachFromGLContext();
// SurfaceTexture owns the texture name and detachFromGLContext
// should have deleted it
- nOnTextureDestroyed(mFinalizer.mDeferredUpdater);
+ nOnTextureDestroyed(mFinalizer.get());
}
});
}
@@ -200,24 +184,26 @@ final class HardwareLayer {
return;
}
- boolean success = nFlushChanges(mFinalizer.mDeferredUpdater);
+ boolean success = nFlushChanges(mFinalizer.get());
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,8 +211,8 @@ 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;
}
@@ -258,15 +244,6 @@ final class HardwareLayer {
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 +258,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 60f8ee3..d71de9f 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -323,12 +323,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.
*/
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 cac23a8..11db996 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -294,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
@@ -309,7 +304,7 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void onLayerDestroyed(HardwareLayer layer) {
- nDestroyLayer(mNativeProxy, layer.getDeferredLayerUpdater());
+ nCancelLayerUpdate(mNativeProxy, layer.getDeferredLayerUpdater());
}
@Override
@@ -398,7 +393,8 @@ 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);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index d45d686..2b4677c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1199,6 +1199,9 @@ public interface WindowManagerPolicy {
/**
* 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 fadeoutDuration);
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
}
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index 5033bee..c41266e 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -127,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 =
@@ -557,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
@@ -906,12 +930,49 @@ public class Toolbar extends ViewGroup {
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;
@@ -934,7 +995,9 @@ public class Toolbar extends ViewGroup {
childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
}
- width += Math.max(getContentInsetStart(), navWidth);
+ final int contentInsetStart = getContentInsetStart();
+ width += Math.max(contentInsetStart, navWidth);
+ collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
int menuWidth = 0;
if (shouldLayout(mMenuView)) {
@@ -946,21 +1009,21 @@ public class Toolbar extends ViewGroup {
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)) {
- measureChildWithMargins(mExpandedActionView, widthMeasureSpec, width,
- heightMeasureSpec, 0);
- width += mExpandedActionView.getMeasuredWidth() +
- getHorizontalMargins(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());
@@ -971,17 +1034,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());
@@ -999,8 +1063,8 @@ public class Toolbar extends ViewGroup {
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());
}
@@ -1031,46 +1095,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);
+ right = layoutChildRight(mCollapseButtonView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mCollapseButtonView, left);
+ 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);
+ right = layoutChildRight(mExpandedActionView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mExpandedActionView, left);
+ 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);
}
}
@@ -1119,48 +1188,52 @@ public class Toolbar extends ViewGroup {
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;
+
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
- titleRight -= lp.rightMargin + mTitleMarginStart;
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;
+
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
- titleLeft += lp.leftMargin + mTitleMarginStart;
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);
@@ -1173,19 +1246,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_HORIZONTAL);
- final int centerViewsWidth = getViewListMeasuredWidth(mTempViews);
+ final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins);
final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
final int halfCenterViewsWidth = centerViewsWidth / 2;
int centerLeft = parentCenter - halfCenterViewsWidth;
@@ -1198,25 +1271,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());
@@ -1224,9 +1307,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());
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 737906a..2900595 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -26,7 +26,9 @@ import com.android.internal.app.IVoiceInteractorRequest;
*/
interface IVoiceInteractor {
IVoiceInteractorRequest startConfirmation(String callingPackage,
- IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+ IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras);
+ IVoiceInteractorRequest startAbortVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
IVoiceInteractorRequest startCommand(String callingPackage,
IVoiceInteractorCallback callback, String command, in Bundle extras);
boolean[] supportsCommands(String callingPackage, in String[] commands);
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index c6f93e1..8dbf9d4 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractorRequest;
oneway interface IVoiceInteractorCallback {
void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
in Bundle result);
+ void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result);
void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
void deliverCancel(IVoiceInteractorRequest request);
}
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 5c7a4e6..c0b5b97 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -588,7 +588,7 @@ public class WindowDecorActionBar extends ActionBar implements
return;
}
- final FragmentTransaction trans = ((View) mDecorToolbar).isInEditMode() ? null :
+ final FragmentTransaction trans = mDecorToolbar.getViewGroup().isInEditMode() ? null :
mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack();
if (mSelectedTab == tab) {
@@ -847,7 +847,7 @@ public class WindowDecorActionBar extends ActionBar implements
mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() &&
- isCollapsed((View) mDecorToolbar)) {
+ isCollapsed(mDecorToolbar.getViewGroup())) {
mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
}
}
@@ -959,7 +959,7 @@ public class WindowDecorActionBar extends ActionBar implements
// Clear out the context mode views after the animation finishes
mContextView.closeMode();
- ((View) mDecorToolbar).sendAccessibilityEvent(
+ mDecorToolbar.getViewGroup().sendAccessibilityEvent(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll);
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index 40834ba..17685fd 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -22,9 +22,6 @@ import android.os.Looper;
import android.os.Message;
public class HandlerCaller {
-
- public final Context mContext;
-
final Looper mMainLooper;
final Handler mH;
@@ -47,7 +44,6 @@ public class HandlerCaller {
public HandlerCaller(Context context, Looper looper, Callback callback,
boolean asyncHandler) {
- mContext = context;
mMainLooper = looper != null ? looper : context.getMainLooper();
mH = new MyHandler(mMainLooper, asyncHandler);
mCallback = callback;
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index f22800c..a5421f5 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -60,6 +60,9 @@ interface IKeyguardService {
/**
* Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper
* and keyguard flag.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
- oneway void startKeyguardExitAnimation(long fadeoutDuration);
+ oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
}
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index f6722a6..c0d1e88 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -183,6 +183,33 @@ public class Preconditions {
}
/**
+ * Ensures that the argument int value is within the inclusive range.
+ *
+ * @param value a int value
+ * @param lower the lower endpoint of the inclusive range
+ * @param upper the upper endpoint of the inclusive range
+ * @param valueName the name of the argument to use if the check fails
+ *
+ * @return the validated int value
+ *
+ * @throws IllegalArgumentException if {@code value} was not within the range
+ */
+ public static int checkArgumentInRange(int value, int lower, int upper,
+ String valueName) {
+ if (value < lower) {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is out of range of [%d, %d] (too low)", valueName, lower, upper));
+ } else if (value > upper) {
+ throw new IllegalArgumentException(
+ String.format(
+ "%s is out of range of [%d, %d] (too high)", valueName, lower, upper));
+ }
+
+ return value;
+ }
+
+ /**
* Ensures that the array is not {@code null}, and none if its elements are {@code null}.
*
* @param value an array of boxed objects
diff --git a/core/java/com/android/internal/util/VirtualRefBasePtr.java b/core/java/com/android/internal/util/VirtualRefBasePtr.java
index 0bd4d3a..52306f1 100644
--- a/core/java/com/android/internal/util/VirtualRefBasePtr.java
+++ b/core/java/com/android/internal/util/VirtualRefBasePtr.java
@@ -32,11 +32,17 @@ public final class VirtualRefBasePtr {
return mNativePtr;
}
+ public void release() {
+ if (mNativePtr != 0) {
+ nDecStrong(mNativePtr);
+ mNativePtr = 0;
+ }
+ }
+
@Override
protected void finalize() throws Throwable {
try {
- nDecStrong(mNativePtr);
- mNativePtr = 0;
+ release();
} finally {
super.finalize();
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 25e3463..d31c5cc 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -43,7 +43,6 @@ import android.view.View;
import android.widget.Button;
import com.android.internal.R;
-import com.android.internal.telephony.ITelephony;
import com.google.android.collect.Lists;
import java.security.MessageDigest;
@@ -1360,19 +1359,11 @@ public class LockPatternUtils {
/**
* Resumes a call in progress. Typically launched from the EmergencyCall button
* on various lockscreens.
- *
- * @return true if we were able to tell InCallScreen to show.
*/
- public boolean resumeCall() {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
- try {
- if (phone != null && phone.showCallScreen()) {
- return true;
- }
- } catch (RemoteException e) {
- // What can we do?
- }
- return false;
+ public void resumeCall() {
+ TelephonyManager telephonyManager =
+ (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ telephonyManager.showCallScreen();
}
private void finishBiometricWeak() {
diff --git a/core/java/com/android/internal/widget/LockPatternUtilsCache.java b/core/java/com/android/internal/widget/LockPatternUtilsCache.java
index 550aa6d..624f67c 100644
--- a/core/java/com/android/internal/widget/LockPatternUtilsCache.java
+++ b/core/java/com/android/internal/widget/LockPatternUtilsCache.java
@@ -28,6 +28,11 @@ import android.util.ArrayMap;
*/
public class LockPatternUtilsCache implements ILockSettings {
+ private static final String HAS_LOCK_PATTERN_CACHE_KEY
+ = "LockPatternUtils.Cache.HasLockPatternCacheKey";
+ private static final String HAS_LOCK_PASSWORD_CACHE_KEY
+ = "LockPatternUtils.Cache.HasLockPasswordCacheKey";
+
private static LockPatternUtilsCache sInstance;
private final ILockSettings mService;
@@ -109,7 +114,9 @@ public class LockPatternUtilsCache implements ILockSettings {
@Override
public void setLockPattern(String pattern, int userId) throws RemoteException {
+ invalidateCache(HAS_LOCK_PATTERN_CACHE_KEY, userId);
mService.setLockPattern(pattern, userId);
+ putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, pattern != null);
}
@Override
@@ -119,7 +126,9 @@ public class LockPatternUtilsCache implements ILockSettings {
@Override
public void setLockPassword(String password, int userId) throws RemoteException {
+ invalidateCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId);
mService.setLockPassword(password, userId);
+ putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, password != null);
}
@Override
@@ -134,12 +143,24 @@ public class LockPatternUtilsCache implements ILockSettings {
@Override
public boolean havePattern(int userId) throws RemoteException {
- return mService.havePattern(userId);
+ Object value = peekCache(HAS_LOCK_PATTERN_CACHE_KEY, userId);
+ if (value instanceof Boolean) {
+ return (boolean) value;
+ }
+ boolean result = mService.havePattern(userId);
+ putCache(HAS_LOCK_PATTERN_CACHE_KEY, userId, result);
+ return result;
}
@Override
public boolean havePassword(int userId) throws RemoteException {
- return mService.havePassword(userId);
+ Object value = peekCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId);
+ if (value instanceof Boolean) {
+ return (boolean) value;
+ }
+ boolean result = mService.havePassword(userId);
+ putCache(HAS_LOCK_PASSWORD_CACHE_KEY, userId, result);
+ return result;
}
@Override
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 36ed344..d841d53 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -57,6 +57,7 @@ public class LockPatternView extends View {
private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
private static final boolean PROFILE_DRAWING = false;
+ private final CellState[][] mCellStates;
private boolean mDrawingProfilingStarted = false;
private Paint mPaint = new Paint();
@@ -187,6 +188,12 @@ public class LockPatternView extends View {
}
}
+ public static class CellState {
+ public float scale = 1.0f;
+ public float translateY = 0.0f;
+ public float alpha = 1.0f;
+ }
+
/**
* How to display the current pattern.
*/
@@ -296,6 +303,18 @@ public class LockPatternView extends View {
mBitmapHeight = Math.max(mBitmapHeight, bitmap.getHeight());
}
+ mPaint.setFilterBitmap(true);
+
+ mCellStates = new CellState[3][3];
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ mCellStates[i][j] = new CellState();
+ }
+ }
+ }
+
+ public CellState[][] getCellStates() {
+ return mCellStates;
}
private Bitmap getBitmapFor(int resId) {
@@ -873,18 +892,22 @@ public class LockPatternView extends View {
//float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2);
for (int j = 0; j < 3; j++) {
float leftX = paddingLeft + j * squareWidth;
- drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);
+ float scale = mCellStates[i][j].scale;
+ mPaint.setAlpha((int) (mCellStates[i][j].alpha * 255));
+ float translationY = mCellStates[i][j].translateY;
+ drawCircle(canvas, (int) leftX, (int) topY + translationY, scale, drawLookup[i][j]);
}
}
+ // Reset the alpha to draw normally
+ mPaint.setAlpha(255);
+
// TODO: the path should be created and cached every time we hit-detect a cell
// only the last segment of the path should be computed here
// draw the path of the pattern (unless we are in stealth mode)
final boolean drawPath = !mInStealthMode;
// draw the arrows associated with the path (unless we are in stealth mode)
- boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
- mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
if (drawPath) {
for (int i = 0; i < count - 1; i++) {
Cell cell = pattern.get(i);
@@ -898,7 +921,8 @@ public class LockPatternView extends View {
}
float leftX = paddingLeft + cell.column * squareWidth;
- float topY = paddingTop + cell.row * squareHeight;
+ float topY = paddingTop + cell.row * squareHeight
+ + mCellStates[cell.row][cell.column].translateY;
drawArrow(canvas, leftX, topY, cell, next);
}
@@ -919,6 +943,9 @@ public class LockPatternView extends View {
float centerX = getCenterXForColumn(cell.column);
float centerY = getCenterYForRow(cell.row);
+
+ // Respect translation in animation
+ centerY += mCellStates[cell.row][cell.column].translateY;
if (i == 0) {
currentPath.moveTo(centerX, centerY);
} else {
@@ -933,8 +960,6 @@ public class LockPatternView extends View {
}
canvas.drawPath(currentPath, mPathPaint);
}
-
- mPaint.setFilterBitmap(oldFlag); // restore default flag
}
private void drawArrow(Canvas canvas, float leftX, float topY, Cell start, Cell end) {
@@ -979,7 +1004,8 @@ public class LockPatternView extends View {
* @param topY
* @param partOfPattern Whether this circle is part of the pattern.
*/
- private void drawCircle(Canvas canvas, int leftX, int topY, boolean partOfPattern) {
+ private void drawCircle(Canvas canvas, float leftX, float topY, float scale,
+ boolean partOfPattern) {
Bitmap outerCircle;
Bitmap innerCircle;
@@ -1019,7 +1045,7 @@ public class LockPatternView extends View {
mCircleMatrix.setTranslate(leftX + offsetX, topY + offsetY);
mCircleMatrix.preTranslate(mBitmapWidth/2, mBitmapHeight/2);
- mCircleMatrix.preScale(sx, sy);
+ mCircleMatrix.preScale(sx * scale, sy * scale);
mCircleMatrix.preTranslate(-mBitmapWidth/2, -mBitmapHeight/2);
canvas.drawBitmap(outerCircle, mCircleMatrix, mPaint);
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index f90aaea..b8dc307 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -132,6 +132,16 @@ public class ToolbarWidgetWrapper implements DecorToolbar {
mToolbar.setContentInsetsRelative(contentInsetStart, contentInsetEnd);
}
+ final int titleTextStyle = a.getResourceId(R.styleable.ActionBar_titleTextStyle, 0);
+ if (titleTextStyle != 0) {
+ mToolbar.setTitleTextAppearance(mToolbar.getContext(), titleTextStyle);
+ }
+
+ final int subtitleTextStyle = a.getResourceId(R.styleable.ActionBar_subtitleTextStyle, 0);
+ if (subtitleTextStyle != 0) {
+ mToolbar.setSubtitleTextAppearance(mToolbar.getContext(), subtitleTextStyle);
+ }
+
a.recycle();
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index fedb1b2..a2b1ed9 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -23,6 +23,11 @@
#define ENCODING_PCM_16BIT 2
#define ENCODING_PCM_8BIT 3
#define ENCODING_PCM_FLOAT 4
+#define ENCODING_INVALID 0
+#define ENCODING_DEFAULT 1
+
+#define CHANNEL_INVALID 0
+#define CHANNEL_OUT_DEFAULT 1
static inline audio_format_t audioFormatToNative(int audioFormat)
{
@@ -33,9 +38,58 @@ static inline audio_format_t audioFormatToNative(int audioFormat)
return AUDIO_FORMAT_PCM_8_BIT;
case ENCODING_PCM_FLOAT:
return AUDIO_FORMAT_PCM_FLOAT;
+ case ENCODING_DEFAULT:
+ return AUDIO_FORMAT_DEFAULT;
default:
return AUDIO_FORMAT_INVALID;
}
}
+static inline int audioFormatFromNative(audio_format_t nativeFormat)
+{
+ switch (nativeFormat) {
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return ENCODING_PCM_16BIT;
+ case AUDIO_FORMAT_PCM_8_BIT:
+ return ENCODING_PCM_8BIT;
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return ENCODING_PCM_FLOAT;
+ case AUDIO_FORMAT_DEFAULT:
+ return ENCODING_DEFAULT;
+ default:
+ return ENCODING_INVALID;
+ }
+}
+
+static inline audio_channel_mask_t outChannelMaskToNative(int channelMask)
+{
+ switch (channelMask) {
+ case CHANNEL_OUT_DEFAULT:
+ case CHANNEL_INVALID:
+ return AUDIO_CHANNEL_NONE;
+ default:
+ return (audio_channel_mask_t)(channelMask>>2);
+ }
+}
+
+static inline int outChannelMaskFromNative(audio_channel_mask_t nativeMask)
+{
+ switch (nativeMask) {
+ case AUDIO_CHANNEL_NONE:
+ return CHANNEL_OUT_DEFAULT;
+ default:
+ return (int)nativeMask<<2;
+ }
+}
+
+static inline audio_channel_mask_t inChannelMaskToNative(int channelMask)
+{
+ return (audio_channel_mask_t)channelMask;
+}
+
+static inline int inChannelMaskFromNative(audio_channel_mask_t nativeMask)
+{
+ return (int)nativeMask;
+}
+
#endif // ANDROID_MEDIA_AUDIOFORMAT_H
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index a9a62f8..0f7e140 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -15,7 +15,9 @@
** limitations under the License.
*/
-#define LOG_TAG "AudioSystem"
+//#define LOG_NDEBUG 0
+
+#define LOG_TAG "AudioSystem-JNI"
#include <utils/Log.h>
#include <jni.h>
@@ -26,6 +28,8 @@
#include <system/audio.h>
#include <system/audio_policy.h>
+#include "android_media_AudioFormat.h"
+#include "android_media_AudioErrors.h"
// ----------------------------------------------------------------------------
@@ -33,12 +37,160 @@ using namespace android;
static const char* const kClassPathName = "android/media/AudioSystem";
+static jclass gArrayListClass;
+static struct {
+ jmethodID add;
+} gArrayListMethods;
+
+static jclass gAudioHandleClass;
+static jmethodID gAudioHandleCstor;
+static struct {
+ jfieldID mId;
+} gAudioHandleFields;
+
+static jclass gAudioPortClass;
+static jmethodID gAudioPortCstor;
+static struct {
+ jfieldID mHandle;
+ jfieldID mRole;
+ jfieldID mGains;
+ jfieldID mActiveConfig;
+ // other fields unused by JNI
+} gAudioPortFields;
+
+static jclass gAudioPortConfigClass;
+static jmethodID gAudioPortConfigCstor;
+static struct {
+ jfieldID mPort;
+ jfieldID mSamplingRate;
+ jfieldID mChannelMask;
+ jfieldID mFormat;
+ jfieldID mGain;
+ jfieldID mConfigMask;
+} gAudioPortConfigFields;
+
+static jclass gAudioDevicePortClass;
+static jmethodID gAudioDevicePortCstor;
+
+static jclass gAudioDevicePortConfigClass;
+static jmethodID gAudioDevicePortConfigCstor;
+
+static jclass gAudioMixPortClass;
+static jmethodID gAudioMixPortCstor;
+
+static jclass gAudioMixPortConfigClass;
+static jmethodID gAudioMixPortConfigCstor;
+
+static jclass gAudioGainClass;
+static jmethodID gAudioGainCstor;
+
+static jclass gAudioGainConfigClass;
+static jmethodID gAudioGainConfigCstor;
+static struct {
+ jfieldID mIndex;
+ jfieldID mMode;
+ jfieldID mChannelMask;
+ jfieldID mValues;
+ jfieldID mRampDurationMs;
+ // other fields unused by JNI
+} gAudioGainConfigFields;
+
+static jclass gAudioPatchClass;
+static jmethodID gAudioPatchCstor;
+static struct {
+ jfieldID mHandle;
+ // other fields unused by JNI
+} gAudioPatchFields;
+
+static const char* const kEventHandlerClassPathName =
+ "android/media/AudioPortEventHandler";
+static jmethodID gPostEventFromNative;
+
enum AudioError {
kAudioStatusOk = 0,
kAudioStatusError = 1,
kAudioStatusMediaServerDied = 100
};
+enum {
+ AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1,
+ AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2,
+ AUDIOPORT_EVENT_SERVICE_DIED = 3,
+};
+
+#define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5
+
+// ----------------------------------------------------------------------------
+// ref-counted object for callbacks
+class JNIAudioPortCallback: public AudioSystem::AudioPortCallback
+{
+public:
+ JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
+ ~JNIAudioPortCallback();
+
+ virtual void onAudioPortListUpdate();
+ virtual void onAudioPatchListUpdate();
+ virtual void onServiceDied();
+
+private:
+ void sendEvent(int event);
+
+ jclass mClass; // Reference to AudioPortEventHandlerDelegate class
+ jobject mObject; // Weak ref to AudioPortEventHandlerDelegate Java object to call on
+};
+
+JNIAudioPortCallback::JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
+{
+
+ // Hold onto the SoundTriggerModule class for use in calling the static method
+ // that posts events to the application thread.
+ jclass clazz = env->GetObjectClass(thiz);
+ if (clazz == NULL) {
+ ALOGE("Can't find class %s", kEventHandlerClassPathName);
+ return;
+ }
+ mClass = (jclass)env->NewGlobalRef(clazz);
+
+ // We use a weak reference so the SoundTriggerModule object can be garbage collected.
+ // The reference is only used as a proxy for callbacks.
+ mObject = env->NewGlobalRef(weak_thiz);
+}
+
+JNIAudioPortCallback::~JNIAudioPortCallback()
+{
+ // remove global references
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mObject);
+ env->DeleteGlobalRef(mClass);
+}
+
+void JNIAudioPortCallback::sendEvent(int event)
+{
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+ event, 0, 0, NULL);
+ if (env->ExceptionCheck()) {
+ ALOGW("An exception occurred while notifying an event.");
+ env->ExceptionClear();
+ }
+}
+
+void JNIAudioPortCallback::onAudioPortListUpdate()
+{
+ sendEvent(AUDIOPORT_EVENT_PORT_LIST_UPDATED);
+}
+
+void JNIAudioPortCallback::onAudioPatchListUpdate()
+{
+ sendEvent(AUDIOPORT_EVENT_PATCH_LIST_UPDATED);
+}
+
+void JNIAudioPortCallback::onServiceDied()
+{
+ sendEvent(AUDIOPORT_EVENT_SERVICE_DIED);
+}
+
static int check_AudioSystem_Command(status_t status)
{
switch (status) {
@@ -281,6 +433,854 @@ android_media_AudioSystem_checkAudioFlinger(JNIEnv *env, jobject clazz)
return (jint) check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
}
+
+static bool useInChannelMask(audio_port_type_t type, audio_port_role_t role)
+{
+ return ((type == AUDIO_PORT_TYPE_DEVICE) && (role == AUDIO_PORT_ROLE_SOURCE)) ||
+ ((type == AUDIO_PORT_TYPE_MIX) && (role == AUDIO_PORT_ROLE_SINK));
+}
+
+static void convertAudioGainConfigToNative(JNIEnv *env,
+ struct audio_gain_config *nAudioGainConfig,
+ const jobject jAudioGainConfig,
+ bool useInMask)
+{
+ nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex);
+ nAudioGainConfig->mode = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
+ ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index);
+ jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask);
+ audio_channel_mask_t nMask;
+ if (useInMask) {
+ nMask = inChannelMaskToNative(jMask);
+ ALOGV("convertAudioGainConfigToNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ nMask = outChannelMaskToNative(jMask);
+ ALOGV("convertAudioGainConfigToNative OUT mask java %x native %x", jMask, nMask);
+ }
+ nAudioGainConfig->channel_mask = nMask;
+ nAudioGainConfig->ramp_duration_ms = env->GetIntField(jAudioGainConfig,
+ gAudioGainConfigFields.mRampDurationMs);
+ jintArray jValues = (jintArray)env->GetObjectField(jAudioGainConfig,
+ gAudioGainConfigFields.mValues);
+ int *nValues = env->GetIntArrayElements(jValues, NULL);
+ size_t size = env->GetArrayLength(jValues);
+ memcpy(nAudioGainConfig->values, nValues, size * sizeof(int));
+ env->DeleteLocalRef(jValues);
+}
+
+
+static jint convertAudioPortConfigToNative(JNIEnv *env,
+ struct audio_port_config *nAudioPortConfig,
+ const jobject jAudioPortConfig)
+{
+ jobject jAudioPort = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mPort);
+ jobject jHandle = env->GetObjectField(jAudioPort, gAudioPortFields.mHandle);
+ nAudioPortConfig->id = env->GetIntField(jHandle, gAudioHandleFields.mId);
+ nAudioPortConfig->role = (audio_port_role_t)env->GetIntField(jAudioPort,
+ gAudioPortFields.mRole);
+ if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+ nAudioPortConfig->type = AUDIO_PORT_TYPE_DEVICE;
+ } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+ nAudioPortConfig->type = AUDIO_PORT_TYPE_MIX;
+ } else {
+ env->DeleteLocalRef(jAudioPort);
+ env->DeleteLocalRef(jHandle);
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ ALOGV("convertAudioPortConfigToNative handle %d role %d type %d",
+ nAudioPortConfig->id, nAudioPortConfig->role, nAudioPortConfig->type);
+
+ nAudioPortConfig->sample_rate = env->GetIntField(jAudioPortConfig,
+ gAudioPortConfigFields.mSamplingRate);
+
+ bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role);
+ audio_channel_mask_t nMask;
+ jint jMask = env->GetIntField(jAudioPortConfig,
+ gAudioPortConfigFields.mChannelMask);
+ if (useInMask) {
+ nMask = inChannelMaskToNative(jMask);
+ ALOGV("convertAudioPortConfigToNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ nMask = outChannelMaskToNative(jMask);
+ ALOGV("convertAudioPortConfigToNative OUT mask java %x native %x", jMask, nMask);
+ }
+ nAudioPortConfig->channel_mask = nMask;
+
+ jint jFormat = env->GetIntField(jAudioPortConfig, gAudioPortConfigFields.mFormat);
+ audio_format_t nFormat = audioFormatToNative(jFormat);
+ ALOGV("convertAudioPortConfigToNative format %d native %d", jFormat, nFormat);
+ nAudioPortConfig->format = nFormat;
+ jobject jGain = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mGain);
+ if (jGain != NULL) {
+ convertAudioGainConfigToNative(env, &nAudioPortConfig->gain, jGain, useInMask);
+ env->DeleteLocalRef(jGain);
+ } else {
+ ALOGV("convertAudioPortConfigToNative no gain");
+ nAudioPortConfig->gain.index = -1;
+ }
+ nAudioPortConfig->config_mask = env->GetIntField(jAudioPortConfig,
+ gAudioPortConfigFields.mConfigMask);
+
+ env->DeleteLocalRef(jAudioPort);
+ env->DeleteLocalRef(jHandle);
+ return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+static jint convertAudioPortConfigFromNative(JNIEnv *env,
+ jobject jAudioPort,
+ jobject *jAudioPortConfig,
+ const struct audio_port_config *nAudioPortConfig)
+{
+ jint jStatus = AUDIO_JAVA_SUCCESS;
+ jobject jAudioGainConfig = NULL;
+ jobject jAudioGain = NULL;
+ jintArray jGainValues;
+ bool audioportCreated = false;
+
+ ALOGV("convertAudioPortConfigFromNative jAudioPort %p", jAudioPort);
+
+ if (jAudioPort == NULL) {
+ jobject jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nAudioPortConfig->id);
+
+ ALOGV("convertAudioPortConfigFromNative handle %d is a %s", nAudioPortConfig->id,
+ nAudioPortConfig->type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix");
+
+ if (jHandle == NULL) {
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ // create dummy port and port config objects with just the correct handle
+ // and configuration data. The actual AudioPortConfig objects will be
+ // constructed by java code with correct class type (device, mix etc...)
+ // and reference to AudioPort instance in this client
+ jAudioPort = env->NewObject(gAudioPortClass, gAudioPortCstor,
+ jHandle,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ env->DeleteLocalRef(jHandle);
+ if (jAudioPort == NULL) {
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ ALOGV("convertAudioPortConfigFromNative jAudioPort created for handle %d",
+ nAudioPortConfig->id);
+
+ audioportCreated = true;
+ }
+
+ bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role);
+
+ audio_channel_mask_t nMask;
+ jint jMask;
+
+ int gainIndex = nAudioPortConfig->gain.index;
+ if (gainIndex >= 0) {
+ ALOGV("convertAudioPortConfigFromNative gain found with index %d mode %x",
+ gainIndex, nAudioPortConfig->gain.mode);
+ if (audioportCreated) {
+ ALOGV("convertAudioPortConfigFromNative creating gain");
+ jAudioGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
+ gainIndex,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0);
+ if (jAudioGain == NULL) {
+ ALOGV("convertAudioPortConfigFromNative creating gain FAILED");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ } else {
+ ALOGV("convertAudioPortConfigFromNative reading gain from port");
+ jobjectArray jGains = (jobjectArray)env->GetObjectField(jAudioPort,
+ gAudioPortFields.mGains);
+ if (jGains == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not get gains from port");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ jAudioGain = env->GetObjectArrayElement(jGains, gainIndex);
+ env->DeleteLocalRef(jGains);
+ if (jAudioGain == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not get gain at index %d", gainIndex);
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ }
+ //TODO: replace popcount by audio utils function mask to count
+ int numValues = popcount(nAudioPortConfig->gain.channel_mask);
+ jGainValues = env->NewIntArray(numValues);
+ if (jGainValues == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not create gain values %d", numValues);
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->SetIntArrayRegion(jGainValues, 0, numValues,
+ nAudioPortConfig->gain.values);
+
+ nMask = nAudioPortConfig->gain.channel_mask;
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ jMask = outChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+ }
+
+ jAudioGainConfig = env->NewObject(gAudioGainConfigClass,
+ gAudioGainConfigCstor,
+ gainIndex,
+ jAudioGain,
+ nAudioPortConfig->gain.mode,
+ jMask,
+ jGainValues,
+ nAudioPortConfig->gain.ramp_duration_ms);
+ env->DeleteLocalRef(jGainValues);
+ if (jAudioGainConfig == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not create gain config");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ }
+ jclass clazz;
+ jmethodID methodID;
+ if (audioportCreated) {
+ clazz = gAudioPortConfigClass;
+ methodID = gAudioPortConfigCstor;
+ ALOGV("convertAudioPortConfigFromNative building a generic port config");
+ } else {
+ if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+ clazz = gAudioDevicePortConfigClass;
+ methodID = gAudioDevicePortConfigCstor;
+ ALOGV("convertAudioPortConfigFromNative building a device config");
+ } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+ clazz = gAudioMixPortConfigClass;
+ methodID = gAudioMixPortConfigCstor;
+ ALOGV("convertAudioPortConfigFromNative building a mix config");
+ } else {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ }
+ nMask = nAudioPortConfig->channel_mask;
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ jMask = outChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+ }
+
+ *jAudioPortConfig = env->NewObject(clazz, methodID,
+ jAudioPort,
+ nAudioPortConfig->sample_rate,
+ jMask,
+ audioFormatFromNative(nAudioPortConfig->format),
+ jAudioGainConfig);
+ if (*jAudioPortConfig == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not create new port config");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ } else {
+ ALOGV("convertAudioPortConfigFromNative OK");
+ }
+
+exit:
+ if (audioportCreated) {
+ env->DeleteLocalRef(jAudioPort);
+ if (jAudioGain != NULL) {
+ env->DeleteLocalRef(jAudioGain);
+ }
+ }
+ if (jAudioGainConfig != NULL) {
+ env->DeleteLocalRef(jAudioGainConfig);
+ }
+ return jStatus;
+}
+
+static jint convertAudioPortFromNative(JNIEnv *env,
+ jobject *jAudioPort, const struct audio_port *nAudioPort)
+{
+ jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
+ jintArray jSamplingRates = NULL;
+ jintArray jChannelMasks = NULL;
+ jintArray jFormats = NULL;
+ jobjectArray jGains = NULL;
+ jobject jHandle = NULL;
+ bool useInMask;
+
+ ALOGV("convertAudioPortFromNative id %d role %d type %d",
+ nAudioPort->id, nAudioPort->role, nAudioPort->type);
+
+ jSamplingRates = env->NewIntArray(nAudioPort->num_sample_rates);
+ if (jSamplingRates == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ if (nAudioPort->num_sample_rates) {
+ env->SetIntArrayRegion(jSamplingRates, 0, nAudioPort->num_sample_rates,
+ (jint *)nAudioPort->sample_rates);
+ }
+
+ jChannelMasks = env->NewIntArray(nAudioPort->num_channel_masks);
+ if (jChannelMasks == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ useInMask = useInChannelMask(nAudioPort->type, nAudioPort->role);
+
+ jint jMask;
+ for (size_t j = 0; j < nAudioPort->num_channel_masks; j++) {
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nAudioPort->channel_masks[j]);
+ } else {
+ jMask = outChannelMaskFromNative(nAudioPort->channel_masks[j]);
+ }
+ env->SetIntArrayRegion(jChannelMasks, j, 1, &jMask);
+ }
+
+ jFormats = env->NewIntArray(nAudioPort->num_formats);
+ if (jFormats == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ for (size_t j = 0; j < nAudioPort->num_formats; j++) {
+ jint jFormat = audioFormatFromNative(nAudioPort->formats[j]);
+ env->SetIntArrayRegion(jFormats, j, 1, &jFormat);
+ }
+
+ jGains = env->NewObjectArray(nAudioPort->num_gains,
+ gAudioGainClass, NULL);
+ if (jGains == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ for (size_t j = 0; j < nAudioPort->num_gains; j++) {
+ audio_channel_mask_t nMask = nAudioPort->gains[j].channel_mask;
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ jMask = outChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+ }
+
+ jobject jGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
+ j,
+ nAudioPort->gains[j].mode,
+ jMask,
+ nAudioPort->gains[j].min_value,
+ nAudioPort->gains[j].max_value,
+ nAudioPort->gains[j].default_value,
+ nAudioPort->gains[j].step_value,
+ nAudioPort->gains[j].min_ramp_ms,
+ nAudioPort->gains[j].max_ramp_ms);
+ if (jGain == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->SetObjectArrayElement(jGains, j, jGain);
+ env->DeleteLocalRef(jGain);
+ }
+
+ jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nAudioPort->id);
+ if (jHandle == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ if (nAudioPort->type == AUDIO_PORT_TYPE_DEVICE) {
+ ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type);
+ jstring jAddress = env->NewStringUTF(nAudioPort->ext.device.address);
+ *jAudioPort = env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor,
+ jHandle, jSamplingRates, jChannelMasks, jFormats, jGains,
+ nAudioPort->ext.device.type, jAddress);
+ env->DeleteLocalRef(jAddress);
+ } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) {
+ ALOGV("convertAudioPortFromNative is a mix");
+ *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor,
+ jHandle, nAudioPort->role, jSamplingRates, jChannelMasks,
+ jFormats, jGains);
+ } else {
+ ALOGE("convertAudioPortFromNative unknown nAudioPort type %d", nAudioPort->type);
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ if (*jAudioPort == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ jobject jAudioPortConfig;
+ jStatus = convertAudioPortConfigFromNative(env,
+ *jAudioPort,
+ &jAudioPortConfig,
+ &nAudioPort->active_config);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+
+ env->SetObjectField(*jAudioPort, gAudioPortFields.mActiveConfig, jAudioPortConfig);
+
+exit:
+ if (jSamplingRates != NULL) {
+ env->DeleteLocalRef(jSamplingRates);
+ }
+ if (jChannelMasks != NULL) {
+ env->DeleteLocalRef(jChannelMasks);
+ }
+ if (jFormats != NULL) {
+ env->DeleteLocalRef(jFormats);
+ }
+ if (jGains != NULL) {
+ env->DeleteLocalRef(jGains);
+ }
+ if (jHandle != NULL) {
+ env->DeleteLocalRef(jHandle);
+ }
+
+ return jStatus;
+}
+
+
+static jint
+android_media_AudioSystem_listAudioPorts(JNIEnv *env, jobject clazz,
+ jobject jPorts, jintArray jGeneration)
+{
+ ALOGV("listAudioPorts");
+
+ if (jPorts == NULL) {
+ ALOGE("listAudioPorts NULL AudioPort ArrayList");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jPorts, gArrayListClass)) {
+ ALOGE("listAudioPorts not an arraylist");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ status_t status;
+ unsigned int generation1;
+ unsigned int generation;
+ unsigned int numPorts;
+ jint *nGeneration;
+ struct audio_port *nPorts = NULL;
+ int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
+
+ // get the port count and all the ports until they both return the same generation
+ do {
+ if (attempts-- < 0) {
+ status = TIMED_OUT;
+ break;
+ }
+
+ numPorts = 0;
+ status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE,
+ AUDIO_PORT_TYPE_NONE,
+ &numPorts,
+ NULL,
+ &generation1);
+ if (status != NO_ERROR || numPorts == 0) {
+ ALOGE_IF(status != NO_ERROR, "AudioSystem::listAudioPorts error %d", status);
+ break;
+ }
+ nPorts = (struct audio_port *)realloc(nPorts, numPorts * sizeof(struct audio_port));
+
+ status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE,
+ AUDIO_PORT_TYPE_NONE,
+ &numPorts,
+ nPorts,
+ &generation);
+ ALOGV("listAudioPorts AudioSystem::listAudioPorts numPorts %d generation %d generation1 %d",
+ numPorts, generation, generation1);
+ } while (generation1 != generation && status == NO_ERROR);
+
+ jint jStatus = nativeToJavaStatus(status);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+
+ nGeneration = env->GetIntArrayElements(jGeneration, NULL);
+ if (nGeneration == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ nGeneration[0] = generation1;
+ env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+
+ for (size_t i = 0; i < numPorts; i++) {
+ jobject jAudioPort;
+ jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort);
+ }
+
+exit:
+ free(nPorts);
+ return jStatus;
+}
+
+static int
+android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz,
+ jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks)
+{
+ status_t status;
+ jint jStatus;
+
+ ALOGV("createAudioPatch");
+ if (jPatches == NULL || jSources == NULL || jSinks == NULL) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ if (env->GetArrayLength(jPatches) != 1) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ jint numSources = env->GetArrayLength(jSources);
+ if (numSources == 0 || numSources > AUDIO_PATCH_PORTS_MAX) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ jint numSinks = env->GetArrayLength(jSinks);
+ if (numSinks == 0 || numSinks > AUDIO_PATCH_PORTS_MAX) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ audio_patch_handle_t handle = (audio_patch_handle_t)0;
+ jobject jPatch = env->GetObjectArrayElement(jPatches, 0);
+ jobject jPatchHandle = NULL;
+ if (jPatch != NULL) {
+ if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
+ handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+ }
+
+ struct audio_patch nPatch;
+
+ nPatch.id = handle;
+ nPatch.num_sources = 0;
+ nPatch.num_sinks = 0;
+ jobject jSource = NULL;
+ jobject jSink = NULL;
+
+ for (jint i = 0; i < numSources; i++) {
+ jSource = env->GetObjectArrayElement(jSources, i);
+ if (!env->IsInstanceOf(jSource, gAudioPortConfigClass)) {
+ jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
+ goto exit;
+ }
+ jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource);
+ env->DeleteLocalRef(jSource);
+ jSource = NULL;
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ nPatch.num_sources++;
+ }
+
+ for (jint i = 0; i < numSinks; i++) {
+ jSink = env->GetObjectArrayElement(jSinks, i);
+ if (!env->IsInstanceOf(jSink, gAudioPortConfigClass)) {
+ jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
+ goto exit;
+ }
+ jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink);
+ env->DeleteLocalRef(jSink);
+ jSink = NULL;
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ nPatch.num_sinks++;
+ }
+
+ ALOGV("AudioSystem::createAudioPatch");
+ status = AudioSystem::createAudioPatch(&nPatch, &handle);
+ ALOGV("AudioSystem::createAudioPatch() returned %d hande %d", status, handle);
+
+ jStatus = nativeToJavaStatus(status);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+
+ if (jPatchHandle == NULL) {
+ jPatchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ handle);
+ if (jPatchHandle == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle, jSources, jSinks);
+ if (jPatch == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->SetObjectArrayElement(jPatches, 0, jPatch);
+ } else {
+ env->SetIntField(jPatchHandle, gAudioHandleFields.mId, handle);
+ }
+
+exit:
+ if (jPatchHandle != NULL) {
+ env->DeleteLocalRef(jPatchHandle);
+ }
+ if (jPatch != NULL) {
+ env->DeleteLocalRef(jPatch);
+ }
+ if (jSource != NULL) {
+ env->DeleteLocalRef(jSource);
+ }
+ if (jSink != NULL) {
+ env->DeleteLocalRef(jSink);
+ }
+ return jStatus;
+}
+
+static int
+android_media_AudioSystem_releaseAudioPatch(JNIEnv *env, jobject clazz,
+ jobject jPatch)
+{
+ ALOGV("releaseAudioPatch");
+ if (jPatch == NULL) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ audio_patch_handle_t handle = (audio_patch_handle_t)0;
+ jobject jPatchHandle = NULL;
+ if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
+ handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+ env->DeleteLocalRef(jPatchHandle);
+
+ ALOGV("AudioSystem::releaseAudioPatch");
+ status_t status = AudioSystem::releaseAudioPatch(handle);
+ ALOGV("AudioSystem::releaseAudioPatch() returned %d", status);
+ jint jStatus = nativeToJavaStatus(status);
+ return status;
+}
+
+static jint
+android_media_AudioSystem_listAudioPatches(JNIEnv *env, jobject clazz,
+ jobject jPatches, jintArray jGeneration)
+{
+ ALOGV("listAudioPatches");
+ if (jPatches == NULL) {
+ ALOGE("listAudioPatches NULL AudioPatch ArrayList");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jPatches, gArrayListClass)) {
+ ALOGE("listAudioPatches not an arraylist");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ status_t status;
+ unsigned int generation1;
+ unsigned int generation;
+ unsigned int numPatches;
+ jint *nGeneration;
+ struct audio_patch *nPatches = NULL;
+ jobjectArray jSources = NULL;
+ jobject jSource = NULL;
+ jobjectArray jSinks = NULL;
+ jobject jSink = NULL;
+ jobject jPatch = NULL;
+ int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
+
+ // get the patch count and all the patches until they both return the same generation
+ do {
+ if (attempts-- < 0) {
+ status = TIMED_OUT;
+ break;
+ }
+
+ numPatches = 0;
+ status = AudioSystem::listAudioPatches(&numPatches,
+ NULL,
+ &generation1);
+ if (status != NO_ERROR || numPatches == 0) {
+ ALOGE_IF(status != NO_ERROR, "listAudioPatches AudioSystem::listAudioPatches error %d",
+ status);
+ break;
+ }
+ nPatches = (struct audio_patch *)realloc(nPatches, numPatches * sizeof(struct audio_patch));
+
+ status = AudioSystem::listAudioPatches(&numPatches,
+ nPatches,
+ &generation);
+ ALOGV("listAudioPatches AudioSystem::listAudioPatches numPatches %d generation %d generation1 %d",
+ numPatches, generation, generation1);
+
+ } while (generation1 != generation && status == NO_ERROR);
+
+ jint jStatus = nativeToJavaStatus(status);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+
+ nGeneration = env->GetIntArrayElements(jGeneration, NULL);
+ if (nGeneration == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ nGeneration[0] = generation1;
+ env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+
+ for (size_t i = 0; i < numPatches; i++) {
+ jobject patchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nPatches[i].id);
+ if (patchHandle == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ ALOGV("listAudioPatches patch %d num_sources %d num_sinks %d",
+ i, nPatches[i].num_sources, nPatches[i].num_sinks);
+
+ env->SetIntField(patchHandle, gAudioHandleFields.mId, nPatches[i].id);
+
+ // load sources
+ jSources = env->NewObjectArray(nPatches[i].num_sources,
+ gAudioPortConfigClass, NULL);
+ if (jSources == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ for (size_t j = 0; j < nPatches[i].num_sources; j++) {
+ jStatus = convertAudioPortConfigFromNative(env,
+ NULL,
+ &jSource,
+ &nPatches[i].sources[j]);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ env->SetObjectArrayElement(jSources, j, jSource);
+ env->DeleteLocalRef(jSource);
+ jSource = NULL;
+ ALOGV("listAudioPatches patch %d source %d is a %s handle %d",
+ i, j,
+ nPatches[i].sources[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
+ nPatches[i].sources[j].id);
+ }
+ // load sinks
+ jSinks = env->NewObjectArray(nPatches[i].num_sinks,
+ gAudioPortConfigClass, NULL);
+ if (jSinks == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ for (size_t j = 0; j < nPatches[i].num_sinks; j++) {
+ jStatus = convertAudioPortConfigFromNative(env,
+ NULL,
+ &jSink,
+ &nPatches[i].sinks[j]);
+
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ env->SetObjectArrayElement(jSinks, j, jSink);
+ env->DeleteLocalRef(jSink);
+ jSink = NULL;
+ ALOGV("listAudioPatches patch %d sink %d is a %s handle %d",
+ i, j,
+ nPatches[i].sinks[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
+ nPatches[i].sinks[j].id);
+ }
+
+ jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor,
+ patchHandle, jSources, jSinks);
+ env->DeleteLocalRef(jSources);
+ jSources = NULL;
+ env->DeleteLocalRef(jSinks);
+ jSinks = NULL;
+ if (jPatch == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch);
+ env->DeleteLocalRef(jPatch);
+ jPatch = NULL;
+ }
+
+exit:
+ if (jSources != NULL) {
+ env->DeleteLocalRef(jSources);
+ }
+ if (jSource != NULL) {
+ env->DeleteLocalRef(jSource);
+ }
+ if (jSinks != NULL) {
+ env->DeleteLocalRef(jSinks);
+ }
+ if (jSink != NULL) {
+ env->DeleteLocalRef(jSink);
+ }
+ if (jPatch != NULL) {
+ env->DeleteLocalRef(jPatch);
+ }
+ free(nPatches);
+ return jStatus;
+}
+
+static jint
+android_media_AudioSystem_setAudioPortConfig(JNIEnv *env, jobject clazz,
+ jobject jAudioPortConfig)
+{
+ ALOGV("setAudioPortConfig");
+ if (jAudioPortConfig == NULL) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ struct audio_port_config nAudioPortConfig;
+ jint jStatus = convertAudioPortConfigToNative(env, &nAudioPortConfig, jAudioPortConfig);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+ status_t status = AudioSystem::setAudioPortConfig(&nAudioPortConfig);
+ ALOGV("AudioSystem::setAudioPortConfig() returned %d", status);
+ jStatus = nativeToJavaStatus(status);
+ return jStatus;
+}
+
+static void
+android_media_AudioSystem_eventHandlerSetup(JNIEnv *env, jobject thiz, jobject weak_this)
+{
+ ALOGV("eventHandlerSetup");
+
+ sp<JNIAudioPortCallback> callback = new JNIAudioPortCallback(env, thiz, weak_this);
+
+ AudioSystem::setAudioPortCallback(callback);
+}
+
+static void
+android_media_AudioSystem_eventHandlerFinalize(JNIEnv *env, jobject thiz)
+{
+ ALOGV("eventHandlerFinalize");
+
+ sp<JNIAudioPortCallback> callback;
+
+ AudioSystem::setAudioPortCallback(callback);
+}
+
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
@@ -309,12 +1309,123 @@ static JNINativeMethod gMethods[] = {
{"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency},
{"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice},
{"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger},
+ {"listAudioPorts", "(Ljava/util/ArrayList;[I)I",
+ (void *)android_media_AudioSystem_listAudioPorts},
+ {"createAudioPatch", "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
+ (void *)android_media_AudioSystem_createAudioPatch},
+ {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
+ (void *)android_media_AudioSystem_releaseAudioPatch},
+ {"listAudioPatches", "(Ljava/util/ArrayList;[I)I",
+ (void *)android_media_AudioSystem_listAudioPatches},
+ {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
+ (void *)android_media_AudioSystem_setAudioPortConfig},
+};
+
+
+static JNINativeMethod gEventHandlerMethods[] = {
+ {"native_setup",
+ "(Ljava/lang/Object;)V",
+ (void *)android_media_AudioSystem_eventHandlerSetup},
+ {"native_finalize",
+ "()V",
+ (void *)android_media_AudioSystem_eventHandlerFinalize},
};
int register_android_media_AudioSystem(JNIEnv *env)
{
+
+ jclass arrayListClass = env->FindClass("java/util/ArrayList");
+ gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass);
+ gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
+
+ jclass audioHandleClass = env->FindClass("android/media/AudioHandle");
+ gAudioHandleClass = (jclass) env->NewGlobalRef(audioHandleClass);
+ gAudioHandleCstor = env->GetMethodID(audioHandleClass, "<init>", "(I)V");
+ gAudioHandleFields.mId = env->GetFieldID(audioHandleClass, "mId", "I");
+
+ jclass audioPortClass = env->FindClass("android/media/AudioPort");
+ gAudioPortClass = (jclass) env->NewGlobalRef(audioPortClass);
+ gAudioPortCstor = env->GetMethodID(audioPortClass, "<init>",
+ "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V");
+ gAudioPortFields.mHandle = env->GetFieldID(audioPortClass, "mHandle",
+ "Landroid/media/AudioHandle;");
+ gAudioPortFields.mRole = env->GetFieldID(audioPortClass, "mRole", "I");
+ gAudioPortFields.mGains = env->GetFieldID(audioPortClass, "mGains",
+ "[Landroid/media/AudioGain;");
+ gAudioPortFields.mActiveConfig = env->GetFieldID(audioPortClass, "mActiveConfig",
+ "Landroid/media/AudioPortConfig;");
+
+ jclass audioPortConfigClass = env->FindClass("android/media/AudioPortConfig");
+ gAudioPortConfigClass = (jclass) env->NewGlobalRef(audioPortConfigClass);
+ gAudioPortConfigCstor = env->GetMethodID(audioPortConfigClass, "<init>",
+ "(Landroid/media/AudioPort;IIILandroid/media/AudioGainConfig;)V");
+ gAudioPortConfigFields.mPort = env->GetFieldID(audioPortConfigClass, "mPort",
+ "Landroid/media/AudioPort;");
+ gAudioPortConfigFields.mSamplingRate = env->GetFieldID(audioPortConfigClass,
+ "mSamplingRate", "I");
+ gAudioPortConfigFields.mChannelMask = env->GetFieldID(audioPortConfigClass,
+ "mChannelMask", "I");
+ gAudioPortConfigFields.mFormat = env->GetFieldID(audioPortConfigClass, "mFormat", "I");
+ gAudioPortConfigFields.mGain = env->GetFieldID(audioPortConfigClass, "mGain",
+ "Landroid/media/AudioGainConfig;");
+ gAudioPortConfigFields.mConfigMask = env->GetFieldID(audioPortConfigClass, "mConfigMask", "I");
+
+ jclass audioDevicePortConfigClass = env->FindClass("android/media/AudioDevicePortConfig");
+ gAudioDevicePortConfigClass = (jclass) env->NewGlobalRef(audioDevicePortConfigClass);
+ gAudioDevicePortConfigCstor = env->GetMethodID(audioDevicePortConfigClass, "<init>",
+ "(Landroid/media/AudioDevicePort;IIILandroid/media/AudioGainConfig;)V");
+
+ jclass audioMixPortConfigClass = env->FindClass("android/media/AudioMixPortConfig");
+ gAudioMixPortConfigClass = (jclass) env->NewGlobalRef(audioMixPortConfigClass);
+ gAudioMixPortConfigCstor = env->GetMethodID(audioMixPortConfigClass, "<init>",
+ "(Landroid/media/AudioMixPort;IIILandroid/media/AudioGainConfig;)V");
+
+ jclass audioDevicePortClass = env->FindClass("android/media/AudioDevicePort");
+ gAudioDevicePortClass = (jclass) env->NewGlobalRef(audioDevicePortClass);
+ gAudioDevicePortCstor = env->GetMethodID(audioDevicePortClass, "<init>",
+ "(Landroid/media/AudioHandle;[I[I[I[Landroid/media/AudioGain;ILjava/lang/String;)V");
+
+ jclass audioMixPortClass = env->FindClass("android/media/AudioMixPort");
+ gAudioMixPortClass = (jclass) env->NewGlobalRef(audioMixPortClass);
+ gAudioMixPortCstor = env->GetMethodID(audioMixPortClass, "<init>",
+ "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V");
+
+ jclass audioGainClass = env->FindClass("android/media/AudioGain");
+ gAudioGainClass = (jclass) env->NewGlobalRef(audioGainClass);
+ gAudioGainCstor = env->GetMethodID(audioGainClass, "<init>", "(IIIIIIIII)V");
+
+ jclass audioGainConfigClass = env->FindClass("android/media/AudioGainConfig");
+ gAudioGainConfigClass = (jclass) env->NewGlobalRef(audioGainConfigClass);
+ gAudioGainConfigCstor = env->GetMethodID(audioGainConfigClass, "<init>",
+ "(ILandroid/media/AudioGain;II[II)V");
+ gAudioGainConfigFields.mIndex = env->GetFieldID(gAudioGainConfigClass, "mIndex", "I");
+ gAudioGainConfigFields.mMode = env->GetFieldID(audioGainConfigClass, "mMode", "I");
+ gAudioGainConfigFields.mChannelMask = env->GetFieldID(audioGainConfigClass, "mChannelMask",
+ "I");
+ gAudioGainConfigFields.mValues = env->GetFieldID(audioGainConfigClass, "mValues", "[I");
+ gAudioGainConfigFields.mRampDurationMs = env->GetFieldID(audioGainConfigClass,
+ "mRampDurationMs", "I");
+
+ jclass audioPatchClass = env->FindClass("android/media/AudioPatch");
+ gAudioPatchClass = (jclass) env->NewGlobalRef(audioPatchClass);
+ gAudioPatchCstor = env->GetMethodID(audioPatchClass, "<init>",
+"(Landroid/media/AudioHandle;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)V");
+ gAudioPatchFields.mHandle = env->GetFieldID(audioPatchClass, "mHandle",
+ "Landroid/media/AudioHandle;");
+
+ jclass eventHandlerClass = env->FindClass(kEventHandlerClassPathName);
+ gPostEventFromNative = env->GetStaticMethodID(eventHandlerClass, "postEventFromNative",
+ "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+
+
AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
- return AndroidRuntime::registerNativeMethods(env,
+ int status = AndroidRuntime::registerNativeMethods(env,
kClassPathName, gMethods, NELEM(gMethods));
+
+ if (status == 0) {
+ status = AndroidRuntime::registerNativeMethods(env,
+ kEventHandlerClassPathName, gEventHandlerMethods, NELEM(gEventHandlerMethods));
+ }
+ return status;
}
diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp
index b2f17de..33a2705 100644
--- a/core/jni/android_view_HardwareLayer.cpp
+++ b/core/jni/android_view_HardwareLayer.cpp
@@ -55,9 +55,7 @@ static jlong android_view_HardwareLayer_createRenderLayer(JNIEnv* env, jobject c
Layer* layer = LayerRenderer::createRenderLayer(width, height);
if (!layer) return 0;
- OpenGLRenderer* renderer = new LayerRenderer(layer);
- renderer->initProperties();
- return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer, renderer) );
+ return reinterpret_cast<jlong>( new DeferredLayerUpdater(layer) );
}
static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject clazz,
@@ -66,18 +64,6 @@ static void android_view_HardwareLayer_onTextureDestroyed(JNIEnv* env, jobject c
layer->backingLayer()->clearTexture();
}
-static jlong android_view_HardwareLayer_detachBackingLayer(JNIEnv* env, jobject clazz,
- jlong layerUpdaterPtr) {
- DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
- return reinterpret_cast<jlong>( layer->detachBackingLayer() );
-}
-
-static void android_view_HardwareLayer_destroyLayerUpdater(JNIEnv* env, jobject clazz,
- jlong layerUpdaterPtr) {
- DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
- delete layer;
-}
-
static jboolean android_view_HardwareLayer_prepare(JNIEnv* env, jobject clazz,
jlong layerUpdaterPtr, jint width, jint height, jboolean isOpaque) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
@@ -157,8 +143,6 @@ static JNINativeMethod gMethods[] = {
{ "nCreateTextureLayer", "()J", (void*) android_view_HardwareLayer_createTextureLayer },
{ "nCreateRenderLayer", "(II)J", (void*) android_view_HardwareLayer_createRenderLayer },
{ "nOnTextureDestroyed", "(J)V", (void*) android_view_HardwareLayer_onTextureDestroyed },
- { "nDetachBackingLayer", "(J)J", (void*) android_view_HardwareLayer_detachBackingLayer },
- { "nDestroyLayerUpdater", "(J)V", (void*) android_view_HardwareLayer_destroyLayerUpdater },
{ "nPrepare", "(JIIZ)Z", (void*) android_view_HardwareLayer_prepare },
{ "nSetLayerPaint", "(JJ)V", (void*) android_view_HardwareLayer_setLayerPaint },
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index bd016fd..1397131 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -287,11 +287,18 @@ static jboolean android_view_ThreadedRenderer_copyLayerInto(JNIEnv* env, jobject
return proxy->copyLayerInto(layer, bitmap);
}
-static void android_view_ThreadedRenderer_destroyLayer(JNIEnv* env, jobject clazz,
+static void android_view_ThreadedRenderer_pushLayerUpdate(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlong layerPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr);
- proxy->destroyLayer(layer);
+ proxy->pushLayerUpdate(layer);
+}
+
+static void android_view_ThreadedRenderer_cancelLayerUpdate(JNIEnv* env, jobject clazz,
+ jlong proxyPtr, jlong layerPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerPtr);
+ proxy->cancelLayerUpdate(layer);
}
static void android_view_ThreadedRenderer_flushCaches(JNIEnv* env, jobject clazz,
@@ -347,7 +354,8 @@ static JNINativeMethod gMethods[] = {
{ "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer },
{ "nCreateTextureLayer", "(J)J", (void*) android_view_ThreadedRenderer_createTextureLayer },
{ "nCopyLayerInto", "(JJJ)Z", (void*) android_view_ThreadedRenderer_copyLayerInto },
- { "nDestroyLayer", "(JJ)V", (void*) android_view_ThreadedRenderer_destroyLayer },
+ { "nPushLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_pushLayerUpdate },
+ { "nCancelLayerUpdate", "(JJ)V", (void*) android_view_ThreadedRenderer_cancelLayerUpdate },
{ "nFlushCaches", "(JI)V", (void*) android_view_ThreadedRenderer_flushCaches },
{ "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence },
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml
index 4c4f6a4..117774a 100644
--- a/core/res/res/anim/input_method_exit.xml
+++ b/core/res/res/anim/input_method_exit.xml
@@ -17,7 +17,7 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <translate android:fromYDelta="0" android:toYDelta="-20%"
+ <translate android:fromYDelta="0" android:toYDelta="10%"
android:interpolator="@interpolator/accelerate_quint"
android:duration="@android:integer/config_shortAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 4a956d7..7e212be 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -18,10 +18,17 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:background="#ff000000" android:shareInterpolator="false">
+ android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60">
<alpha
- android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true"
- android:interpolator="@interpolator/decelerate_quint"
- android:duration="0"/>
+ android:interpolator="@interpolator/linear_out_slow_in"
+ android:duration="@integer/config_shortAnimTime"/>
+ <scale
+ android:fromXScale="0.95" android:toXScale="1.0"
+ android:fromYScale="0.95" android:toYScale="1.0"
+ android:pivotX="50%" android:pivotY="50%"
+ android:fillEnabled="true" android:fillBefore="true"
+ android:interpolator="@interpolator/linear_out_slow_in"
+ android:duration="@integer/config_shortAnimTime" />
</set> \ No newline at end of file
diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
deleted file mode 100644
index f7a6a65..0000000
--- a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:detachWallpaper="true" android:shareInterpolator="false">
- <alpha
- android:fromAlpha="0.0" android:toAlpha="1.0"
- android:fillEnabled="true" android:fillBefore="true"
- android:interpolator="@interpolator/decelerate_quad"
- android:startOffset="@android:integer/config_mediumAnimTime"
- android:duration="@android:integer/config_shortAnimTime"/>
-</set>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e9d8ccc..f6732d3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1538,6 +1538,7 @@
-->
<string-array translatable="false" name="config_globalActionsList">
<item>power</item>
+ <item>bugreport</item>
<item>users</item>
</string-array>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7dc967c..2d5477c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2252,6 +2252,7 @@
<public type="style" name="Theme.Quantum.NoActionBar.Overscan" />
<public type="style" name="Theme.Quantum.NoActionBar.TranslucentDecor" />
<public type="style" name="Theme.Quantum.Panel" />
+ <public type="style" name="Theme.Quantum.Voice" />
<public type="style" name="Theme.Quantum.Wallpaper" />
<public type="style" name="Theme.Quantum.Wallpaper.NoTitleBar" />
@@ -2268,6 +2269,7 @@
<public type="style" name="Theme.Quantum.Light.NoActionBar.Overscan" />
<public type="style" name="Theme.Quantum.Light.NoActionBar.TranslucentDecor" />
<public type="style" name="Theme.Quantum.Light.Panel" />
+ <public type="style" name="Theme.Quantum.Light.Voice" />
<public type="style" name="ThemeOverlay" />
<public type="style" name="ThemeOverlay.Quantum" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 933063f..a0b3b63 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -169,6 +169,12 @@ please see styles_device_defaults.xml.
<item name="windowExitAnimation">@anim/input_method_exit</item>
</style>
+ <!-- Window animations that are applied to voice activity windows. -->
+ <style name="Animation.VoiceActivity">
+ <item name="windowEnterAnimation">@anim/voice_activity_open_enter</item>
+ <item name="windowExitAnimation">@anim/voice_activity_close_exit</item>
+ </style>
+
<!-- Window animations that are applied to voice interaction overlay windows. -->
<style name="Animation.VoiceInteractionSession">
<item name="windowEnterAnimation">@anim/voice_layer_enter</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6e1629b..6cd7cd2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1679,7 +1679,6 @@
<java-symbol type="anim" name="push_down_out" />
<java-symbol type="anim" name="push_up_in" />
<java-symbol type="anim" name="push_up_out" />
- <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" />
<java-symbol type="anim" name="lock_screen_behind_enter" />
<java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index 484c694..47ba764 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -913,6 +913,22 @@ please see themes_device_defaults.xml.
<item name="windowNoTitle">true</item>
</style>
+ <!-- Quantum theme for an activity that is to be used for voice interaction.
+ This gives the activity a floating dialog style, to incorporate with the
+ system voice experience. -->
+ <style name="Theme.Quantum.Voice" parent="@style/Theme.Quantum.Dialog">
+ <item name="windowAnimationStyle">@style/Animation.VoiceActivity</item>
+ <item name="backgroundDimEnabled">false</item>
+ </style>
+
+ <!-- Quantum light theme for an activity that is to be used for voice interaction.
+ This gives the activity a floating dialog style, to incorporate with the
+ system voice experience. -->
+ <style name="Theme.Quantum.Light.Voice" parent="@style/Theme.Quantum.Light.Dialog">
+ <item name="windowAnimationStyle">@style/Animation.VoiceActivity</item>
+ <item name="backgroundDimEnabled">false</item>
+ </style>
+
<!-- Default theme for quantum style input methods, which is used by the
{@link android.inputmethodservice.InputMethodService} class.
this inherits from Theme.Panel, but sets up IME appropriate animations
@@ -929,7 +945,7 @@ please see themes_device_defaults.xml.
this inherits from Theme.Panel, but sets up appropriate animations
and a few custom attributes. -->
<style name="Theme.Quantum.VoiceInteractionSession" parent="Theme.Quantum.Light.Panel">
- <item name="android:windowAnimationStyle">@android:style/Animation.VoiceInteractionSession</item>
+ <item name="windowAnimationStyle">@style/Animation.VoiceInteractionSession</item>
</style>
<!-- Theme for the search input bar. -->
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
index ec35d85..bf34f1d 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScanFilterTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
-import android.bluetooth.BluetoothLeScanner.ScanResult;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.test.suitebuilder.annotation.SmallTest;
@@ -26,22 +27,21 @@ import junit.framework.TestCase;
/**
* Unit test cases for Bluetooth LE scan filters.
* <p>
- * To run this test, use adb shell am instrument -e class
- * 'android.bluetooth.BluetoothLeScanFilterTest' -w
+ * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanFilterTest' -w
* 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
*/
-public class BluetoothLeScanFilterTest extends TestCase {
+public class ScanFilterTest extends TestCase {
private static final String DEVICE_MAC = "01:02:03:04:05:AB";
private ScanResult mScanResult;
- private BluetoothLeScanFilter.Builder mFilterBuilder;
+ private ScanFilter.Builder mFilterBuilder;
@Override
protected void setUp() throws Exception {
byte[] scanRecord = new byte[] {
0x02, 0x01, 0x1a, // advertising flags
0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids
- 0x04, 0x09, 0x50, 0x65, 0x64, // name
+ 0x04, 0x09, 0x50, 0x65, 0x64, // setName
0x02, 0x0A, (byte) 0xec, // tx power level
0x05, 0x16, 0x0b, 0x11, 0x50, 0x64, // service data
0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
@@ -51,134 +51,135 @@ public class BluetoothLeScanFilterTest extends TestCase {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice(DEVICE_MAC);
mScanResult = new ScanResult(device, scanRecord, -10, 1397545200000000L);
- mFilterBuilder = BluetoothLeScanFilter.newBuilder();
+ mFilterBuilder = new ScanFilter.Builder();
}
@SmallTest
- public void testNameFilter() {
- BluetoothLeScanFilter filter = mFilterBuilder.name("Ped").build();
- assertTrue("name filter fails", filter.matches(mScanResult));
+ public void testsetNameFilter() {
+ ScanFilter filter = mFilterBuilder.setName("Ped").build();
+ assertTrue("setName filter fails", filter.matches(mScanResult));
- filter = mFilterBuilder.name("Pem").build();
- assertFalse("name filter fails", filter.matches(mScanResult));
+ filter = mFilterBuilder.setName("Pem").build();
+ assertFalse("setName filter fails", filter.matches(mScanResult));
}
@SmallTest
public void testDeviceFilter() {
- BluetoothLeScanFilter filter = mFilterBuilder.macAddress(DEVICE_MAC).build();
+ ScanFilter filter = mFilterBuilder.setMacAddress(DEVICE_MAC).build();
assertTrue("device filter fails", filter.matches(mScanResult));
- filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build();
+ filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build();
assertFalse("device filter fails", filter.matches(mScanResult));
}
@SmallTest
- public void testServiceUuidFilter() {
- BluetoothLeScanFilter filter = mFilterBuilder.serviceUuid(
+ public void testsetServiceUuidFilter() {
+ ScanFilter filter = mFilterBuilder.setServiceUuid(
ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB")).build();
assertTrue("uuid filter fails", filter.matches(mScanResult));
- filter = mFilterBuilder.serviceUuid(
+ filter = mFilterBuilder.setServiceUuid(
ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build();
assertFalse("uuid filter fails", filter.matches(mScanResult));
filter = mFilterBuilder
- .serviceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"))
- .serviceUuidMask(ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .setServiceUuid(ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"),
+ ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
.build();
assertTrue("uuid filter fails", filter.matches(mScanResult));
}
@SmallTest
- public void testServiceDataFilter() {
- byte[] serviceData = new byte[] {
+ public void testsetServiceDataFilter() {
+ byte[] setServiceData = new byte[] {
0x0b, 0x11, 0x50, 0x64 };
- BluetoothLeScanFilter filter = mFilterBuilder.serviceData(serviceData).build();
+ ScanFilter filter = mFilterBuilder.setServiceData(setServiceData).build();
assertTrue("service data filter fails", filter.matches(mScanResult));
byte[] nonMatchData = new byte[] {
0x0b, 0x01, 0x50, 0x64 };
- filter = mFilterBuilder.serviceData(nonMatchData).build();
+ filter = mFilterBuilder.setServiceData(nonMatchData).build();
assertFalse("service data filter fails", filter.matches(mScanResult));
byte[] mask = new byte[] {
(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF };
- filter = mFilterBuilder.serviceData(nonMatchData).serviceDataMask(mask).build();
+ filter = mFilterBuilder.setServiceData(nonMatchData, mask).build();
assertTrue("partial service data filter fails", filter.matches(mScanResult));
}
@SmallTest
public void testManufacturerSpecificData() {
- byte[] manufacturerData = new byte[] {
+ byte[] setManufacturerData = new byte[] {
(byte) 0xE0, 0x00, 0x02, 0x15 };
int manufacturerId = 224;
- BluetoothLeScanFilter filter =
- mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build();
- assertTrue("manufacturerData filter fails", filter.matches(mScanResult));
+ ScanFilter filter =
+ mFilterBuilder.setManufacturerData(manufacturerId, setManufacturerData).build();
+ assertTrue("setManufacturerData filter fails", filter.matches(mScanResult));
byte[] nonMatchData = new byte[] {
(byte) 0xF0, 0x00, 0x02, 0x15 };
- filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData).build();
- assertFalse("manufacturerData filter fails", filter.matches(mScanResult));
+ filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData).build();
+ assertFalse("setManufacturerData filter fails", filter.matches(mScanResult));
byte[] mask = new byte[] {
(byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
};
- filter = mFilterBuilder.manufacturerData(manufacturerId, nonMatchData)
- .manufacturerDataMask(mask).build();
- assertTrue("partial manufacturerData filter fails", filter.matches(mScanResult));
+ filter = mFilterBuilder.setManufacturerData(manufacturerId, nonMatchData, mask).build();
+ assertTrue("partial setManufacturerData filter fails", filter.matches(mScanResult));
}
@SmallTest
public void testReadWriteParcel() {
- BluetoothLeScanFilter filter = mFilterBuilder.build();
+ ScanFilter filter = mFilterBuilder.build();
testReadWriteParcelForFilter(filter);
- filter = mFilterBuilder.name("Ped").build();
+ filter = mFilterBuilder.setName("Ped").build();
testReadWriteParcelForFilter(filter);
- filter = mFilterBuilder.macAddress("11:22:33:44:55:66").build();
+ filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build();
testReadWriteParcelForFilter(filter);
- filter = mFilterBuilder.serviceUuid(
+ filter = mFilterBuilder.setServiceUuid(
ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB")).build();
testReadWriteParcelForFilter(filter);
- filter = mFilterBuilder.serviceUuidMask(
+ filter = mFilterBuilder.setServiceUuid(
+ ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"),
ParcelUuid.fromString("FFFFFFF0-FFFF-FFFF-FFFF-FFFFFFFFFFFF")).build();
testReadWriteParcelForFilter(filter);
- byte[] serviceData = new byte[] {
+ byte[] setServiceData = new byte[] {
0x0b, 0x11, 0x50, 0x64 };
- filter = mFilterBuilder.serviceData(serviceData).build();
+ filter = mFilterBuilder.setServiceData(setServiceData).build();
testReadWriteParcelForFilter(filter);
byte[] serviceDataMask = new byte[] {
(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF };
- filter = mFilterBuilder.serviceDataMask(serviceDataMask).build();
+ filter = mFilterBuilder.setServiceData(setServiceData, serviceDataMask).build();
testReadWriteParcelForFilter(filter);
byte[] manufacturerData = new byte[] {
(byte) 0xE0, 0x00, 0x02, 0x15 };
int manufacturerId = 224;
- filter = mFilterBuilder.manufacturerData(manufacturerId, manufacturerData).build();
+ filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData).build();
testReadWriteParcelForFilter(filter);
byte[] manufacturerDataMask = new byte[] {
(byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
};
- filter = mFilterBuilder.manufacturerDataMask(manufacturerDataMask).build();
+ filter = mFilterBuilder.setManufacturerData(manufacturerId, manufacturerData,
+ manufacturerDataMask).build();
testReadWriteParcelForFilter(filter);
}
- private void testReadWriteParcelForFilter(BluetoothLeScanFilter filter) {
+ private void testReadWriteParcelForFilter(ScanFilter filter) {
Parcel parcel = Parcel.obtain();
filter.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
- BluetoothLeScanFilter filterFromParcel =
- BluetoothLeScanFilter.CREATOR.createFromParcel(parcel);
+ ScanFilter filterFromParcel =
+ ScanFilter.CREATOR.createFromParcel(parcel);
System.out.println(filterFromParcel);
assertEquals(filter, filterFromParcel);
}
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
index eb6c419..cece96b 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAdvertiseScanDataTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
+import android.bluetooth.le.ScanRecord;
import android.os.ParcelUuid;
import android.test.suitebuilder.annotation.SmallTest;
@@ -24,13 +25,13 @@ import junit.framework.TestCase;
import java.util.Arrays;
/**
- * Unit test cases for {@link BluetoothLeAdvertiseScanData}.
+ * Unit test cases for {@link ScanRecord}.
* <p>
* To run this test, use adb shell am instrument -e class
- * 'android.bluetooth.BluetoothLeAdvertiseScanDataTest' -w
+ * 'android.bluetooth.ScanRecordTest' -w
* 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
*/
-public class BluetoothLeAdvertiseScanDataTest extends TestCase {
+public class ScanRecordTest extends TestCase {
@SmallTest
public void testParser() {
@@ -43,8 +44,7 @@ public class BluetoothLeAdvertiseScanDataTest extends TestCase {
0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble
};
- BluetoothLeAdvertiseScanData.ScanRecord data = BluetoothLeAdvertiseScanData.ScanRecord
- .getParser().parseFromScanRecord(scanRecord);
+ ScanRecord data = ScanRecord.parseFromBytes(scanRecord);
assertEquals(0x1a, data.getAdvertiseFlags());
ParcelUuid uuid1 = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB");
ParcelUuid uuid2 = ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB");
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java
index 8064ba8..241e88f 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeScannerTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanResultTest.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package android.bluetooth;
+package android.bluetooth.le;
-import android.bluetooth.BluetoothLeScanner.ScanResult;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.test.suitebuilder.annotation.SmallTest;
@@ -25,17 +26,18 @@ import junit.framework.TestCase;
/**
* Unit test cases for Bluetooth LE scans.
* <p>
- * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothLeScannerTest'
- * -w 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
+ * To run this test, use adb shell am instrument -e class 'android.bluetooth.ScanResultTest' -w
+ * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
*/
-public class BluetoothLeScannerTest extends TestCase {
+public class ScanResultTest extends TestCase {
/**
* Test read and write parcel of ScanResult
*/
@SmallTest
public void testScanResultParceling() {
- BluetoothDevice device = new BluetoothDevice("01:02:03:04:05:06");
+ BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(
+ "01:02:03:04:05:06");
byte[] scanRecord = new byte[] {
1, 2, 3 };
int rssi = -10;