Merge "Paste as plain text by Ctrl-Shift-V"
diff --git a/CleanSpec.mk b/CleanSpec.mk
index c35f332..c812b6a 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -228,6 +228,11 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/SystemUI_intermediates/)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/Keyguard_intermediates/)
$(call add-clean-step, rm -f $(OUT_DIR)/target/product/*/system/framework/android.policy.jar)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/bin/inputflinger $(PRODUCT_OUT)/symbols/system/bin/inputflinger)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libinputflingerhost.so $(PRODUCT_OUT)/system/lib64/libinputflingerhost.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libinputflingerhost.so $(PRODUCT_OUT)/symbols/system/lib64/libinputflingerhost.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/lib/libinputflingerhost.so $(PRODUCT_OUT)/obj_arm/lib/libinputflingerhost.so)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinputflingerhost_intermediates $(PRODUCT_OUT)/obj_arm/SHARED_LIBRARIES/libinputflingerhost_intermediates)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 62081ee..a501fa7 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -138,6 +138,7 @@
" am task lock <TASK_ID>\n" +
" am task lock stop\n" +
" am task resizeable <TASK_ID> [true|false]\n" +
+ " am task resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" +
" am get-config\n" +
"\n" +
"am start: start an Activity. Options are:\n" +
@@ -249,11 +250,11 @@
"am stack resize: change <STACK_ID> size and position to <LEFT,TOP,RIGHT,BOTTOM>" +
".\n" +
"\n" +
- "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally" +
- " starting the new stack with [INTENT] if specified. If [INTENT] isn't" +
- " specified and the current stack has more than one task, then the top task" +
- " of the current task will be moved to the new stack. Command will also force" +
- " all current tasks in both stacks to be resizeable." +
+ "am stack split: split <STACK_ID> into 2 stacks <v>ertically or <h>orizontally\n" +
+ " starting the new stack with [INTENT] if specified. If [INTENT] isn't\n" +
+ " specified and the current stack has more than one task, then the top task\n" +
+ " of the current task will be moved to the new stack. Command will also force\n" +
+ " all current tasks in both stacks to be resizeable.\n" +
"\n" +
"am stack list: list all of the activity stacks and their sizes.\n" +
"\n" +
@@ -265,6 +266,10 @@
"\n" +
"am task resizeable: change if <TASK_ID> is resizeable (true) or not (false).\n" +
"\n" +
+ "am task resize: makes sure <TASK_ID> is in a stack with the specified bounds.\n" +
+ " Forces the task to be resizeable and creates a stack if no existing stack\n" +
+ " has the specified bounds.\n" +
+ "\n" +
"am get-config: retrieve the configuration and any recent configurations\n" +
" of the device\n" +
"\n" +
@@ -1742,33 +1747,14 @@
private void runStackResize() throws Exception {
String stackIdStr = nextArgRequired();
int stackId = Integer.valueOf(stackIdStr);
- String leftStr = nextArgRequired();
- int left = Integer.valueOf(leftStr);
- String topStr = nextArgRequired();
- int top = Integer.valueOf(topStr);
- String rightStr = nextArgRequired();
- int right = Integer.valueOf(rightStr);
- String bottomStr = nextArgRequired();
- int bottom = Integer.valueOf(bottomStr);
- if (left < 0) {
- System.err.println("Error: bad left arg: " + leftStr);
- return;
- }
- if (top < 0) {
- System.err.println("Error: bad top arg: " + topStr);
- return;
- }
- if (right <= 0) {
- System.err.println("Error: bad right arg: " + rightStr);
- return;
- }
- if (bottom <= 0) {
- System.err.println("Error: bad bottom arg: " + bottomStr);
+ final Rect bounds = getBounds();
+ if (bounds == null) {
+ System.err.println("Error: invalid input bounds");
return;
}
try {
- mAm.resizeStack(stackId, new Rect(left, top, right, bottom));
+ mAm.resizeStack(stackId, bounds);
} catch (RemoteException e) {
}
}
@@ -1857,6 +1843,8 @@
runTaskLock();
} else if (op.equals("resizeable")) {
runTaskResizeable();
+ } else if (op.equals("resize")) {
+ runTaskResize();
} else {
showError("Error: unknown command '" + op + "'");
return;
@@ -1890,6 +1878,20 @@
}
}
+ private void runTaskResize() throws Exception {
+ final String taskIdStr = nextArgRequired();
+ final int taskId = Integer.valueOf(taskIdStr);
+ final Rect bounds = getBounds();
+ if (bounds == null) {
+ System.err.println("Error: invalid input bounds");
+ return;
+ }
+ try {
+ mAm.resizeTask(taskId, bounds);
+ } catch (RemoteException e) {
+ }
+ }
+
private List<Configuration> getRecentConfigurations(int days) {
IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService(
Context.USAGE_STATS_SERVICE));
@@ -1986,4 +1988,32 @@
}
return fd;
}
+
+ private Rect getBounds() {
+ String leftStr = nextArgRequired();
+ int left = Integer.valueOf(leftStr);
+ String topStr = nextArgRequired();
+ int top = Integer.valueOf(topStr);
+ String rightStr = nextArgRequired();
+ int right = Integer.valueOf(rightStr);
+ String bottomStr = nextArgRequired();
+ int bottom = Integer.valueOf(bottomStr);
+ if (left < 0) {
+ System.err.println("Error: bad left arg: " + leftStr);
+ return null;
+ }
+ if (top < 0) {
+ System.err.println("Error: bad top arg: " + topStr);
+ return null;
+ }
+ if (right <= 0) {
+ System.err.println("Error: bad right arg: " + rightStr);
+ return null;
+ }
+ if (bottom <= 0) {
+ System.err.println("Error: bad bottom arg: " + bottomStr);
+ return null;
+ }
+ return new Rect(left, top, right, bottom);
+ }
}
diff --git a/cmds/bootanimation/AudioPlayer.cpp b/cmds/bootanimation/AudioPlayer.cpp
index 81fe5f8..2932130 100644
--- a/cmds/bootanimation/AudioPlayer.cpp
+++ b/cmds/bootanimation/AudioPlayer.cpp
@@ -305,7 +305,7 @@
exit:
if (pcm)
pcm_close(pcm);
- mCurrentFile->release();
+ delete mCurrentFile;
mCurrentFile = NULL;
return false;
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 1d4de22..bb25ec6 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -179,7 +179,7 @@
// FileMap memory is never released until application exit.
// Release it now as the texture is already loaded and the memory used for
// the packed resource can be released.
- frame.map->release();
+ delete frame.map;
// ensure we can call getPixels(). No need to call unlock, since the
// bitmap will go out of scope when we return from this method.
@@ -446,7 +446,7 @@
}
outString.setTo((char const*)entryMap->getDataPtr(), entryMap->getDataLength());
- entryMap->release();
+ delete entryMap;
return true;
}
diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp
index bbe6eef2..197e36b 100644
--- a/cmds/idmap/scan.cpp
+++ b/cmds/idmap/scan.cpp
@@ -147,20 +147,20 @@
char *buf = new char[uncompLen];
if (NULL == buf) {
ALOGW("%s: failed to allocate %zd byte\n", __FUNCTION__, uncompLen);
- dataMap->release();
+ delete dataMap;
return -1;
}
StreamingZipInflater inflater(dataMap, uncompLen);
if (inflater.read(buf, uncompLen) < 0) {
ALOGW("%s: failed to inflate %zd byte\n", __FUNCTION__, uncompLen);
delete[] buf;
- dataMap->release();
+ delete dataMap;
return -1;
}
int priority = parse_manifest(buf, uncompLen, target_package_name);
delete[] buf;
- dataMap->release();
+ delete dataMap;
return priority;
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 005b1d9..bb307bb 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2313,6 +2313,15 @@
return true;
}
+ case RESIZE_TASK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ Rect r = Rect.CREATOR.createFromParcel(data);
+ resizeTask(taskId, r);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String filename = data.readString();
@@ -5438,6 +5447,20 @@
}
@Override
+ public void resizeTask(int taskId, Rect r) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ r.writeToParcel(data, 0);
+ mRemote.transact(RESIZE_TASK_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1f5a1a0..a7e9413 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -464,6 +464,7 @@
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException;
public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
+ public void resizeTask(int taskId, Rect bounds) throws RemoteException;
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
@@ -808,4 +809,5 @@
int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282;
int SET_TASK_RESIZEABLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+283;
int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+284;
+ int RESIZE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+285;
}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index cd32dae..1d16516 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -73,9 +73,7 @@
* {@link #commit} or {@link #apply} are called.
*
* @param key The name of the preference to modify.
- * @param value The new value for the preference. Supplying {@code null}
- * as the value is equivalent to calling {@link #remove(String)} with
- * this key.
+ * @param value The new value for the preference.
*
* @return Returns a reference to the same Editor object, so you can
* chain put calls together.
diff --git a/core/java/android/content/UndoManager.java b/core/java/android/content/UndoManager.java
index e9ec5a4..e3bc238 100644
--- a/core/java/android/content/UndoManager.java
+++ b/core/java/android/content/UndoManager.java
@@ -20,9 +20,9 @@
import android.os.Parcelable;
import android.os.ParcelableParcel;
import android.text.TextUtils;
+import android.util.ArrayMap;
import java.util.ArrayList;
-import java.util.HashMap;
/**
* Top-level class for managing and interacting with the global undo state for
@@ -54,7 +54,9 @@
* @hide
*/
public class UndoManager {
- private final HashMap<String, UndoOwner> mOwners = new HashMap<String, UndoOwner>();
+ // The common case is a single undo owner (e.g. for a TextView), so default to that capacity.
+ private final ArrayMap<String, UndoOwner> mOwners =
+ new ArrayMap<String, UndoOwner>(1 /* capacity */);
private final ArrayList<UndoState> mUndos = new ArrayList<UndoState>();
private final ArrayList<UndoState> mRedos = new ArrayList<UndoState>();
private int mUpdateCount;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index d52dd30..e303f61 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -205,6 +205,20 @@
public static final int DOZE_WAKE_LOCK = 0x00000040;
/**
+ * Wake lock level: Keep the device awake enough to allow drawing to occur.
+ * <p>
+ * This is used by the window manager to allow applications to draw while the
+ * system is dozing. It currently has no effect unless the power manager is in
+ * the dozing state.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
+ * </p>
+ *
+ * {@hide}
+ */
+ public static final int DRAW_WAKE_LOCK = 0x00000080;
+
+ /**
* Mask for the wake lock level component of a combined wake lock level and flags integer.
*
* @hide
@@ -489,6 +503,7 @@
case FULL_WAKE_LOCK:
case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
case DOZE_WAKE_LOCK:
+ case DRAW_WAKE_LOCK:
break;
default:
throw new IllegalArgumentException("Must specify a valid wake lock level.");
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index bf51ed1..ac6bbb7 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -16,6 +16,10 @@
package android.security;
+import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.OperationResult;
import android.security.KeystoreArguments;
/**
@@ -52,4 +56,19 @@
int reset_uid(int uid);
int sync_uid(int sourceUid, int targetUid);
int password_uid(String password, int uid);
+
+ // Keymaster 0.4 methods
+ int addRngEntropy(in byte[] data);
+ int generateKey(String alias, in KeymasterArguments arguments, int uid, int flags,
+ out KeyCharacteristics characteristics);
+ int getKeyCharacteristics(String alias, in byte[] clientId,
+ in byte[] appId, out KeyCharacteristics characteristics);
+ int importKey(String alias, in KeymasterArguments arguments, int format,
+ in byte[] keyData, int uid, int flags, out KeyCharacteristics characteristics);
+ ExportResult exportKey(String alias, int format, in byte[] clientId, in byte[] appId);
+ OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable,
+ in KeymasterArguments params, out KeymasterArguments operationParams);
+ OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input);
+ OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature);
+ int abort(IBinder handle);
}
diff --git a/core/java/android/security/keymaster/ExportResult.aidl b/core/java/android/security/keymaster/ExportResult.aidl
new file mode 100644
index 0000000..f522355
--- /dev/null
+++ b/core/java/android/security/keymaster/ExportResult.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+/* @hide */
+parcelable ExportResult;
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
new file mode 100644
index 0000000..bb44c03
--- /dev/null
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Class for handling parceling the return values from keymaster's export operation.
+ * @hide
+ */
+public class ExportResult implements Parcelable {
+ public final int resultCode;
+ public final byte[] exportData;
+
+ public static final Parcelable.Creator<ExportResult> CREATOR = new
+ Parcelable.Creator<ExportResult>() {
+ public ExportResult createFromParcel(Parcel in) {
+ return new ExportResult(in);
+ }
+
+ public ExportResult[] newArray(int length) {
+ return new ExportResult[length];
+ }
+ };
+
+ protected ExportResult(Parcel in) {
+ resultCode = in.readInt();
+ exportData = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(resultCode);
+ out.writeByteArray(exportData);
+ }
+};
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.aidl b/core/java/android/security/keymaster/KeyCharacteristics.aidl
new file mode 100644
index 0000000..15014b1
--- /dev/null
+++ b/core/java/android/security/keymaster/KeyCharacteristics.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+/* @hide */
+parcelable KeyCharacteristics;
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java
new file mode 100644
index 0000000..b803a1b
--- /dev/null
+++ b/core/java/android/security/keymaster/KeyCharacteristics.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class KeyCharacteristics implements Parcelable {
+ public KeymasterArguments swEnforced;
+ public KeymasterArguments hwEnforced;
+
+ public static final Parcelable.Creator<KeyCharacteristics> CREATOR = new
+ Parcelable.Creator<KeyCharacteristics>() {
+ public KeyCharacteristics createFromParcel(Parcel in) {
+ return new KeyCharacteristics(in);
+ }
+
+ public KeyCharacteristics[] newArray(int length) {
+ return new KeyCharacteristics[length];
+ }
+ };
+
+ public KeyCharacteristics() {}
+
+ protected KeyCharacteristics(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ swEnforced.writeToParcel(out, flags);
+ hwEnforced.writeToParcel(out, flags);
+ }
+
+ public void readFromParcel(Parcel in) {
+ swEnforced = KeymasterArguments.CREATOR.createFromParcel(in);
+ hwEnforced = KeymasterArguments.CREATOR.createFromParcel(in);
+ }
+}
+
diff --git a/core/java/android/security/keymaster/KeymasterArgument.java b/core/java/android/security/keymaster/KeymasterArgument.java
new file mode 100644
index 0000000..9a1c894
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterArgument.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFormatException;
+
+/**
+ * Base class for the Java side of a Keymaster tagged argument.
+ * <p>
+ * Serialization code for this and subclasses must be kept in sync with system/security/keystore
+ * and with hardware/libhardware/include/hardware/keymaster_defs.h
+ * @hide
+ */
+abstract class KeymasterArgument implements Parcelable {
+ public final int tag;
+
+ public static final Parcelable.Creator<KeymasterArgument> CREATOR = new
+ Parcelable.Creator<KeymasterArgument>() {
+ public KeymasterArgument createFromParcel(Parcel in) {
+ final int pos = in.dataPosition();
+ final int tag = in.readInt();
+ switch (KeymasterDefs.getTagType(tag)) {
+ case KeymasterDefs.KM_ENUM:
+ case KeymasterDefs.KM_ENUM_REP:
+ case KeymasterDefs.KM_INT:
+ case KeymasterDefs.KM_INT_REP:
+ return new KeymasterIntArgument(tag, in);
+ case KeymasterDefs.KM_LONG:
+ return new KeymasterLongArgument(tag, in);
+ case KeymasterDefs.KM_DATE:
+ return new KeymasterDateArgument(tag, in);
+ case KeymasterDefs.KM_BYTES:
+ case KeymasterDefs.KM_BIGNUM:
+ return new KeymasterBlobArgument(tag, in);
+ case KeymasterDefs.KM_BOOL:
+ return new KeymasterBooleanArgument(tag, in);
+ default:
+ throw new ParcelFormatException("Bad tag: " + tag + " at " + pos);
+ }
+ }
+ public KeymasterArgument[] newArray(int size) {
+ return new KeymasterArgument[size];
+ }
+ };
+
+ protected KeymasterArgument(int tag) {
+ this.tag = tag;
+ }
+
+ /**
+ * Writes the value of this argument, if any, to the provided parcel.
+ */
+ public abstract void writeValue(Parcel out);
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(tag);
+ writeValue(out);
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterArguments.aidl b/core/java/android/security/keymaster/KeymasterArguments.aidl
new file mode 100644
index 0000000..7aef5a6
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterArguments.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+/* @hide */
+parcelable KeymasterArguments;
diff --git a/core/java/android/security/keymaster/KeymasterArguments.java b/core/java/android/security/keymaster/KeymasterArguments.java
new file mode 100644
index 0000000..b5fd4bd
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterArguments.java
@@ -0,0 +1,186 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Utility class for the java side of user specified Keymaster arguments.
+ * <p>
+ * Serialization code for this and subclasses must be kept in sync with system/security/keystore
+ * @hide
+ */
+public class KeymasterArguments implements Parcelable {
+ List<KeymasterArgument> mArguments;
+
+ public static final Parcelable.Creator<KeymasterArguments> CREATOR = new
+ Parcelable.Creator<KeymasterArguments>() {
+ public KeymasterArguments createFromParcel(Parcel in) {
+ return new KeymasterArguments(in);
+ }
+ public KeymasterArguments[] newArray(int size) {
+ return new KeymasterArguments[size];
+ }
+ };
+
+ public KeymasterArguments() {
+ mArguments = new ArrayList<KeymasterArgument>();
+ }
+
+ private KeymasterArguments(Parcel in) {
+ mArguments = in.createTypedArrayList(KeymasterArgument.CREATOR);
+ }
+
+ public void addInt(int tag, int value) {
+ mArguments.add(new KeymasterIntArgument(tag, value));
+ }
+
+ public void addBoolean(int tag) {
+ mArguments.add(new KeymasterBooleanArgument(tag));
+ }
+
+ public void addLong(int tag, long value) {
+ mArguments.add(new KeymasterLongArgument(tag, value));
+ }
+
+ public void addBlob(int tag, byte[] value) {
+ mArguments.add(new KeymasterBlobArgument(tag, value));
+ }
+
+ public void addDate(int tag, Date value) {
+ mArguments.add(new KeymasterDateArgument(tag, value));
+ }
+
+ private KeymasterArgument getArgumentByTag(int tag) {
+ for (KeymasterArgument arg : mArguments) {
+ if (arg.tag == tag) {
+ return arg;
+ }
+ }
+ return null;
+ }
+
+ public boolean containsTag(int tag) {
+ return getArgumentByTag(tag) != null;
+ }
+
+ public int getInt(int tag, int defaultValue) {
+ switch (KeymasterDefs.getTagType(tag)) {
+ case KeymasterDefs.KM_ENUM:
+ case KeymasterDefs.KM_INT:
+ break; // Accepted types
+ case KeymasterDefs.KM_INT_REP:
+ case KeymasterDefs.KM_ENUM_REP:
+ throw new IllegalArgumentException("Repeatable tags must use getInts: " + tag);
+ default:
+ throw new IllegalArgumentException("Tag is not an int type: " + tag);
+ }
+ KeymasterArgument arg = getArgumentByTag(tag);
+ if (arg == null) {
+ return defaultValue;
+ }
+ return ((KeymasterIntArgument) arg).value;
+ }
+
+ public long getLong(int tag, long defaultValue) {
+ if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_LONG) {
+ throw new IllegalArgumentException("Tag is not a long type: " + tag);
+ }
+ KeymasterArgument arg = getArgumentByTag(tag);
+ if (arg == null) {
+ return defaultValue;
+ }
+ return ((KeymasterLongArgument) arg).value;
+ }
+
+ public Date getDate(int tag, Date defaultValue) {
+ if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) {
+ throw new IllegalArgumentException("Tag is not a date type: " + tag);
+ }
+ KeymasterArgument arg = getArgumentByTag(tag);
+ if (arg == null) {
+ return defaultValue;
+ }
+ return ((KeymasterDateArgument) arg).date;
+ }
+
+ public boolean getBoolean(int tag, boolean defaultValue) {
+ if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BOOL) {
+ throw new IllegalArgumentException("Tag is not a boolean type: " + tag);
+ }
+ KeymasterArgument arg = getArgumentByTag(tag);
+ if (arg == null) {
+ return defaultValue;
+ }
+ return true;
+ }
+
+ public byte[] getBlob(int tag, byte[] defaultValue) {
+ switch (KeymasterDefs.getTagType(tag)) {
+ case KeymasterDefs.KM_BYTES:
+ case KeymasterDefs.KM_BIGNUM:
+ break; // Allowed types.
+ default:
+ throw new IllegalArgumentException("Tag is not a blob type: " + tag);
+ }
+ KeymasterArgument arg = getArgumentByTag(tag);
+ if (arg == null) {
+ return defaultValue;
+ }
+ return ((KeymasterBlobArgument) arg).blob;
+ }
+
+ public List<Integer> getInts(int tag) {
+ switch (KeymasterDefs.getTagType(tag)) {
+ case KeymasterDefs.KM_INT_REP:
+ case KeymasterDefs.KM_ENUM_REP:
+ break; // Allowed types.
+ default:
+ throw new IllegalArgumentException("Tag is not a repeating type: " + tag);
+ }
+ List<Integer> values = new ArrayList<Integer>();
+ for (KeymasterArgument arg : mArguments) {
+ if (arg.tag == tag) {
+ values.add(((KeymasterIntArgument) arg).value);
+ }
+ }
+ return values;
+ }
+
+ public int size() {
+ return mArguments.size();
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeTypedList(mArguments);
+ }
+
+ public void readFromParcel(Parcel in) {
+ in.readTypedList(mArguments, KeymasterArgument.CREATOR);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterBlobArgument.java b/core/java/android/security/keymaster/KeymasterBlobArgument.java
new file mode 100644
index 0000000..27f1153
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterBlobArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterBlobArgument extends KeymasterArgument {
+ public final byte[] blob;
+
+ public KeymasterBlobArgument(int tag, byte[] blob) {
+ super(tag);
+ this.blob = blob;
+ }
+
+ public KeymasterBlobArgument(int tag, Parcel in) {
+ super(tag);
+ blob = in.createByteArray();
+ }
+
+ @Override
+ public void writeValue(Parcel out) {
+ out.writeByteArray(blob);
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterBooleanArgument.java b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
new file mode 100644
index 0000000..8e17db4
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterBooleanArgument extends KeymasterArgument {
+
+ // Boolean arguments are always true if they exist and false if they don't.
+ public final boolean value = true;
+
+ public KeymasterBooleanArgument(int tag) {
+ super(tag);
+ }
+
+ public KeymasterBooleanArgument(int tag, Parcel in) {
+ super(tag);
+ }
+
+ @Override
+ public void writeValue(Parcel out) {
+ // Do nothing, value is implicit.
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDateArgument.java b/core/java/android/security/keymaster/KeymasterDateArgument.java
new file mode 100644
index 0000000..e8f4055
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterDateArgument.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Date;
+
+/**
+ * @hide
+ */
+class KeymasterDateArgument extends KeymasterArgument {
+ public final Date date;
+
+ public KeymasterDateArgument(int tag, Date date) {
+ super(tag);
+ this.date = date;
+ }
+
+ public KeymasterDateArgument(int tag, Parcel in) {
+ super(tag);
+ date = new Date(in.readLong());
+ }
+
+ @Override
+ public void writeValue(Parcel out) {
+ out.writeLong(date.getTime());
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
new file mode 100644
index 0000000..88cad79
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -0,0 +1,227 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+/**
+ * Class tracking all the keymaster enum values needed for the binder API to keystore.
+ * This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h
+ * See keymaster_defs.h for detailed descriptions of each constant.
+ * @hide
+ */
+public final class KeymasterDefs {
+
+ private KeymasterDefs() {}
+
+ // Tag types.
+ public static final int KM_INVALID = 0 << 28;
+ public static final int KM_ENUM = 1 << 28;
+ public static final int KM_ENUM_REP = 2 << 28;
+ public static final int KM_INT = 3 << 28;
+ public static final int KM_INT_REP = 4 << 28;
+ public static final int KM_LONG = 5 << 28;
+ public static final int KM_DATE = 6 << 28;
+ public static final int KM_BOOL = 7 << 28;
+ public static final int KM_BIGNUM = 8 << 28;
+ public static final int KM_BYTES = 9 << 28;
+
+ // Tag values.
+ public static final int KM_TAG_INVALID = KM_INVALID | 0;
+ public static final int KM_TAG_PURPOSE = KM_ENUM_REP | 1;
+ public static final int KM_TAG_ALGORITHM = KM_ENUM | 2;
+ public static final int KM_TAG_KEY_SIZE = KM_INT | 3;
+ public static final int KM_TAG_BLOCK_MODE = KM_ENUM | 4;
+ public static final int KM_TAG_DIGEST = KM_ENUM | 5;
+ public static final int KM_TAG_MAC_LENGTH = KM_INT | 6;
+ public static final int KM_TAG_PADDING = KM_ENUM | 7;
+ public static final int KM_TAG_RETURN_UNAUTHED = KM_BOOL | 8;
+ public static final int KM_TAG_CALLER_NONCE = KM_BOOL | 9;
+
+ public static final int KM_TAG_RESCOPING_ADD = KM_ENUM_REP | 101;
+ public static final int KM_TAG_RESCOPING_DEL = KM_ENUM_REP | 102;
+ public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
+
+ public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_LONG | 200;
+ public static final int KM_TAG_DSA_GENERATOR = KM_BIGNUM | 201;
+ public static final int KM_TAG_DSA_P = KM_BIGNUM | 202;
+ public static final int KM_TAG_DSA_Q = KM_BIGNUM | 203;
+ public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
+ public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
+ public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
+ public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = KM_INT | 403;
+ public static final int KM_TAG_MAX_USES_PER_BOOT = KM_INT | 404;
+
+ public static final int KM_TAG_ALL_USERS = KM_BOOL | 500;
+ public static final int KM_TAG_USER_ID = KM_INT | 501;
+ public static final int KM_TAG_NO_AUTH_REQUIRED = KM_BOOL | 502;
+ public static final int KM_TAG_USER_AUTH_ID = KM_INT_REP | 503;
+ public static final int KM_TAG_AUTH_TIMEOUT = KM_INT | 504;
+
+ public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
+ public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
+
+ public static final int KM_TAG_APPLICATION_DATA = KM_BYTES | 700;
+ public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
+ public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
+ public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
+ public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
+
+ public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
+ public static final int KM_TAG_NONCE = KM_BYTES | 1001;
+ public static final int KM_TAG_CHUNK_LENGTH = KM_INT | 1002;
+
+ // Algorithm values.
+ public static final int KM_ALGORITHM_RSA = 1;
+ public static final int KM_ALGORITHM_DSA = 2;
+ public static final int KM_ALGORITHM_ECDSA = 3;
+ public static final int KM_ALGORITHM_ECIES = 4;
+ public static final int KM_ALGORITHM_AES = 32;
+ public static final int KM_ALGORITHM_3DES = 33;
+ public static final int KM_ALGORITHM_SKIPJACK = 34;
+ public static final int KM_ALGORITHM_MARS = 48;
+ public static final int KM_ALGORITHM_RC6 = 49;
+ public static final int KM_ALGORITHM_SERPENT = 50;
+ public static final int KM_ALGORITHM_TWOFISH = 51;
+ public static final int KM_ALGORITHM_IDEA = 52;
+ public static final int KM_ALGORITHM_RC5 = 53;
+ public static final int KM_ALGORITHM_CAST5 = 54;
+ public static final int KM_ALGORITHM_BLOWFISH = 55;
+ public static final int KM_ALGORITHM_RC4 = 64;
+ public static final int KM_ALGORITHM_CHACHA20 = 65;
+ public static final int KM_ALGORITHM_HMAC = 128;
+
+ // Block modes.
+ public static final int KM_MODE_FIRST_UNAUTHENTICATED = 1;
+ public static final int KM_MODE_ECB = KM_MODE_FIRST_UNAUTHENTICATED;
+ public static final int KM_MODE_CBC = 2;
+ public static final int KM_MODE_CBC_CTS = 3;
+ public static final int KM_MODE_CTR = 4;
+ public static final int KM_MODE_OFB = 5;
+ public static final int KM_MODE_CFB = 6;
+ public static final int KM_MODE_XTS = 7;
+ public static final int KM_MODE_FIRST_AUTHENTICATED = 32;
+ public static final int KM_MODE_GCM = KM_MODE_FIRST_AUTHENTICATED;
+ public static final int KM_MODE_OCB = 33;
+ public static final int KM_MODE_CCM = 34;
+ public static final int KM_MODE_FIRST_MAC = 128;
+ public static final int KM_MODE_CMAC = KM_MODE_FIRST_MAC;
+ public static final int KM_MODE_POLY1305 = 129;
+
+ // Padding modes.
+ public static final int KM_PAD_NONE = 1;
+ public static final int KM_PAD_RSA_OAEP = 2;
+ public static final int KM_PAD_RSA_PSS = 3;
+ public static final int KM_PAD_RSA_PKCS1_1_5_ENCRYPT = 4;
+ public static final int KM_PAD_RSA_PKCS1_1_5_SIGN = 5;
+ public static final int KM_PAD_ANSI_X923 = 32;
+ public static final int KM_PAD_ISO_10126 = 33;
+ public static final int KM_PAD_ZERO = 64;
+ public static final int KM_PAD_PKCS7 = 65;
+ public static final int KM_PAD_ISO_7816_4 = 66;
+
+ // Digest modes.
+ public static final int KM_DIGEST_NONE = 0;
+ public static final int KM_DIGEST_MD5 = 1;
+ public static final int KM_DIGEST_SHA1 = 2;
+ public static final int KM_DIGEST_SHA_2_224 = 3;
+ public static final int KM_DIGEST_SHA_2_256 = 4;
+ public static final int KM_DIGEST_SHA_2_384 = 5;
+ public static final int KM_DIGEST_SHA_2_512 = 6;
+ public static final int KM_DIGEST_SHA_3_256 = 7;
+ public static final int KM_DIGEST_SHA_3_384 = 8;
+ public static final int KM_DIGEST_SHA_3_512 = 9;
+
+ // Key origins.
+ public static final int KM_ORIGIN_HARDWARE = 0;
+ public static final int KM_ORIGIN_SOFTWARE = 1;
+ public static final int KM_ORIGIN_IMPORTED = 2;
+
+ // Key usability requirements.
+ public static final int KM_BLOB_STANDALONE = 0;
+ public static final int KM_BLOB_REQUIRES_FILE_SYSTEM = 1;
+
+ // Operation Purposes.
+ public static final int KM_PURPOSE_ENCRYPT = 0;
+ public static final int KM_PURPOSE_DECRYPT = 1;
+ public static final int KM_PURPOSE_SIGN = 2;
+ public static final int KM_PURPOSE_VERIFY = 3;
+
+ // Key formats.
+ public static final int KM_KEY_FORMAT_X509 = 0;
+ public static final int KM_KEY_FORMAT_PKCS8 = 1;
+ public static final int KM_KEY_FORMAT_PKCS12 = 2;
+ public static final int KM_KEY_FORMAT_RAW = 3;
+
+ // Error codes.
+ public static final int KM_ERROR_OK = 0;
+ public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
+ public static final int KM_ERROR_UNSUPPORTED_PURPOSE = -2;
+ public static final int KM_ERROR_INCOMPATIBLE_PURPOSE = -3;
+ public static final int KM_ERROR_UNSUPPORTED_ALGORITHM = -4;
+ public static final int KM_ERROR_INCOMPATIBLE_ALGORITHM = -5;
+ public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
+ public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
+ public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
+ public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9;
+ public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
+ public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
+ public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
+ public static final int KM_ERROR_INCOMPATIBLE_DIGEST = -13;
+ public static final int KM_ERROR_INVALID_EXPIRATION_TIME = -14;
+ public static final int KM_ERROR_INVALID_USER_ID = -15;
+ public static final int KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT = -16;
+ public static final int KM_ERROR_UNSUPPORTED_KEY_FORMAT = -17;
+ public static final int KM_ERROR_INCOMPATIBLE_KEY_FORMAT = -18;
+ public static final int KM_ERROR_UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = -19;
+ public static final int KM_ERROR_UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = -20;
+ public static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
+ public static final int KM_ERROR_KEY_EXPORT_OPTIONS_INVALID = -22;
+ public static final int KM_ERROR_DELEGATION_NOT_ALLOWED = -23;
+ public static final int KM_ERROR_KEY_NOT_YET_VALID = -24;
+ public static final int KM_ERROR_KEY_EXPIRED = -25;
+ public static final int KM_ERROR_KEY_USER_NOT_AUTHENTICATED = -26;
+ public static final int KM_ERROR_OUTPUT_PARAMETER_NULL = -27;
+ public static final int KM_ERROR_INVALID_OPERATION_HANDLE = -28;
+ public static final int KM_ERROR_INSUFFICIENT_BUFFER_SPACE = -29;
+ public static final int KM_ERROR_VERIFICATION_FAILED = -30;
+ public static final int KM_ERROR_TOO_MANY_OPERATIONS = -31;
+ public static final int KM_ERROR_UNEXPECTED_NULL_POINTER = -32;
+ public static final int KM_ERROR_INVALID_KEY_BLOB = -33;
+ public static final int KM_ERROR_IMPORTED_KEY_NOT_ENCRYPTED = -34;
+ public static final int KM_ERROR_IMPORTED_KEY_DECRYPTION_FAILED = -35;
+ public static final int KM_ERROR_IMPORTED_KEY_NOT_SIGNED = -36;
+ public static final int KM_ERROR_IMPORTED_KEY_VERIFICATION_FAILED = -37;
+ public static final int KM_ERROR_INVALID_ARGUMENT = -38;
+ public static final int KM_ERROR_UNSUPPORTED_TAG = -39;
+ public static final int KM_ERROR_INVALID_TAG = -40;
+ public static final int KM_ERROR_MEMORY_ALLOCATION_FAILED = -41;
+ public static final int KM_ERROR_INVALID_RESCOPING = -42;
+ public static final int KM_ERROR_INVALID_DSA_PARAMS = -43;
+ public static final int KM_ERROR_IMPORT_PARAMETER_MISMATCH = -44;
+ public static final int KM_ERROR_SECURE_HW_ACCESS_DENIED = -45;
+ public static final int KM_ERROR_OPERATION_CANCELLED = -46;
+ public static final int KM_ERROR_CONCURRENT_ACCESS_CONFLICT = -47;
+ public static final int KM_ERROR_SECURE_HW_BUSY = -48;
+ public static final int KM_ERROR_SECURE_HW_COMMUNICATION_FAILED = -49;
+ public static final int KM_ERROR_UNSUPPORTED_EC_FIELD = -50;
+ public static final int KM_ERROR_UNIMPLEMENTED = -100;
+ public static final int KM_ERROR_VERSION_MISMATCH = -101;
+ public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
+
+ public static int getTagType(int tag) {
+ return tag & (0xF << 28);
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterIntArgument.java b/core/java/android/security/keymaster/KeymasterIntArgument.java
new file mode 100644
index 0000000..71797ae
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterIntArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterIntArgument extends KeymasterArgument {
+ public final int value;
+
+ public KeymasterIntArgument(int tag, int value) {
+ super(tag);
+ this.value = value;
+ }
+
+ public KeymasterIntArgument(int tag, Parcel in) {
+ super(tag);
+ value = in.readInt();
+ }
+
+ @Override
+ public void writeValue(Parcel out) {
+ out.writeInt(value);
+ }
+}
diff --git a/core/java/android/security/keymaster/KeymasterLongArgument.java b/core/java/android/security/keymaster/KeymasterLongArgument.java
new file mode 100644
index 0000000..781b1ab
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterLongArgument.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+class KeymasterLongArgument extends KeymasterArgument {
+ public final long value;
+
+ public KeymasterLongArgument(int tag, long value) {
+ super(tag);
+ this.value = value;
+ }
+
+ public KeymasterLongArgument(int tag, Parcel in) {
+ super(tag);
+ value = in.readLong();
+ }
+
+ @Override
+ public void writeValue(Parcel out) {
+ out.writeLong(value);
+ }
+}
diff --git a/core/java/android/security/keymaster/OperationResult.aidl b/core/java/android/security/keymaster/OperationResult.aidl
new file mode 100644
index 0000000..699e8d0
--- /dev/null
+++ b/core/java/android/security/keymaster/OperationResult.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+/* @hide */
+parcelable OperationResult;
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
new file mode 100644
index 0000000..ad54c96
--- /dev/null
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2015, 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.security.keymaster;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class for handling the parceling of return values from keymaster crypto operations
+ * (begin/update/finish).
+ * @hide
+ */
+public class OperationResult implements Parcelable {
+ public final int resultCode;
+ public final IBinder token;
+ public final int inputConsumed;
+ public final byte[] output;
+
+ public static final Parcelable.Creator<OperationResult> CREATOR = new
+ Parcelable.Creator<OperationResult>() {
+ public OperationResult createFromParcel(Parcel in) {
+ return new OperationResult(in);
+ }
+
+ public OperationResult[] newArray(int length) {
+ return new OperationResult[length];
+ }
+ };
+
+ protected OperationResult(Parcel in) {
+ resultCode = in.readInt();
+ token = in.readStrongBinder();
+ inputConsumed = in.readInt();
+ output = in.createByteArray();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(resultCode);
+ out.writeStrongBinder(token);
+ out.writeInt(inputConsumed);
+ out.writeByteArray(output);
+ }
+}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index d08ab46..fc0148f 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -194,4 +194,19 @@
void onRectangleOnScreenRequested(IBinder token, in Rect rectangle);
IWindowId getWindowId(IBinder window);
+
+ /**
+ * When the system is dozing in a low-power partially suspended state, pokes a short
+ * lived wake lock and ensures that the display is ready to accept the next frame
+ * of content drawn in the window.
+ *
+ * This mechanism is bound to the window rather than to the display manager or the
+ * power manager so that the system can ensure that the window is actually visible
+ * and prevent runaway applications from draining the battery. This is similar to how
+ * FLAG_KEEP_SCREEN_ON works.
+ *
+ * This method is synchronous because it may need to acquire a wake lock before returning.
+ * The assumption is that this method will be called rather infrequently.
+ */
+ void pokeDrawLock(IBinder window);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f392682..a5fa5ed 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -47,6 +47,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -838,6 +839,7 @@
final int newDisplayState = mDisplay.getState();
if (oldDisplayState != newDisplayState) {
mAttachInfo.mDisplayState = newDisplayState;
+ pokeDrawLockIfNeeded();
if (oldDisplayState != Display.STATE_UNKNOWN) {
final int oldScreenState = toViewScreenState(oldDisplayState);
final int newScreenState = toViewScreenState(newDisplayState);
@@ -868,6 +870,19 @@
}
};
+ void pokeDrawLockIfNeeded() {
+ final int displayState = mAttachInfo.mDisplayState;
+ if (mView != null && mAdded && mTraversalScheduled
+ && (displayState == Display.STATE_DOZE
+ || displayState == Display.STATE_DOZE_SUSPEND)) {
+ try {
+ mWindowSession.pokeDrawLock(mWindow);
+ } catch (RemoteException ex) {
+ // System server died, oh well.
+ }
+ }
+ }
+
@Override
public void requestFitSystemWindows() {
checkThread();
@@ -1042,6 +1057,7 @@
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
+ pokeDrawLockIfNeeded();
}
}
@@ -3084,17 +3100,6 @@
return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);
}
- private static void forceLayout(View view) {
- view.forceLayout();
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
- final int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- forceLayout(group.getChildAt(i));
- }
- }
- }
-
private final static int MSG_INVALIDATE = 1;
private final static int MSG_INVALIDATE_RECT = 2;
private final static int MSG_DIE = 3;
@@ -3228,10 +3233,6 @@
mReportNextDraw = true;
}
- if (mView != null) {
- forceLayout(mView);
- }
-
requestLayout();
}
break;
@@ -3246,9 +3247,6 @@
mWinFrame.top = t;
mWinFrame.bottom = t + h;
- if (mView != null) {
- forceLayout(mView);
- }
requestLayout();
}
break;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 51c6a66..4c0520e 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1852,6 +1852,12 @@
<item>users</item>
</string-array>
+ <!-- Number of milliseconds to hold a wake lock to ensure that drawing is fully
+ flushed to the display while dozing. This value needs to be large enough
+ to account for processing and rendering time plus a frame or two of latency
+ in the display pipeline plus some slack just to be sure. -->
+ <integer name="config_drawLockTimeoutMillis">120</integer>
+
<!-- default telephony hardware configuration for this platform.
-->
<!-- this string array should be overridden by the device to present a list
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c418dc3..d240047 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -31,7 +31,7 @@
<integer name="max_action_buttons">2</integer>
<dimen name="toast_y_offset">64dip</dimen>
<!-- Height of the status bar -->
- <dimen name="status_bar_height">25dip</dimen>
+ <dimen name="status_bar_height">24dp</dimen>
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_height">48dp</dimen>
<!-- Height of the bottom navigation bar in portrait; often the same as @dimen/navigation_bar_height -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 37c6d29..615f445 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -345,6 +345,7 @@
<java-symbol type="integer" name="config_bluetooth_max_advertisers" />
<java-symbol type="integer" name="config_bluetooth_max_scan_filters" />
<java-symbol type="integer" name="config_cursorWindowSize" />
+ <java-symbol type="integer" name="config_drawLockTimeoutMillis" />
<java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
<java-symbol type="integer" name="config_extraFreeKbytesAdjust" />
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 70693f2..c4794d9 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -173,7 +173,7 @@
@Override
public void setColorFilter(ColorFilter cf) {
- mDrawableContainerState.mHasColorFilter = (cf != null);
+ mDrawableContainerState.mHasColorFilter = true;
if (mDrawableContainerState.mColorFilter != cf) {
mDrawableContainerState.mColorFilter = cf;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index e753a7c..bfbf028 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -18,8 +18,14 @@
import com.android.org.conscrypt.NativeCrypto;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.security.keymaster.ExportResult;
+import android.security.keymaster.KeyCharacteristics;
+import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.OperationResult;
import android.util.Log;
import java.util.Locale;
@@ -58,6 +64,8 @@
private final IKeystoreService mBinder;
+ private IBinder mToken;
+
private KeyStore(IKeystoreService binder) {
mBinder = binder;
}
@@ -68,6 +76,13 @@
return new KeyStore(keystore);
}
+ private synchronized IBinder getToken() {
+ if (mToken == null) {
+ mToken = new Binder();
+ }
+ return mToken;
+ }
+
static int getKeyTypeForAlgorithm(String keyType) {
if ("RSA".equalsIgnoreCase(keyType)) {
return NativeCrypto.EVP_PKEY_RSA;
@@ -363,4 +378,100 @@
public int getLastError() {
return mError;
}
+
+ public boolean addRngEntropy(byte[] data) {
+ try {
+ return mBinder.addRngEntropy(data) == NO_ERROR;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return false;
+ }
+ }
+
+ public int generateKey(String alias, KeymasterArguments args, int uid, int flags,
+ KeyCharacteristics outCharacteristics) {
+ try {
+ return mBinder.generateKey(alias, args, uid, flags, outCharacteristics);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
+
+ public int generateKey(String alias, KeymasterArguments args, int flags,
+ KeyCharacteristics outCharacteristics) {
+ return generateKey(alias, args, UID_SELF, flags, outCharacteristics);
+ }
+
+ public int getKeyCharacteristics(String alias, byte[] clientId, byte[] appId,
+ KeyCharacteristics outCharacteristics) {
+ try {
+ return mBinder.getKeyCharacteristics(alias, clientId, appId, outCharacteristics);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
+
+ public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
+ int uid, int flags, KeyCharacteristics outCharacteristics) {
+ try {
+ return mBinder.importKey(alias, args, format, keyData, uid, flags,
+ outCharacteristics);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
+
+ public int importKey(String alias, KeymasterArguments args, int format, byte[] keyData,
+ int flags, KeyCharacteristics outCharacteristics) {
+ return importKey(alias, args, format, keyData, UID_SELF, flags, outCharacteristics);
+ }
+
+ public ExportResult exportKey(String alias, int format, byte[] clientId, byte[] appId) {
+ try {
+ return mBinder.exportKey(alias, format, clientId, appId);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return null;
+ }
+ }
+
+ public OperationResult begin(String alias, int purpose, boolean pruneable,
+ KeymasterArguments args, KeymasterArguments outArgs) {
+ try {
+ return mBinder.begin(getToken(), alias, purpose, pruneable, args, outArgs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return null;
+ }
+ }
+
+ public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
+ try {
+ return mBinder.update(token, arguments, input);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return null;
+ }
+ }
+
+ public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature) {
+ try {
+ return mBinder.finish(token, arguments, signature);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return null;
+ }
+ }
+
+ public int abort(IBinder token) {
+ try {
+ return mBinder.abort(token);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return SYSTEM_ERROR;
+ }
+ }
}
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 4b3382e..782806e 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -532,7 +532,7 @@
void _FileAsset::close(void)
{
if (mMap != NULL) {
- mMap->release();
+ delete mMap;
mMap = NULL;
}
if (mBuf != NULL) {
@@ -612,7 +612,7 @@
map = new FileMap;
if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
- map->release();
+ delete map;
return NULL;
}
@@ -827,7 +827,7 @@
void _CompressedAsset::close(void)
{
if (mMap != NULL) {
- mMap->release();
+ delete mMap;
mMap = NULL;
}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index ef0d072..af3d9b3 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -200,7 +200,7 @@
FileMap* newMap = new FileMap();
if (!newMap->create(mFileName, fd, ze.offset, actualLen, true)) {
- newMap->release();
+ delete newMap;
return NULL;
}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 81d5afe..97b3f63 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -437,10 +437,7 @@
public void setCaptureRate(double fps) {
// Make sure that time lapse is enabled when this method is called.
setParameter("time-lapse-enable=1");
-
- double timeBetweenFrameCapture = 1 / fps;
- long timeBetweenFrameCaptureUs = (long) (1000000 * timeBetweenFrameCapture);
- setParameter("time-between-time-lapse-frame-capture=" + timeBetweenFrameCaptureUs);
+ setParameter("time-lapse-fps=" + fps);
}
/**
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..6e29d23
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..6110e9e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..6cca225
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..6445f2a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..78c0294
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..2fcc3b0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..70d35bf
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..2d9b75e
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..b6ebe34
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..8b67b91
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..1fa0a3d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..175bd78
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..05b27e8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..6e9f8ae
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..5f3371f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..539d77f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..3216776
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..4f381ba
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..c67127d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..d3b356b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..2d38129
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..fb76575
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..d8b68eb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..02cc3af
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..7805b7a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..8127774
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..84b8085
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..289d6ac
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..72bc804
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..e31ce2b
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..f23b0e7
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
new file mode 100644
index 0000000..1e12f96
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
new file mode 100644
index 0000000..8b547d9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
new file mode 100644
index 0000000..03c5033
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
new file mode 100644
index 0000000..b9a9923
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
new file mode 100644
index 0000000..989e1ab
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
new file mode 100644
index 0000000..de8c389
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png
Binary files differ
diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
new file mode 100644
index 0000000..2eb8a92
--- /dev/null
+++ b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png
Binary files differ
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f055a2c..870afeb 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -67,4 +67,87 @@
<!-- Status message of Wi-Fi when it is connected by a Wi-Fi assistant application. [CHAR LIMIT=NONE] -->
<string name="connected_via_wfa">Connected via Wi\u2011Fi assistant</string>
+
+ <!-- Bluetooth settings. Message when a device is disconnected -->
+ <string name="bluetooth_disconnected">Disconnected</string>
+ <!-- Bluetooth settings. Message when disconnecting from a device -->
+ <string name="bluetooth_disconnecting">Disconnecting\u2026</string>
+ <!-- Bluetooth settings. Message when connecting to a device -->
+ <string name="bluetooth_connecting">Connecting\u2026</string>
+ <!-- Bluetooth settings. Message when connected to a device. [CHAR LIMIT=40] -->
+ <string name="bluetooth_connected">Connected</string>
+
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. -->
+ <string name="bluetooth_profile_a2dp">Media audio</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the headset or handsfree profile. -->
+ <string name="bluetooth_profile_headset">Phone audio</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the OPP profile. -->
+ <string name="bluetooth_profile_opp">File transfer</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the HID profile. -->
+ <string name="bluetooth_profile_hid">Input device</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
+ <string name="bluetooth_profile_pan">Internet access</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
+ <string name="bluetooth_profile_pbap">Contact sharing</string>
+ <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
+ <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
+ <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
+ <string name="bluetooth_profile_map">Message Access</string>
+
+ <!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference when A2DP is connected. -->
+ <string name="bluetooth_a2dp_profile_summary_connected">Connected to media audio</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference when headset is connected. -->
+ <string name="bluetooth_headset_profile_summary_connected">Connected to phone audio</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is connected. -->
+ <string name="bluetooth_opp_profile_summary_connected">Connected to file transfer server</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference when map is connected. -->
+ <string name="bluetooth_map_profile_summary_connected">Connected to map</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference when OPP is not connected. -->
+ <string name="bluetooth_opp_profile_summary_not_connected">Not connected to file transfer server</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the HID checkbox preference when HID is connected. -->
+ <string name="bluetooth_hid_profile_summary_connected">Connected to input device</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (user role). [CHAR LIMIT=25]-->
+ <string name="bluetooth_pan_user_profile_summary_connected">Connected to device for Internet access</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the checkbox preference when PAN is connected (NAP role). [CHAR LIMIT=25]-->
+ <string name="bluetooth_pan_nap_profile_summary_connected">Sharing local Internet connection with device</string>
+
+ <!-- Bluetooth settings. Connection options screen. The summary
+ for the PAN checkbox preference that describes how checking it
+ will set the PAN profile as preferred. -->
+ <string name="bluetooth_pan_profile_summary_use_for">Use for Internet access</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the map checkbox preference that describes how checking it will set the map profile as preferred. -->
+ <string name="bluetooth_map_profile_summary_use_for">Use for map</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the A2DP checkbox preference that describes how checking it will set the A2DP profile as preferred. -->
+ <string name="bluetooth_a2dp_profile_summary_use_for">Use for media audio</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the headset checkbox preference that describes how checking it will set the headset profile as preferred. -->
+ <string name="bluetooth_headset_profile_summary_use_for">Use for phone audio</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the OPP checkbox preference that describes how checking it will set the OPP profile as preferred. -->
+ <string name="bluetooth_opp_profile_summary_use_for">Use for file transfer</string>
+ <!-- Bluetooth settings. Connection options screen. The summary
+ for the HID checkbox preference that describes how checking it
+ will set the HID profile as preferred. -->
+ <string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
+
+ <!-- Button text for accepting an incoming pairing request. [CHAR LIMIT=20] -->
+ <string name="bluetooth_pairing_accept">Pair</string>
+ <!-- Button text for accepting an incoming pairing request in all caps. [CHAR LIMIT=20] -->
+ <string name="bluetooth_pairing_accept_all_caps">PAIR</string>
+ <!-- Button text for declining an incoming pairing request. [CHAR LIMIT=20] -->
+ <string name="bluetooth_pairing_decline">Cancel</string>
+
+ <!-- Message in pairing dialogs. [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_pairing_will_share_phonebook">Pairing grants access to your contacts and call history when connected.</string>
+ <!-- Message for the error dialog when BT pairing fails generically. -->
+ <string name="bluetooth_pairing_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g>.</string>
+
+ <!-- Message for the error dialog when BT pairing fails because the PIN /
+ Passkey entered is incorrect. -->
+ <string name="bluetooth_pairing_pin_error_message">Couldn\'t pair with <xliff:g id="device_name">%1$s</xliff:g> because of an incorrect PIN or passkey.</string>
+ <!-- Message for the error dialog when BT pairing fails because the other device is down. -->
+ <string name="bluetooth_pairing_device_down_error_message">Can\'t communicate with <xliff:g id="device_name">%1$s</xliff:g>.</string>
+ <!-- Message for the error dialog when BT pairing fails because the other device rejected the pairing. -->
+ <string name="bluetooth_pairing_rejected_error_message">Pairing rejected by <xliff:g id="device_name">%1$s</xliff:g>.</string>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
new file mode 100644
index 0000000..58e5e29a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+public class TetherUtil {
+
+ // Types of tethering.
+ public static final int TETHERING_INVALID = -1;
+ public static final int TETHERING_WIFI = 0;
+ public static final int TETHERING_USB = 1;
+ public static final int TETHERING_BLUETOOTH = 2;
+
+ // Extras used for communicating with the TetherService.
+ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+ public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+ public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+ /**
+ * Tells the service to run a provision check now.
+ */
+ public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+ /**
+ * Enables wifi tethering if the provision check is successful. Used by
+ * QS to enable tethering.
+ */
+ public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether";
+
+ public static ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
+ .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
+
+ public static boolean setWifiTethering(boolean enable, Context context) {
+ final WifiManager wifiManager =
+ (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ final ContentResolver cr = context.getContentResolver();
+ /**
+ * Disable Wifi if enabling tethering
+ */
+ int wifiState = wifiManager.getWifiState();
+ if (enable && ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
+ (wifiState == WifiManager.WIFI_STATE_ENABLED))) {
+ wifiManager.setWifiEnabled(false);
+ Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1);
+ }
+
+ boolean success = wifiManager.setWifiApEnabled(null, enable);
+ /**
+ * If needed, restore Wifi on tether disable
+ */
+ if (!enable) {
+ int wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
+ if (wifiSavedState == 1) {
+ wifiManager.setWifiEnabled(true);
+ Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
+ }
+ }
+ return success;
+ }
+
+ public static boolean isWifiTetherEnabled(Context context) {
+ WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+ return wifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED;
+ }
+
+ public static boolean isProvisioningNeeded(Context context) {
+ // Keep in sync with other usage of config_mobile_hotspot_provision_app.
+ // ConnectivityManager#enforceTetherChangePermission
+ String[] provisionApp = context.getResources().getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app);
+ if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
+ || provisionApp == null) {
+ return false;
+ }
+ return (provisionApp.length == 2);
+ }
+
+ public static boolean isTetheringSupported(Context context) {
+ final ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER;
+ return !isSecondaryUser && cm.isTetheringSupported();
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
new file mode 100755
index 0000000..9608daa
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class A2dpProfile implements LocalBluetoothProfile {
+ private static final String TAG = "A2dpProfile";
+ private static boolean V = false;
+
+ private BluetoothA2dp mService;
+ private boolean mIsProfileReady;
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+
+ static final ParcelUuid[] SINK_UUIDS = {
+ BluetoothUuid.AudioSink,
+ BluetoothUuid.AdvAudioDist,
+ };
+
+ static final String NAME = "A2DP";
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 1;
+
+ // These callbacks run on the main thread.
+ private final class A2dpServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (V) Log.d(TAG,"Bluetooth service connected");
+ mService = (BluetoothA2dp) proxy;
+ // We just bound to the service, so refresh the UI for any connected A2DP devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ Log.w(TAG, "A2dpProfile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ }
+ device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+ mIsProfileReady=true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (V) Log.d(TAG,"Bluetooth service disconnected");
+ mIsProfileReady=false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ A2dpProfile(Context context, LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(),
+ BluetoothProfile.A2DP);
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) return new ArrayList<BluetoothDevice>(0);
+ return mService.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ if (mService == null) return false;
+ List<BluetoothDevice> sinks = getConnectedDevices();
+ if (sinks != null) {
+ for (BluetoothDevice sink : sinks) {
+ mService.disconnect(sink);
+ }
+ }
+ return mService.connect(device);
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) return false;
+ // Downgrade priority as user is disconnecting the headset.
+ if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ return mService.disconnect(device);
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+ return mService.getPriority(device);
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ if (mService == null) return;
+ if (preferred) {
+ if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ } else {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ }
+ }
+ boolean isA2dpPlaying() {
+ if (mService == null) return false;
+ List<BluetoothDevice> sinks = mService.getConnectedDevices();
+ if (!sinks.isEmpty()) {
+ if (mService.isA2dpPlaying(sinks.get(0))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_a2dp;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_a2dp_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_a2dp_profile_summary_connected;
+
+ default:
+ return Utils.getConnectionStateSummary(state);
+ }
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_headphones_a2dp;
+ }
+
+ protected void finalize() {
+ if (V) Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP,
+ mService);
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up A2DP proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
new file mode 100644
index 0000000..b802f58
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+
+/**
+ * BluetoothCallback provides a callback interface for the settings
+ * UI to receive events from {@link BluetoothEventManager}.
+ */
+public interface BluetoothCallback {
+ void onBluetoothStateChanged(int bluetoothState);
+ void onScanningStateChanged(boolean started);
+ void onDeviceAdded(CachedBluetoothDevice cachedDevice);
+ void onDeviceDeleted(CachedBluetoothDevice cachedDevice);
+ void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java
new file mode 100644
index 0000000..8dec86a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+/**
+ * BluetoothDeviceFilter contains a static method that returns a
+ * Filter object that returns whether or not the BluetoothDevice
+ * passed to it matches the specified filter type constant from
+ * {@link android.bluetooth.BluetoothDevicePicker}.
+ */
+public final class BluetoothDeviceFilter {
+ private static final String TAG = "BluetoothDeviceFilter";
+
+ /** The filter interface to external classes. */
+ public interface Filter {
+ boolean matches(BluetoothDevice device);
+ }
+
+ /** All filter singleton (referenced directly). */
+ public static final Filter ALL_FILTER = new AllFilter();
+
+ /** Bonded devices only filter (referenced directly). */
+ public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter();
+
+ /** Unbonded devices only filter (referenced directly). */
+ public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter();
+
+ /** Table of singleton filter objects. */
+ private static final Filter[] FILTERS = {
+ ALL_FILTER, // FILTER_TYPE_ALL
+ new AudioFilter(), // FILTER_TYPE_AUDIO
+ new TransferFilter(), // FILTER_TYPE_TRANSFER
+ new PanuFilter(), // FILTER_TYPE_PANU
+ new NapFilter() // FILTER_TYPE_NAP
+ };
+
+ /** Private constructor. */
+ private BluetoothDeviceFilter() {
+ }
+
+ /**
+ * Returns the singleton {@link Filter} object for the specified type,
+ * or {@link #ALL_FILTER} if the type value is out of range.
+ *
+ * @param filterType a constant from BluetoothDevicePicker
+ * @return a singleton object implementing the {@link Filter} interface.
+ */
+ public static Filter getFilter(int filterType) {
+ if (filterType >= 0 && filterType < FILTERS.length) {
+ return FILTERS[filterType];
+ } else {
+ Log.w(TAG, "Invalid filter type " + filterType + " for device picker");
+ return ALL_FILTER;
+ }
+ }
+
+ /** Filter that matches all devices. */
+ private static final class AllFilter implements Filter {
+ public boolean matches(BluetoothDevice device) {
+ return true;
+ }
+ }
+
+ /** Filter that matches only bonded devices. */
+ private static final class BondedDeviceFilter implements Filter {
+ public boolean matches(BluetoothDevice device) {
+ return device.getBondState() == BluetoothDevice.BOND_BONDED;
+ }
+ }
+
+ /** Filter that matches only unbonded devices. */
+ private static final class UnbondedDeviceFilter implements Filter {
+ public boolean matches(BluetoothDevice device) {
+ return device.getBondState() != BluetoothDevice.BOND_BONDED;
+ }
+ }
+
+ /** Parent class of filters based on UUID and/or Bluetooth class. */
+ private abstract static class ClassUuidFilter implements Filter {
+ abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass);
+
+ public boolean matches(BluetoothDevice device) {
+ return matches(device.getUuids(), device.getBluetoothClass());
+ }
+ }
+
+ /** Filter that matches devices that support AUDIO profiles. */
+ private static final class AudioFilter extends ClassUuidFilter {
+ @Override
+ boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+ if (uuids != null) {
+ if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) {
+ return true;
+ }
+ if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) {
+ return true;
+ }
+ } else if (btClass != null) {
+ if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) ||
+ btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /** Filter that matches devices that support Object Transfer. */
+ private static final class TransferFilter extends ClassUuidFilter {
+ @Override
+ boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+ if (uuids != null) {
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
+ return true;
+ }
+ }
+ return btClass != null
+ && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP);
+ }
+ }
+
+ /** Filter that matches devices that support PAN User (PANU) profile. */
+ private static final class PanuFilter extends ClassUuidFilter {
+ @Override
+ boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+ if (uuids != null) {
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) {
+ return true;
+ }
+ }
+ return btClass != null
+ && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU);
+ }
+ }
+
+ /** Filter that matches devices that support NAP profile. */
+ private static final class NapFilter extends ClassUuidFilter {
+ @Override
+ boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) {
+ if (uuids != null) {
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) {
+ return true;
+ }
+ }
+ return btClass != null
+ && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
new file mode 100644
index 0000000..acb7e7a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+/* Required to handle timeout notification when phone is suspended */
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+
+public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver {
+ private static final String TAG = "BluetoothDiscoverableTimeoutReceiver";
+
+ private static final String INTENT_DISCOVERABLE_TIMEOUT =
+ "android.bluetooth.intent.DISCOVERABLE_TIMEOUT";
+
+ public static void setDiscoverableAlarm(Context context, long alarmTime) {
+ Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime);
+
+ Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
+ intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
+ PendingIntent pending = PendingIntent.getBroadcast(
+ context, 0, intent, 0);
+ AlarmManager alarmManager =
+ (AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
+
+ if (pending != null) {
+ // Cancel any previous alarms that do the same thing.
+ alarmManager.cancel(pending);
+ Log.d(TAG, "setDiscoverableAlarm(): cancel prev alarm");
+ }
+ pending = PendingIntent.getBroadcast(
+ context, 0, intent, 0);
+
+ alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending);
+ }
+
+ public static void cancelDiscoverableAlarm(Context context) {
+ Log.d(TAG, "cancelDiscoverableAlarm(): Enter");
+
+ Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT);
+ intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class);
+ PendingIntent pending = PendingIntent.getBroadcast(
+ context, 0, intent, PendingIntent.FLAG_NO_CREATE);
+ if (pending != null) {
+ // Cancel any previous alarms that do the same thing.
+ AlarmManager alarmManager =
+ (AlarmManager) context.getSystemService (Context.ALARM_SERVICE);
+
+ alarmManager.cancel(pending);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ LocalBluetoothAdapter localBluetoothAdapter = LocalBluetoothAdapter.getInstance();
+
+ if(localBluetoothAdapter != null &&
+ localBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
+ Log.d(TAG, "Disable discoverable...");
+
+ localBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+ } else {
+ Log.e(TAG, "localBluetoothAdapter is NULL!!");
+ }
+ }
+};
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
new file mode 100755
index 0000000..7c92368
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth
+ * API and dispatches the event on the UI thread to the right class in the
+ * Settings.
+ */
+public final class BluetoothEventManager {
+ private static final String TAG = "BluetoothEventManager";
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private LocalBluetoothProfileManager mProfileManager;
+ private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
+ private final Map<String, Handler> mHandlerMap;
+ private Context mContext;
+
+ private final Collection<BluetoothCallback> mCallbacks =
+ new ArrayList<BluetoothCallback>();
+
+ interface Handler {
+ void onReceive(Context context, Intent intent, BluetoothDevice device);
+ }
+
+ void addHandler(String action, Handler handler) {
+ mHandlerMap.put(action, handler);
+ mAdapterIntentFilter.addAction(action);
+ }
+
+ void addProfileHandler(String action, Handler handler) {
+ mHandlerMap.put(action, handler);
+ mProfileIntentFilter.addAction(action);
+ }
+
+ // Set profile manager after construction due to circular dependency
+ void setProfileManager(LocalBluetoothProfileManager manager) {
+ mProfileManager = manager;
+ }
+
+ BluetoothEventManager(LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager, Context context) {
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mAdapterIntentFilter = new IntentFilter();
+ mProfileIntentFilter = new IntentFilter();
+ mHandlerMap = new HashMap<String, Handler>();
+ mContext = context;
+
+ // Bluetooth on/off broadcasts
+ addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());
+
+ // Discovery broadcasts
+ addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
+ addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));
+ addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
+ addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler());
+ addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
+
+ // Pairing broadcasts
+ addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());
+ addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler());
+
+ // Fine-grained state broadcasts
+ addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
+ addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
+
+ // Dock event broadcasts
+ addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler());
+
+ mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter);
+ }
+
+ void registerProfileIntentReceiver() {
+ mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter);
+ }
+
+ /** Register to start receiving callbacks for Bluetooth events. */
+ public void registerCallback(BluetoothCallback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ /** Unregister to stop receiving callbacks for Bluetooth events. */
+ public void unregisterCallback(BluetoothCallback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ BluetoothDevice device = intent
+ .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ Handler handler = mHandlerMap.get(action);
+ if (handler != null) {
+ handler.onReceive(context, intent, device);
+ }
+ }
+ };
+
+ private class AdapterStateChangedHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ // update local profiles and get paired devices
+ mLocalAdapter.setBluetoothStateInt(state);
+ // send callback to update UI and possibly start scanning
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onBluetoothStateChanged(state);
+ }
+ }
+ // Inform CachedDeviceManager that the adapter state has changed
+ mDeviceManager.onBluetoothStateChanged(state);
+ }
+ }
+
+ private class ScanningStateChangedHandler implements Handler {
+ private final boolean mStarted;
+
+ ScanningStateChangedHandler(boolean started) {
+ mStarted = started;
+ }
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onScanningStateChanged(mStarted);
+ }
+ }
+ mDeviceManager.onScanningStateChanged(mStarted);
+ }
+ }
+
+ private class DeviceFoundHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
+ BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS);
+ String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
+ // TODO Pick up UUID. They should be available for 2.1 devices.
+ // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1.
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+ Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: "
+ + cachedDevice);
+ // callback to UI to create Preference for new device
+ dispatchDeviceAdded(cachedDevice);
+ }
+ cachedDevice.setRssi(rssi);
+ cachedDevice.setBtClass(btClass);
+ cachedDevice.setNewName(name);
+ cachedDevice.setVisible(true);
+ }
+ }
+
+ private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onDeviceAdded(cachedDevice);
+ }
+ }
+ }
+
+ private class DeviceDisappearedHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device);
+ return;
+ }
+ if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) {
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onDeviceDeleted(cachedDevice);
+ }
+ }
+ }
+ }
+ }
+
+ private class NameChangedHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ mDeviceManager.onDeviceNameUpdated(device);
+ }
+ }
+
+ private class BondStateChangedHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ if (device == null) {
+ Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
+ return;
+ }
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ Log.w(TAG, "CachedBluetoothDevice for device " + device +
+ " not found, calling readPairedDevices().");
+ if (!readPairedDevices()) {
+ Log.e(TAG, "Got bonding state changed for " + device +
+ ", but we have no record of that device.");
+ return;
+ }
+ cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ Log.e(TAG, "Got bonding state changed for " + device +
+ ", but device not added in cache.");
+ return;
+ }
+ }
+
+ synchronized (mCallbacks) {
+ for (BluetoothCallback callback : mCallbacks) {
+ callback.onDeviceBondStateChanged(cachedDevice, bondState);
+ }
+ }
+ cachedDevice.onBondingStateChanged(bondState);
+
+ if (bondState == BluetoothDevice.BOND_NONE) {
+ int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
+ BluetoothDevice.ERROR);
+
+ showUnbondMessage(context, cachedDevice.getName(), reason);
+ }
+ }
+
+ /**
+ * Called when we have reached the unbonded state.
+ *
+ * @param reason one of the error reasons from
+ * BluetoothDevice.UNBOND_REASON_*
+ */
+ private void showUnbondMessage(Context context, String name, int reason) {
+ int errorMsg;
+
+ switch(reason) {
+ case BluetoothDevice.UNBOND_REASON_AUTH_FAILED:
+ errorMsg = R.string.bluetooth_pairing_pin_error_message;
+ break;
+ case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED:
+ errorMsg = R.string.bluetooth_pairing_rejected_error_message;
+ break;
+ case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN:
+ errorMsg = R.string.bluetooth_pairing_device_down_error_message;
+ break;
+ case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS:
+ case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
+ case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
+ case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
+ errorMsg = R.string.bluetooth_pairing_error_message;
+ break;
+ default:
+ Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason);
+ return;
+ }
+ Utils.showError(context, name, errorMsg);
+ }
+ }
+
+ private class ClassChangedHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ mDeviceManager.onBtClassChanged(device);
+ }
+ }
+
+ private class UuidChangedHandler implements Handler {
+ public void onReceive(Context context, Intent intent,
+ BluetoothDevice device) {
+ mDeviceManager.onUuidChanged(device);
+ }
+ }
+
+ private class PairingCancelHandler implements Handler {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ if (device == null) {
+ Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE");
+ return;
+ }
+ int errorMsg = R.string.bluetooth_pairing_error_message;
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ Utils.showError(context, cachedDevice.getName(), errorMsg);
+ }
+ }
+
+ private class DockEventHandler implements Handler {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ // Remove if unpair device upon undocking
+ int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1;
+ int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked);
+ if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) {
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice != null) {
+ cachedDevice.setVisible(false);
+ }
+ }
+ }
+ }
+ }
+ boolean readPairedDevices() {
+ Set<BluetoothDevice> bondedDevices = mLocalAdapter.getBondedDevices();
+ if (bondedDevices == null) {
+ return false;
+ }
+
+ boolean deviceAdded = false;
+ for (BluetoothDevice device : bondedDevices) {
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device);
+ dispatchDeviceAdded(cachedDevice);
+ deviceAdded = true;
+ }
+ }
+
+ return deviceAdded;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
new file mode 100755
index 0000000..ddcc49f7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.ParcelUuid;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Log;
+import android.bluetooth.BluetoothAdapter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * CachedBluetoothDevice represents a remote Bluetooth device. It contains
+ * attributes of the device (such as the address, name, RSSI, etc.) and
+ * functionality that can be performed on the device (connect, pair, disconnect,
+ * etc.).
+ */
+public final class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> {
+ private static final String TAG = "CachedBluetoothDevice";
+ private static final boolean DEBUG = Utils.V;
+
+ private final Context mContext;
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final LocalBluetoothProfileManager mProfileManager;
+ private final BluetoothDevice mDevice;
+ private String mName;
+ private short mRssi;
+ private BluetoothClass mBtClass;
+ private HashMap<LocalBluetoothProfile, Integer> mProfileConnectionState;
+
+ private final List<LocalBluetoothProfile> mProfiles =
+ new ArrayList<LocalBluetoothProfile>();
+
+ // List of profiles that were previously in mProfiles, but have been removed
+ private final List<LocalBluetoothProfile> mRemovedProfiles =
+ new ArrayList<LocalBluetoothProfile>();
+
+ // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP
+ private boolean mLocalNapRoleConnected;
+
+ private boolean mVisible;
+
+ private int mPhonebookPermissionChoice;
+
+ private int mMessagePermissionChoice;
+
+ private int mMessageRejectionCount;
+
+ private final Collection<Callback> mCallbacks = new ArrayList<Callback>();
+
+ // Following constants indicate the user's choices of Phone book/message access settings
+ // User hasn't made any choice or settings app has wiped out the memory
+ public final static int ACCESS_UNKNOWN = 0;
+ // User has accepted the connection and let Settings app remember the decision
+ public final static int ACCESS_ALLOWED = 1;
+ // User has rejected the connection and let Settings app remember the decision
+ public final static int ACCESS_REJECTED = 2;
+
+ // How many times user should reject the connection to make the choice persist.
+ private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2;
+
+ private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject";
+
+ /**
+ * When we connect to multiple profiles, we only want to display a single
+ * error even if they all fail. This tracks that state.
+ */
+ private boolean mIsConnectingErrorPossible;
+
+ /**
+ * Last time a bt profile auto-connect was attempted.
+ * If an ACTION_UUID intent comes in within
+ * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect
+ * again with the new UUIDs
+ */
+ private long mConnectAttempted;
+
+ // See mConnectAttempted
+ private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000;
+
+ /** Auto-connect after pairing only if locally initiated. */
+ private boolean mConnectAfterPairing;
+
+ /**
+ * Describes the current device and profile for logging.
+ *
+ * @param profile Profile to describe
+ * @return Description of the device and profile
+ */
+ private String describe(LocalBluetoothProfile profile) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Address:").append(mDevice);
+ if (profile != null) {
+ sb.append(" Profile:").append(profile);
+ }
+
+ return sb.toString();
+ }
+
+ void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
+ if (Utils.D) {
+ Log.d(TAG, "onProfileStateChanged: profile " + profile +
+ " newProfileState " + newProfileState);
+ }
+ if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF)
+ {
+ if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored...");
+ return;
+ }
+ mProfileConnectionState.put(profile, newProfileState);
+ if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
+ if (profile instanceof MapProfile) {
+ profile.setPreferred(mDevice, true);
+ } else if (!mProfiles.contains(profile)) {
+ mRemovedProfiles.remove(profile);
+ mProfiles.add(profile);
+ if (profile instanceof PanProfile &&
+ ((PanProfile) profile).isLocalRoleNap(mDevice)) {
+ // Device doesn't support NAP, so remove PanProfile on disconnect
+ mLocalNapRoleConnected = true;
+ }
+ }
+ } else if (profile instanceof MapProfile &&
+ newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+ profile.setPreferred(mDevice, false);
+ } else if (mLocalNapRoleConnected && profile instanceof PanProfile &&
+ ((PanProfile) profile).isLocalRoleNap(mDevice) &&
+ newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
+ Log.d(TAG, "Removing PanProfile from device after NAP disconnect");
+ mProfiles.remove(profile);
+ mRemovedProfiles.add(profile);
+ mLocalNapRoleConnected = false;
+ }
+ }
+
+ CachedBluetoothDevice(Context context,
+ LocalBluetoothAdapter adapter,
+ LocalBluetoothProfileManager profileManager,
+ BluetoothDevice device) {
+ mContext = context;
+ mLocalAdapter = adapter;
+ mProfileManager = profileManager;
+ mDevice = device;
+ mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>();
+ fillData();
+ }
+
+ public void disconnect() {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ disconnect(profile);
+ }
+ // Disconnect PBAP server in case its connected
+ // This is to ensure all the profiles are disconnected as some CK/Hs do not
+ // disconnect PBAP connection when HF connection is brought down
+ PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
+ if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED)
+ {
+ PbapProfile.disconnect(mDevice);
+ }
+ }
+
+ public void disconnect(LocalBluetoothProfile profile) {
+ if (profile.disconnect(mDevice)) {
+ if (Utils.D) {
+ Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
+ }
+ }
+ }
+
+ public void connect(boolean connectAllProfiles) {
+ if (!ensurePaired()) {
+ return;
+ }
+
+ mConnectAttempted = SystemClock.elapsedRealtime();
+ connectWithoutResettingTimer(connectAllProfiles);
+ }
+
+ void onBondingDockConnect() {
+ // Attempt to connect if UUIDs are available. Otherwise,
+ // we will connect when the ACTION_UUID intent arrives.
+ connect(false);
+ }
+
+ private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+ // Try to initialize the profiles if they were not.
+ if (mProfiles.isEmpty()) {
+ // if mProfiles is empty, then do not invoke updateProfiles. This causes a race
+ // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated
+ // from bluetooth stack but ACTION.uuid is not sent yet.
+ // Eventually ACTION.uuid will be received which shall trigger the connection of the
+ // various profiles
+ // If UUIDs are not available yet, connect will be happen
+ // upon arrival of the ACTION_UUID intent.
+ Log.d(TAG, "No profiles. Maybe we will connect later");
+ return;
+ }
+
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+
+ int preferredProfiles = 0;
+ for (LocalBluetoothProfile profile : mProfiles) {
+ if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) {
+ if (profile.isPreferred(mDevice)) {
+ ++preferredProfiles;
+ connectInt(profile);
+ }
+ }
+ }
+ if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
+
+ if (preferredProfiles == 0) {
+ connectAutoConnectableProfiles();
+ }
+ }
+
+ private void connectAutoConnectableProfiles() {
+ if (!ensurePaired()) {
+ return;
+ }
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+
+ for (LocalBluetoothProfile profile : mProfiles) {
+ if (profile.isAutoConnectable()) {
+ profile.setPreferred(mDevice, true);
+ connectInt(profile);
+ }
+ }
+ }
+
+ /**
+ * Connect this device to the specified profile.
+ *
+ * @param profile the profile to use with the remote device
+ */
+ public void connectProfile(LocalBluetoothProfile profile) {
+ mConnectAttempted = SystemClock.elapsedRealtime();
+ // Reset the only-show-one-error-dialog tracking variable
+ mIsConnectingErrorPossible = true;
+ connectInt(profile);
+ // Refresh the UI based on profile.connect() call
+ refresh();
+ }
+
+ synchronized void connectInt(LocalBluetoothProfile profile) {
+ if (!ensurePaired()) {
+ return;
+ }
+ if (profile.connect(mDevice)) {
+ if (Utils.D) {
+ Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
+ }
+ return;
+ }
+ Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName);
+ }
+
+ private boolean ensurePaired() {
+ if (getBondState() == BluetoothDevice.BOND_NONE) {
+ startPairing();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public boolean startPairing() {
+ // Pairing is unreliable while scanning, so cancel discovery
+ if (mLocalAdapter.isDiscovering()) {
+ mLocalAdapter.cancelDiscovery();
+ }
+
+ if (!mDevice.createBond()) {
+ return false;
+ }
+
+ mConnectAfterPairing = true; // auto-connect after pairing
+ return true;
+ }
+
+ /**
+ * Return true if user initiated pairing on this device. The message text is
+ * slightly different for local vs. remote initiated pairing dialogs.
+ */
+ boolean isUserInitiatedPairing() {
+ return mConnectAfterPairing;
+ }
+
+ public void unpair() {
+ int state = getBondState();
+
+ if (state == BluetoothDevice.BOND_BONDING) {
+ mDevice.cancelBondProcess();
+ }
+
+ if (state != BluetoothDevice.BOND_NONE) {
+ final BluetoothDevice dev = mDevice;
+ if (dev != null) {
+ final boolean successful = dev.removeBond();
+ if (successful) {
+ if (Utils.D) {
+ Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null));
+ }
+ } else if (Utils.V) {
+ Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " +
+ describe(null));
+ }
+ }
+ }
+ }
+
+ public int getProfileConnectionState(LocalBluetoothProfile profile) {
+ if (mProfileConnectionState == null ||
+ mProfileConnectionState.get(profile) == null) {
+ // If cache is empty make the binder call to get the state
+ int state = profile.getConnectionStatus(mDevice);
+ mProfileConnectionState.put(profile, state);
+ }
+ return mProfileConnectionState.get(profile);
+ }
+
+ public void clearProfileConnectionState ()
+ {
+ if (Utils.D) {
+ Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName());
+ }
+ for (LocalBluetoothProfile profile :getProfiles()) {
+ mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED);
+ }
+ }
+
+ // TODO: do any of these need to run async on a background thread?
+ private void fillData() {
+ fetchName();
+ fetchBtClass();
+ updateProfiles();
+ migratePhonebookPermissionChoice();
+ migrateMessagePermissionChoice();
+ fetchMessageRejectionCount();
+
+ mVisible = false;
+ dispatchAttributesChanged();
+ }
+
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Populate name from BluetoothDevice.ACTION_FOUND intent
+ */
+ void setNewName(String name) {
+ if (mName == null) {
+ mName = name;
+ if (mName == null || TextUtils.isEmpty(mName)) {
+ mName = mDevice.getAddress();
+ }
+ dispatchAttributesChanged();
+ }
+ }
+
+ /**
+ * user changes the device name
+ */
+ public void setName(String name) {
+ if (!mName.equals(name)) {
+ mName = name;
+ mDevice.setAlias(name);
+ dispatchAttributesChanged();
+ }
+ }
+
+ void refreshName() {
+ fetchName();
+ dispatchAttributesChanged();
+ }
+
+ private void fetchName() {
+ mName = mDevice.getAliasName();
+
+ if (TextUtils.isEmpty(mName)) {
+ mName = mDevice.getAddress();
+ if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName);
+ }
+ }
+
+ void refresh() {
+ dispatchAttributesChanged();
+ }
+
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ public void setVisible(boolean visible) {
+ if (mVisible != visible) {
+ mVisible = visible;
+ dispatchAttributesChanged();
+ }
+ }
+
+ public int getBondState() {
+ return mDevice.getBondState();
+ }
+
+ void setRssi(short rssi) {
+ if (mRssi != rssi) {
+ mRssi = rssi;
+ dispatchAttributesChanged();
+ }
+ }
+
+ /**
+ * Checks whether we are connected to this device (any profile counts).
+ *
+ * @return Whether it is connected.
+ */
+ public boolean isConnected() {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ int status = getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTED) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isConnectedProfile(LocalBluetoothProfile profile) {
+ int status = getProfileConnectionState(profile);
+ return status == BluetoothProfile.STATE_CONNECTED;
+
+ }
+
+ public boolean isBusy() {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ int status = getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTING
+ || status == BluetoothProfile.STATE_DISCONNECTING) {
+ return true;
+ }
+ }
+ return getBondState() == BluetoothDevice.BOND_BONDING;
+ }
+
+ /**
+ * Fetches a new value for the cached BT class.
+ */
+ private void fetchBtClass() {
+ mBtClass = mDevice.getBluetoothClass();
+ }
+
+ private boolean updateProfiles() {
+ ParcelUuid[] uuids = mDevice.getUuids();
+ if (uuids == null) return false;
+
+ ParcelUuid[] localUuids = mLocalAdapter.getUuids();
+ if (localUuids == null) return false;
+
+ /**
+ * Now we know if the device supports PBAP, update permissions...
+ */
+ processPhonebookAccess();
+
+ mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles,
+ mLocalNapRoleConnected, mDevice);
+
+ if (DEBUG) {
+ Log.e(TAG, "updating profiles for " + mDevice.getAliasName());
+ BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
+
+ if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
+ Log.v(TAG, "UUID:");
+ for (ParcelUuid uuid : uuids) {
+ Log.v(TAG, " " + uuid);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Refreshes the UI for the BT class, including fetching the latest value
+ * for the class.
+ */
+ void refreshBtClass() {
+ fetchBtClass();
+ dispatchAttributesChanged();
+ }
+
+ /**
+ * Refreshes the UI when framework alerts us of a UUID change.
+ */
+ void onUuidChanged() {
+ updateProfiles();
+
+ if (DEBUG) {
+ Log.e(TAG, "onUuidChanged: Time since last connect"
+ + (SystemClock.elapsedRealtime() - mConnectAttempted));
+ }
+
+ /*
+ * If a connect was attempted earlier without any UUID, we will do the
+ * connect now.
+ */
+ if (!mProfiles.isEmpty()
+ && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock
+ .elapsedRealtime()) {
+ connectWithoutResettingTimer(false);
+ }
+ dispatchAttributesChanged();
+ }
+
+ void onBondingStateChanged(int bondState) {
+ if (bondState == BluetoothDevice.BOND_NONE) {
+ mProfiles.clear();
+ mConnectAfterPairing = false; // cancel auto-connect
+ setPhonebookPermissionChoice(ACCESS_UNKNOWN);
+ setMessagePermissionChoice(ACCESS_UNKNOWN);
+ mMessageRejectionCount = 0;
+ saveMessageRejectionCount();
+ }
+
+ refresh();
+
+ if (bondState == BluetoothDevice.BOND_BONDED) {
+ if (mDevice.isBluetoothDock()) {
+ onBondingDockConnect();
+ } else if (mConnectAfterPairing) {
+ connect(false);
+ }
+ mConnectAfterPairing = false;
+ }
+ }
+
+ void setBtClass(BluetoothClass btClass) {
+ if (btClass != null && mBtClass != btClass) {
+ mBtClass = btClass;
+ dispatchAttributesChanged();
+ }
+ }
+
+ public BluetoothClass getBtClass() {
+ return mBtClass;
+ }
+
+ public List<LocalBluetoothProfile> getProfiles() {
+ return Collections.unmodifiableList(mProfiles);
+ }
+
+ public List<LocalBluetoothProfile> getConnectableProfiles() {
+ List<LocalBluetoothProfile> connectableProfiles =
+ new ArrayList<LocalBluetoothProfile>();
+ for (LocalBluetoothProfile profile : mProfiles) {
+ if (profile.isConnectable()) {
+ connectableProfiles.add(profile);
+ }
+ }
+ return connectableProfiles;
+ }
+
+ public List<LocalBluetoothProfile> getRemovedProfiles() {
+ return mRemovedProfiles;
+ }
+
+ public void registerCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ public void unregisterCallback(Callback callback) {
+ synchronized (mCallbacks) {
+ mCallbacks.remove(callback);
+ }
+ }
+
+ private void dispatchAttributesChanged() {
+ synchronized (mCallbacks) {
+ for (Callback callback : mCallbacks) {
+ callback.onDeviceAttributesChanged();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mDevice.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ((o == null) || !(o instanceof CachedBluetoothDevice)) {
+ return false;
+ }
+ return mDevice.equals(((CachedBluetoothDevice) o).mDevice);
+ }
+
+ @Override
+ public int hashCode() {
+ return mDevice.getAddress().hashCode();
+ }
+
+ // This comparison uses non-final fields so the sort order may change
+ // when device attributes change (such as bonding state). Settings
+ // will completely refresh the device list when this happens.
+ public int compareTo(CachedBluetoothDevice another) {
+ // Connected above not connected
+ int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Paired above not paired
+ comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) -
+ (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Visible above not visible
+ comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0);
+ if (comparison != 0) return comparison;
+
+ // Stronger signal above weaker signal
+ comparison = another.mRssi - mRssi;
+ if (comparison != 0) return comparison;
+
+ // Fallback on name
+ return mName.compareTo(another.mName);
+ }
+
+ public interface Callback {
+ void onDeviceAttributesChanged();
+ }
+
+ public int getPhonebookPermissionChoice() {
+ int permission = mDevice.getPhonebookAccessPermission();
+ if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ return ACCESS_ALLOWED;
+ } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+ return ACCESS_REJECTED;
+ }
+ return ACCESS_UNKNOWN;
+ }
+
+ public void setPhonebookPermissionChoice(int permissionChoice) {
+ int permission = BluetoothDevice.ACCESS_UNKNOWN;
+ if (permissionChoice == ACCESS_ALLOWED) {
+ permission = BluetoothDevice.ACCESS_ALLOWED;
+ } else if (permissionChoice == ACCESS_REJECTED) {
+ permission = BluetoothDevice.ACCESS_REJECTED;
+ }
+ mDevice.setPhonebookAccessPermission(permission);
+ }
+
+ // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+ // app's shared preferences).
+ private void migratePhonebookPermissionChoice() {
+ SharedPreferences preferences = mContext.getSharedPreferences(
+ "bluetooth_phonebook_permission", Context.MODE_PRIVATE);
+ if (!preferences.contains(mDevice.getAddress())) {
+ return;
+ }
+
+ if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+ int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+ if (oldPermission == ACCESS_ALLOWED) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ } else if (oldPermission == ACCESS_REJECTED) {
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ }
+ }
+
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.remove(mDevice.getAddress());
+ editor.commit();
+ }
+
+ public int getMessagePermissionChoice() {
+ int permission = mDevice.getMessageAccessPermission();
+ if (permission == BluetoothDevice.ACCESS_ALLOWED) {
+ return ACCESS_ALLOWED;
+ } else if (permission == BluetoothDevice.ACCESS_REJECTED) {
+ return ACCESS_REJECTED;
+ }
+ return ACCESS_UNKNOWN;
+ }
+
+ public void setMessagePermissionChoice(int permissionChoice) {
+ int permission = BluetoothDevice.ACCESS_UNKNOWN;
+ if (permissionChoice == ACCESS_ALLOWED) {
+ permission = BluetoothDevice.ACCESS_ALLOWED;
+ } else if (permissionChoice == ACCESS_REJECTED) {
+ permission = BluetoothDevice.ACCESS_REJECTED;
+ }
+ mDevice.setMessageAccessPermission(permission);
+ }
+
+ // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth
+ // app's shared preferences).
+ private void migrateMessagePermissionChoice() {
+ SharedPreferences preferences = mContext.getSharedPreferences(
+ "bluetooth_message_permission", Context.MODE_PRIVATE);
+ if (!preferences.contains(mDevice.getAddress())) {
+ return;
+ }
+
+ if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) {
+ int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN);
+ if (oldPermission == ACCESS_ALLOWED) {
+ mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
+ } else if (oldPermission == ACCESS_REJECTED) {
+ mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ }
+ }
+
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.remove(mDevice.getAddress());
+ editor.commit();
+ }
+
+ /**
+ * @return Whether this rejection should persist.
+ */
+ public boolean checkAndIncreaseMessageRejectionCount() {
+ if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) {
+ mMessageRejectionCount++;
+ saveMessageRejectionCount();
+ }
+ return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST;
+ }
+
+ private void fetchMessageRejectionCount() {
+ SharedPreferences preference = mContext.getSharedPreferences(
+ MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE);
+ mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0);
+ }
+
+ private void saveMessageRejectionCount() {
+ SharedPreferences.Editor editor = mContext.getSharedPreferences(
+ MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit();
+ if (mMessageRejectionCount == 0) {
+ editor.remove(mDevice.getAddress());
+ } else {
+ editor.putInt(mDevice.getAddress(), mMessageRejectionCount);
+ }
+ editor.commit();
+ }
+
+ private void processPhonebookAccess() {
+ if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return;
+
+ ParcelUuid[] uuids = mDevice.getUuids();
+ if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) {
+ // The pairing dialog now warns of phone-book access for paired devices.
+ // No separate prompt is displayed after pairing.
+ setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
new file mode 100755
index 0000000..65db95f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
+ */
+public final class CachedBluetoothDeviceManager {
+ private static final String TAG = "CachedBluetoothDeviceManager";
+ private static final boolean DEBUG = Utils.D;
+
+ private Context mContext;
+ private final List<CachedBluetoothDevice> mCachedDevices =
+ new ArrayList<CachedBluetoothDevice>();
+
+ CachedBluetoothDeviceManager(Context context) {
+ mContext = context;
+ }
+
+ public synchronized Collection<CachedBluetoothDevice> getCachedDevicesCopy() {
+ return new ArrayList<CachedBluetoothDevice>(mCachedDevices);
+ }
+
+ public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) {
+ cachedDevice.setVisible(false);
+ return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE;
+ }
+
+ public void onDeviceNameUpdated(BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = findDevice(device);
+ if (cachedDevice != null) {
+ cachedDevice.refreshName();
+ }
+ }
+
+ /**
+ * Search for existing {@link CachedBluetoothDevice} or return null
+ * if this device isn't in the cache. Use {@link #addDevice}
+ * to create and return a new {@link CachedBluetoothDevice} for
+ * a newly discovered {@link BluetoothDevice}.
+ *
+ * @param device the address of the Bluetooth device
+ * @return the cached device object for this device, or null if it has
+ * not been previously seen
+ */
+ public CachedBluetoothDevice findDevice(BluetoothDevice device) {
+ for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+ if (cachedDevice.getDevice().equals(device)) {
+ return cachedDevice;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create and return a new {@link CachedBluetoothDevice}. This assumes
+ * that {@link #findDevice} has already been called and returned null.
+ * @param device the address of the new Bluetooth device
+ * @return the newly created CachedBluetoothDevice object
+ */
+ public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter,
+ LocalBluetoothProfileManager profileManager,
+ BluetoothDevice device) {
+ CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter,
+ profileManager, device);
+ synchronized (mCachedDevices) {
+ mCachedDevices.add(newDevice);
+ }
+ return newDevice;
+ }
+
+ /**
+ * Attempts to get the name of a remote device, otherwise returns the address.
+ *
+ * @param device The remote device.
+ * @return The name, or if unavailable, the address.
+ */
+ public String getName(BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = findDevice(device);
+ if (cachedDevice != null) {
+ return cachedDevice.getName();
+ }
+
+ String name = device.getAliasName();
+ if (name != null) {
+ return name;
+ }
+
+ return device.getAddress();
+ }
+
+ public synchronized void clearNonBondedDevices() {
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+ mCachedDevices.remove(i);
+ }
+ }
+ }
+
+ public synchronized void onScanningStateChanged(boolean started) {
+ if (!started) return;
+
+ // If starting a new scan, clear old visibility
+ // Iterate in reverse order since devices may be removed.
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ cachedDevice.setVisible(false);
+ }
+ }
+
+ public synchronized void onBtClassChanged(BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = findDevice(device);
+ if (cachedDevice != null) {
+ cachedDevice.refreshBtClass();
+ }
+ }
+
+ public synchronized void onUuidChanged(BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = findDevice(device);
+ if (cachedDevice != null) {
+ cachedDevice.onUuidChanged();
+ }
+ }
+
+ public synchronized void onBluetoothStateChanged(int bluetoothState) {
+ // When Bluetooth is turning off, we need to clear the non-bonded devices
+ // Otherwise, they end up showing up on the next BT enable
+ if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) {
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
+ cachedDevice.setVisible(false);
+ mCachedDevices.remove(i);
+ } else {
+ // For bonded devices, we need to clear the connection status so that
+ // when BT is enabled next time, device connection status shall be retrieved
+ // by making a binder call.
+ cachedDevice.clearProfileConnectionState();
+ }
+ }
+ }
+ }
+ private void log(String msg) {
+ if (DEBUG) {
+ Log.d(TAG, msg);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
new file mode 100755
index 0000000..5529866
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HeadsetProfile handles Bluetooth HFP and Headset profiles.
+ */
+public final class HeadsetProfile implements LocalBluetoothProfile {
+ private static final String TAG = "HeadsetProfile";
+ private static boolean V = true;
+
+ private BluetoothHeadset mService;
+ private boolean mIsProfileReady;
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ static final ParcelUuid[] UUIDS = {
+ BluetoothUuid.HSP,
+ BluetoothUuid.Handsfree,
+ };
+
+ static final String NAME = "HEADSET";
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 0;
+
+ // These callbacks run on the main thread.
+ private final class HeadsetServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (V) Log.d(TAG,"Bluetooth service connected");
+ mService = (BluetoothHeadset) proxy;
+ // We just bound to the service, so refresh the UI for any connected HFP devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ Log.w(TAG, "HeadsetProfile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ }
+ device.onProfileStateChanged(HeadsetProfile.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady=true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (V) Log.d(TAG,"Bluetooth service disconnected");
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady=false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ HeadsetProfile(Context context, LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(),
+ BluetoothProfile.HEADSET);
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ if (mService == null) return false;
+ List<BluetoothDevice> sinks = mService.getConnectedDevices();
+ if (sinks != null) {
+ for (BluetoothDevice sink : sinks) {
+ Log.d(TAG,"Not disconnecting device = " + sink);
+ }
+ }
+ return mService.connect(device);
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) return false;
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ if (!deviceList.isEmpty()) {
+ for (BluetoothDevice dev : deviceList) {
+ if (dev.equals(device)) {
+ if (V) Log.d(TAG,"Downgrade priority as user" +
+ "is disconnecting the headset");
+ // Downgrade priority as user is disconnecting the headset.
+ if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ return mService.disconnect(device);
+ }
+ }
+ }
+ return false;
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ if (!deviceList.isEmpty()){
+ for (BluetoothDevice dev : deviceList) {
+ if (dev.equals(device)) {
+ return mService.getConnectionState(device);
+ }
+ }
+ }
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+ return mService.getPriority(device);
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ if (mService == null) return;
+ if (preferred) {
+ if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ } else {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ }
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) return new ArrayList<BluetoothDevice>(0);
+ return mService.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_headset;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_headset_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_headset_profile_summary_connected;
+
+ default:
+ return Utils.getConnectionStateSummary(state);
+ }
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_headset_hfp;
+ }
+
+ protected void finalize() {
+ if (V) Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEADSET,
+ mService);
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up HID proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
new file mode 100755
index 0000000..a9e8db5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.List;
+
+/**
+ * HidProfile handles Bluetooth HID profile.
+ */
+public final class HidProfile implements LocalBluetoothProfile {
+ private static final String TAG = "HidProfile";
+ private static boolean V = true;
+
+ private BluetoothInputDevice mService;
+ private boolean mIsProfileReady;
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ static final String NAME = "HID";
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 3;
+
+ // These callbacks run on the main thread.
+ private final class InputDeviceServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (V) Log.d(TAG,"Bluetooth service connected");
+ mService = (BluetoothInputDevice) proxy;
+ // We just bound to the service, so refresh the UI for any connected HID devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ Log.w(TAG, "HidProfile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ }
+ device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+ mIsProfileReady=true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (V) Log.d(TAG,"Bluetooth service disconnected");
+ mIsProfileReady=false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ HidProfile(Context context, LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ adapter.getProfileProxy(context, new InputDeviceServiceListener(),
+ BluetoothProfile.INPUT_DEVICE);
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.connect(device);
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.disconnect(device);
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+
+ return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+ ? mService.getConnectionState(device)
+ : BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+ return mService.getPriority(device);
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ if (mService == null) return;
+ if (preferred) {
+ if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ } else {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ }
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ // TODO: distinguish between keyboard and mouse?
+ return R.string.bluetooth_profile_hid;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_hid_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_hid_profile_summary_connected;
+
+ default:
+ return Utils.getConnectionStateSummary(state);
+ }
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ if (btClass == null) {
+ return R.drawable.ic_lockscreen_ime;
+ }
+ return getHidClassDrawable(btClass);
+ }
+
+ public static int getHidClassDrawable(BluetoothClass btClass) {
+ switch (btClass.getDeviceClass()) {
+ case BluetoothClass.Device.PERIPHERAL_KEYBOARD:
+ case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING:
+ return R.drawable.ic_lockscreen_ime;
+ case BluetoothClass.Device.PERIPHERAL_POINTING:
+ return R.drawable.ic_bt_pointing_hid;
+ default:
+ return R.drawable.ic_bt_misc_hid;
+ }
+ }
+
+ protected void finalize() {
+ if (V) Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE,
+ mService);
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up HID proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
new file mode 100644
index 0000000..0c1adec
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.Set;
+
+/**
+ * LocalBluetoothAdapter provides an interface between the Settings app
+ * and the functionality of the local {@link BluetoothAdapter}, specifically
+ * those related to state transitions of the adapter itself.
+ *
+ * <p>Connection and bonding state changes affecting specific devices
+ * are handled by {@link CachedBluetoothDeviceManager},
+ * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}.
+ */
+public final class LocalBluetoothAdapter {
+ private static final String TAG = "LocalBluetoothAdapter";
+
+ /** This class does not allow direct access to the BluetoothAdapter. */
+ private final BluetoothAdapter mAdapter;
+
+ private LocalBluetoothProfileManager mProfileManager;
+
+ private static LocalBluetoothAdapter sInstance;
+
+ private int mState = BluetoothAdapter.ERROR;
+
+ private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins
+
+ private long mLastScan;
+
+ private LocalBluetoothAdapter(BluetoothAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ void setProfileManager(LocalBluetoothProfileManager manager) {
+ mProfileManager = manager;
+ }
+
+ /**
+ * Get the singleton instance of the LocalBluetoothAdapter. If this device
+ * doesn't support Bluetooth, then null will be returned. Callers must be
+ * prepared to handle a null return value.
+ * @return the LocalBluetoothAdapter object, or null if not supported
+ */
+ static synchronized LocalBluetoothAdapter getInstance() {
+ if (sInstance == null) {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ sInstance = new LocalBluetoothAdapter(adapter);
+ }
+ }
+
+ return sInstance;
+ }
+
+ // Pass-through BluetoothAdapter methods that we can intercept if necessary
+
+ public void cancelDiscovery() {
+ mAdapter.cancelDiscovery();
+ }
+
+ public boolean enable() {
+ return mAdapter.enable();
+ }
+
+ public boolean disable() {
+ return mAdapter.disable();
+ }
+
+ void getProfileProxy(Context context,
+ BluetoothProfile.ServiceListener listener, int profile) {
+ mAdapter.getProfileProxy(context, listener, profile);
+ }
+
+ public Set<BluetoothDevice> getBondedDevices() {
+ return mAdapter.getBondedDevices();
+ }
+
+ public String getName() {
+ return mAdapter.getName();
+ }
+
+ public int getScanMode() {
+ return mAdapter.getScanMode();
+ }
+
+ public int getState() {
+ return mAdapter.getState();
+ }
+
+ public ParcelUuid[] getUuids() {
+ return mAdapter.getUuids();
+ }
+
+ public boolean isDiscovering() {
+ return mAdapter.isDiscovering();
+ }
+
+ public boolean isEnabled() {
+ return mAdapter.isEnabled();
+ }
+
+ public void setDiscoverableTimeout(int timeout) {
+ mAdapter.setDiscoverableTimeout(timeout);
+ }
+
+ public void setName(String name) {
+ mAdapter.setName(name);
+ }
+
+ public void setScanMode(int mode) {
+ mAdapter.setScanMode(mode);
+ }
+
+ public boolean setScanMode(int mode, int duration) {
+ return mAdapter.setScanMode(mode, duration);
+ }
+
+ public void startScanning(boolean force) {
+ // Only start if we're not already scanning
+ if (!mAdapter.isDiscovering()) {
+ if (!force) {
+ // Don't scan more than frequently than SCAN_EXPIRATION_MS,
+ // unless forced
+ if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
+ return;
+ }
+
+ // If we are playing music, don't scan unless forced.
+ A2dpProfile a2dp = mProfileManager.getA2dpProfile();
+ if (a2dp != null && a2dp.isA2dpPlaying()) {
+ return;
+ }
+ }
+
+ if (mAdapter.startDiscovery()) {
+ mLastScan = System.currentTimeMillis();
+ }
+ }
+ }
+
+ public void stopScanning() {
+ if (mAdapter.isDiscovering()) {
+ mAdapter.cancelDiscovery();
+ }
+ }
+
+ public synchronized int getBluetoothState() {
+ // Always sync state, in case it changed while paused
+ syncBluetoothState();
+ return mState;
+ }
+
+ synchronized void setBluetoothStateInt(int state) {
+ mState = state;
+
+ if (state == BluetoothAdapter.STATE_ON) {
+ // if mProfileManager hasn't been constructed yet, it will
+ // get the adapter UUIDs in its constructor when it is.
+ if (mProfileManager != null) {
+ mProfileManager.setBluetoothStateOn();
+ }
+ }
+ }
+
+ // Returns true if the state changed; false otherwise.
+ boolean syncBluetoothState() {
+ int currentState = mAdapter.getState();
+ if (currentState != mState) {
+ setBluetoothStateInt(mAdapter.getState());
+ return true;
+ }
+ return false;
+ }
+
+ public void setBluetoothEnabled(boolean enabled) {
+ boolean success = enabled
+ ? mAdapter.enable()
+ : mAdapter.disable();
+
+ if (success) {
+ setBluetoothStateInt(enabled
+ ? BluetoothAdapter.STATE_TURNING_ON
+ : BluetoothAdapter.STATE_TURNING_OFF);
+ } else {
+ if (Utils.V) {
+ Log.v(TAG, "setBluetoothEnabled call, manager didn't return " +
+ "success for enabled: " + enabled);
+ }
+
+ syncBluetoothState();
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
new file mode 100644
index 0000000..4adc62e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * LocalBluetoothManager provides a simplified interface on top of a subset of
+ * the Bluetooth API. Note that {@link #getInstance} will return null
+ * if there is no Bluetooth adapter on this device, and callers must be
+ * prepared to handle this case.
+ */
+public final class LocalBluetoothManager {
+ private static final String TAG = "LocalBluetoothManager";
+
+ /** Singleton instance. */
+ private static LocalBluetoothManager sInstance;
+
+ private final Context mContext;
+
+ /** If a BT-related activity is in the foreground, this will be it. */
+ private Context mForegroundActivity;
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+
+ private final CachedBluetoothDeviceManager mCachedDeviceManager;
+
+ /** The Bluetooth profile manager. */
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ /** The broadcast receiver event manager. */
+ private final BluetoothEventManager mEventManager;
+
+ public static synchronized LocalBluetoothManager getInstance(Context context,
+ BluetoothManagerCallback onInitCallback) {
+ if (sInstance == null) {
+ LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance();
+ if (adapter == null) {
+ return null;
+ }
+ // This will be around as long as this process is
+ Context appContext = context.getApplicationContext();
+ sInstance = new LocalBluetoothManager(adapter, appContext);
+ if (onInitCallback != null) {
+ onInitCallback.onBluetoothManagerInitialized(appContext, sInstance);
+ }
+ }
+
+ return sInstance;
+ }
+
+ private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) {
+ mContext = context;
+ mLocalAdapter = adapter;
+
+ mCachedDeviceManager = new CachedBluetoothDeviceManager(context);
+ mEventManager = new BluetoothEventManager(mLocalAdapter,
+ mCachedDeviceManager, context);
+ mProfileManager = new LocalBluetoothProfileManager(context,
+ mLocalAdapter, mCachedDeviceManager, mEventManager);
+ }
+
+ public LocalBluetoothAdapter getBluetoothAdapter() {
+ return mLocalAdapter;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+
+ public Context getForegroundActivity() {
+ return mForegroundActivity;
+ }
+
+ public boolean isForegroundActivity() {
+ return mForegroundActivity != null;
+ }
+
+ public synchronized void setForegroundActivity(Context context) {
+ if (context != null) {
+ Log.d(TAG, "setting foreground activity to non-null context");
+ mForegroundActivity = context;
+ } else {
+ if (mForegroundActivity != null) {
+ Log.d(TAG, "setting foreground activity to null");
+ mForegroundActivity = null;
+ }
+ }
+ }
+
+ public CachedBluetoothDeviceManager getCachedDeviceManager() {
+ return mCachedDeviceManager;
+ }
+
+ public BluetoothEventManager getEventManager() {
+ return mEventManager;
+ }
+
+ public LocalBluetoothProfileManager getProfileManager() {
+ return mProfileManager;
+ }
+
+ public interface BluetoothManagerCallback {
+ void onBluetoothManagerInitialized(Context appContext,
+ LocalBluetoothManager bluetoothManager);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
new file mode 100755
index 0000000..abcb989
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * LocalBluetoothProfile is an interface defining the basic
+ * functionality related to a Bluetooth profile.
+ */
+public interface LocalBluetoothProfile {
+
+ /**
+ * Returns true if the user can initiate a connection, false otherwise.
+ */
+ boolean isConnectable();
+
+ /**
+ * Returns true if the user can enable auto connection for this profile.
+ */
+ boolean isAutoConnectable();
+
+ boolean connect(BluetoothDevice device);
+
+ boolean disconnect(BluetoothDevice device);
+
+ int getConnectionStatus(BluetoothDevice device);
+
+ boolean isPreferred(BluetoothDevice device);
+
+ int getPreferred(BluetoothDevice device);
+
+ void setPreferred(BluetoothDevice device, boolean preferred);
+
+ boolean isProfileReady();
+
+ /** Display order for device profile settings. */
+ int getOrdinal();
+
+ /**
+ * Returns the string resource ID for the localized name for this profile.
+ * @param device the Bluetooth device (to distinguish between PAN roles)
+ */
+ int getNameResource(BluetoothDevice device);
+
+ /**
+ * Returns the string resource ID for the summary text for this profile
+ * for the specified device, e.g. "Use for media audio" or
+ * "Connected to media audio".
+ * @param device the device to query for profile connection status
+ * @return a string resource ID for the profile summary text
+ */
+ int getSummaryResourceForDevice(BluetoothDevice device);
+
+ int getDrawableResource(BluetoothClass btClass);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
new file mode 100644
index 0000000..b0a7b27
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.content.Intent;
+import android.os.ParcelUuid;
+import android.util.Log;
+import android.os.Handler;
+import android.os.Message;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+
+/**
+ * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile
+ * objects for the available Bluetooth profiles.
+ */
+public final class LocalBluetoothProfileManager {
+ private static final String TAG = "LocalBluetoothProfileManager";
+ private static final boolean DEBUG = Utils.D;
+ /** Singleton instance. */
+ private static LocalBluetoothProfileManager sInstance;
+
+ /**
+ * An interface for notifying BluetoothHeadset IPC clients when they have
+ * been connected to the BluetoothHeadset service.
+ * Only used by com.android.settings.bluetooth.DockService.
+ */
+ public interface ServiceListener {
+ /**
+ * Called to notify the client when this proxy object has been
+ * connected to the BluetoothHeadset service. Clients must wait for
+ * this callback before making IPC calls on the BluetoothHeadset
+ * service.
+ */
+ void onServiceConnected();
+
+ /**
+ * Called to notify the client that this proxy object has been
+ * disconnected from the BluetoothHeadset service. Clients must not
+ * make IPC calls on the BluetoothHeadset service after this callback.
+ * This callback will currently only occur if the application hosting
+ * the BluetoothHeadset service, but may be called more often in future.
+ */
+ void onServiceDisconnected();
+ }
+
+ private final Context mContext;
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final BluetoothEventManager mEventManager;
+
+ private A2dpProfile mA2dpProfile;
+ private HeadsetProfile mHeadsetProfile;
+ private MapProfile mMapProfile;
+ private final HidProfile mHidProfile;
+ private OppProfile mOppProfile;
+ private final PanProfile mPanProfile;
+ private final PbapServerProfile mPbapProfile;
+
+ /**
+ * Mapping from profile name, e.g. "HEADSET" to profile object.
+ */
+ private final Map<String, LocalBluetoothProfile>
+ mProfileNameMap = new HashMap<String, LocalBluetoothProfile>();
+
+ LocalBluetoothProfileManager(Context context,
+ LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager,
+ BluetoothEventManager eventManager) {
+ mContext = context;
+
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mEventManager = eventManager;
+ // pass this reference to adapter and event manager (circular dependency)
+ mLocalAdapter.setProfileManager(this);
+ mEventManager.setProfileManager(this);
+
+ ParcelUuid[] uuids = adapter.getUuids();
+
+ // uuids may be null if Bluetooth is turned off
+ if (uuids != null) {
+ updateLocalProfiles(uuids);
+ }
+
+ // Always add HID and PAN profiles
+ mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this);
+ addProfile(mHidProfile, HidProfile.NAME,
+ BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
+
+ mPanProfile = new PanProfile(context);
+ addPanProfile(mPanProfile, PanProfile.NAME,
+ BluetoothPan.ACTION_CONNECTION_STATE_CHANGED);
+
+ if(DEBUG) Log.d(TAG, "Adding local MAP profile");
+ mMapProfile = new MapProfile(mContext, mLocalAdapter,
+ mDeviceManager, this);
+ addProfile(mMapProfile, MapProfile.NAME,
+ BluetoothMap.ACTION_CONNECTION_STATE_CHANGED);
+
+ //Create PBAP server profile, but do not add it to list of profiles
+ // as we do not need to monitor the profile as part of profile list
+ mPbapProfile = new PbapServerProfile(context);
+
+ if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
+ }
+
+ /**
+ * Initialize or update the local profile objects. If a UUID was previously
+ * present but has been removed, we print a warning but don't remove the
+ * profile object as it might be referenced elsewhere, or the UUID might
+ * come back and we don't want multiple copies of the profile objects.
+ * @param uuids
+ */
+ void updateLocalProfiles(ParcelUuid[] uuids) {
+ // A2DP
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) {
+ if (mA2dpProfile == null) {
+ if(DEBUG) Log.d(TAG, "Adding local A2DP profile");
+ mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this);
+ addProfile(mA2dpProfile, A2dpProfile.NAME,
+ BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ } else if (mA2dpProfile != null) {
+ Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing.");
+ }
+
+ // Headset / Handsfree
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) ||
+ BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) {
+ if (mHeadsetProfile == null) {
+ if (DEBUG) Log.d(TAG, "Adding local HEADSET profile");
+ mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter,
+ mDeviceManager, this);
+ addProfile(mHeadsetProfile, HeadsetProfile.NAME,
+ BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+ }
+ } else if (mHeadsetProfile != null) {
+ Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing.");
+ }
+
+ // OPP
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) {
+ if (mOppProfile == null) {
+ if(DEBUG) Log.d(TAG, "Adding local OPP profile");
+ mOppProfile = new OppProfile();
+ // Note: no event handler for OPP, only name map.
+ mProfileNameMap.put(OppProfile.NAME, mOppProfile);
+ }
+ } else if (mOppProfile != null) {
+ Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
+ }
+ mEventManager.registerProfileIntentReceiver();
+
+ // There is no local SDP record for HID and Settings app doesn't control PBAP
+ }
+
+ private final Collection<ServiceListener> mServiceListeners =
+ new ArrayList<ServiceListener>();
+
+ private void addProfile(LocalBluetoothProfile profile,
+ String profileName, String stateChangedAction) {
+ mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile));
+ mProfileNameMap.put(profileName, profile);
+ }
+
+ private void addPanProfile(LocalBluetoothProfile profile,
+ String profileName, String stateChangedAction) {
+ mEventManager.addProfileHandler(stateChangedAction,
+ new PanStateChangedHandler(profile));
+ mProfileNameMap.put(profileName, profile);
+ }
+
+ public LocalBluetoothProfile getProfileByName(String name) {
+ return mProfileNameMap.get(name);
+ }
+
+ // Called from LocalBluetoothAdapter when state changes to ON
+ void setBluetoothStateOn() {
+ ParcelUuid[] uuids = mLocalAdapter.getUuids();
+ if (uuids != null) {
+ updateLocalProfiles(uuids);
+ }
+ mEventManager.readPairedDevices();
+ }
+
+ /**
+ * Generic handler for connection state change events for the specified profile.
+ */
+ private class StateChangedHandler implements BluetoothEventManager.Handler {
+ final LocalBluetoothProfile mProfile;
+
+ StateChangedHandler(LocalBluetoothProfile profile) {
+ mProfile = profile;
+ }
+
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
+ if (cachedDevice == null) {
+ Log.w(TAG, "StateChangedHandler found new device: " + device);
+ cachedDevice = mDeviceManager.addDevice(mLocalAdapter,
+ LocalBluetoothProfileManager.this, device);
+ }
+ int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
+ int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0);
+ if (newState == BluetoothProfile.STATE_DISCONNECTED &&
+ oldState == BluetoothProfile.STATE_CONNECTING) {
+ Log.i(TAG, "Failed to connect " + mProfile + " device");
+ }
+
+ cachedDevice.onProfileStateChanged(mProfile, newState);
+ cachedDevice.refresh();
+ }
+ }
+
+ /** State change handler for NAP and PANU profiles. */
+ private class PanStateChangedHandler extends StateChangedHandler {
+
+ PanStateChangedHandler(LocalBluetoothProfile profile) {
+ super(profile);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ PanProfile panProfile = (PanProfile) mProfile;
+ int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0);
+ panProfile.setLocalRole(device, role);
+ super.onReceive(context, intent, device);
+ }
+ }
+
+ // called from DockService
+ public void addServiceListener(ServiceListener l) {
+ mServiceListeners.add(l);
+ }
+
+ // called from DockService
+ public void removeServiceListener(ServiceListener l) {
+ mServiceListeners.remove(l);
+ }
+
+ // not synchronized: use only from UI thread! (TODO: verify)
+ void callServiceConnectedListeners() {
+ for (ServiceListener l : mServiceListeners) {
+ l.onServiceConnected();
+ }
+ }
+
+ // not synchronized: use only from UI thread! (TODO: verify)
+ void callServiceDisconnectedListeners() {
+ for (ServiceListener listener : mServiceListeners) {
+ listener.onServiceDisconnected();
+ }
+ }
+
+ // This is called by DockService, so check Headset and A2DP.
+ public synchronized boolean isManagerReady() {
+ // Getting just the headset profile is fine for now. Will need to deal with A2DP
+ // and others if they aren't always in a ready state.
+ LocalBluetoothProfile profile = mHeadsetProfile;
+ if (profile != null) {
+ return profile.isProfileReady();
+ }
+ profile = mA2dpProfile;
+ if (profile != null) {
+ return profile.isProfileReady();
+ }
+ return false;
+ }
+
+ public A2dpProfile getA2dpProfile() {
+ return mA2dpProfile;
+ }
+
+ public HeadsetProfile getHeadsetProfile() {
+ return mHeadsetProfile;
+ }
+
+ public PbapServerProfile getPbapProfile(){
+ return mPbapProfile;
+ }
+
+ public MapProfile getMapProfile(){
+ return mMapProfile;
+ }
+
+ /**
+ * Fill in a list of LocalBluetoothProfile objects that are supported by
+ * the local device and the remote device.
+ *
+ * @param uuids of the remote device
+ * @param localUuids UUIDs of the local device
+ * @param profiles The list of profiles to fill
+ * @param removedProfiles list of profiles that were removed
+ */
+ synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids,
+ Collection<LocalBluetoothProfile> profiles,
+ Collection<LocalBluetoothProfile> removedProfiles,
+ boolean isPanNapConnected, BluetoothDevice device) {
+ // Copy previous profile list into removedProfiles
+ removedProfiles.clear();
+ removedProfiles.addAll(profiles);
+ profiles.clear();
+
+ if (uuids == null) {
+ return;
+ }
+
+ if (mHeadsetProfile != null) {
+ if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) &&
+ BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) ||
+ (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) &&
+ BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) {
+ profiles.add(mHeadsetProfile);
+ removedProfiles.remove(mHeadsetProfile);
+ }
+ }
+
+ if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) &&
+ mA2dpProfile != null) {
+ profiles.add(mA2dpProfile);
+ removedProfiles.remove(mA2dpProfile);
+ }
+
+ if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) &&
+ mOppProfile != null) {
+ profiles.add(mOppProfile);
+ removedProfiles.remove(mOppProfile);
+ }
+
+ if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) ||
+ BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) &&
+ mHidProfile != null) {
+ profiles.add(mHidProfile);
+ removedProfiles.remove(mHidProfile);
+ }
+
+ if(isPanNapConnected)
+ if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists.");
+ if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) &&
+ mPanProfile != null) || isPanNapConnected) {
+ profiles.add(mPanProfile);
+ removedProfiles.remove(mPanProfile);
+ }
+
+ if ((mMapProfile != null) &&
+ (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
+ profiles.add(mMapProfile);
+ removedProfiles.remove(mMapProfile);
+ mMapProfile.setPreferred(device, true);
+ }
+ }
+
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
new file mode 100644
index 0000000..e6a152f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MapProfile handles Bluetooth MAP profile.
+ */
+public final class MapProfile implements LocalBluetoothProfile {
+ private static final String TAG = "MapProfile";
+ private static boolean V = true;
+
+ private BluetoothMap mService;
+ private boolean mIsProfileReady;
+
+ private final LocalBluetoothAdapter mLocalAdapter;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ static final ParcelUuid[] UUIDS = {
+ BluetoothUuid.MAP,
+ BluetoothUuid.MNS,
+ BluetoothUuid.MAS,
+ };
+
+ static final String NAME = "MAP";
+
+ // Order of this profile in device profiles list
+
+ // These callbacks run on the main thread.
+ private final class MapServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (V) Log.d(TAG,"Bluetooth service connected");
+ mService = (BluetoothMap) proxy;
+ // We just bound to the service, so refresh the UI for any connected MAP devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ Log.w(TAG, "MapProfile found new device: " + nextDevice);
+ device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+ }
+ device.onProfileStateChanged(MapProfile.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady=true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (V) Log.d(TAG,"Bluetooth service disconnected");
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady=false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady);
+ return mIsProfileReady;
+ }
+
+ MapProfile(Context context, LocalBluetoothAdapter adapter,
+ CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mLocalAdapter = adapter;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+ mLocalAdapter.getProfileProxy(context, new MapServiceListener(),
+ BluetoothProfile.MAP);
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return true;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ if(V)Log.d(TAG,"connect() - should not get called");
+ return false; // MAP never connects out
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) return false;
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) {
+ if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ return mService.disconnect(device);
+ } else {
+ return false;
+ }
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device));
+
+ return !deviceList.isEmpty() && deviceList.get(0).equals(device)
+ ? mService.getConnectionState(device)
+ : BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ if (mService == null) return BluetoothProfile.PRIORITY_OFF;
+ return mService.getPriority(device);
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ if (mService == null) return;
+ if (preferred) {
+ if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+ }
+ } else {
+ mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+ }
+ }
+
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) return new ArrayList<BluetoothDevice>(0);
+ return mService.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return BluetoothProfile.MAP;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_map;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_map_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_map_profile_summary_connected;
+
+ default:
+ return Utils.getConnectionStateSummary(state);
+ }
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_cellphone;
+ }
+
+ protected void finalize() {
+ if (V) Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP,
+ mService);
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up MAP proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
new file mode 100755
index 0000000..31e675c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import com.android.settingslib.R;
+
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+
+/**
+ * OppProfile handles Bluetooth OPP.
+ */
+final class OppProfile implements LocalBluetoothProfile {
+
+ static final String NAME = "OPP";
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 2;
+
+ public boolean isConnectable() {
+ return false;
+ }
+
+ public boolean isAutoConnectable() {
+ return false;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ return false;
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ return false;
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ return false;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ }
+
+ public boolean isProfileReady() {
+ return true;
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_opp;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ return 0; // OPP profile not displayed in UI
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return 0; // no icon for OPP
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
new file mode 100755
index 0000000..3af89e6
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * PanProfile handles Bluetooth PAN profile (NAP and PANU).
+ */
+final class PanProfile implements LocalBluetoothProfile {
+ private static final String TAG = "PanProfile";
+ private static boolean V = true;
+
+ private BluetoothPan mService;
+ private boolean mIsProfileReady;
+
+ // Tethering direction for each device
+ private final HashMap<BluetoothDevice, Integer> mDeviceRoleMap =
+ new HashMap<BluetoothDevice, Integer>();
+
+ static final String NAME = "PAN";
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 4;
+
+ // These callbacks run on the main thread.
+ private final class PanServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (V) Log.d(TAG,"Bluetooth service connected");
+ mService = (BluetoothPan) proxy;
+ mIsProfileReady=true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (V) Log.d(TAG,"Bluetooth service disconnected");
+ mIsProfileReady=false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ PanProfile(Context context) {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ adapter.getProfileProxy(context, new PanServiceListener(),
+ BluetoothProfile.PAN);
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return false;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ if (mService == null) return false;
+ List<BluetoothDevice> sinks = mService.getConnectedDevices();
+ if (sinks != null) {
+ for (BluetoothDevice sink : sinks) {
+ mService.disconnect(sink);
+ }
+ }
+ return mService.connect(device);
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.disconnect(device);
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ // return current connection status so profile checkbox is set correctly
+ return getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ return -1;
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ // ignore: isPreferred is always true for PAN
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ if (isLocalRoleNap(device)) {
+ return R.string.bluetooth_profile_pan_nap;
+ } else {
+ return R.string.bluetooth_profile_pan;
+ }
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ int state = getConnectionStatus(device);
+ switch (state) {
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_pan_profile_summary_use_for;
+
+ case BluetoothProfile.STATE_CONNECTED:
+ if (isLocalRoleNap(device)) {
+ return R.string.bluetooth_pan_nap_profile_summary_connected;
+ } else {
+ return R.string.bluetooth_pan_user_profile_summary_connected;
+ }
+
+ default:
+ return Utils.getConnectionStateSummary(state);
+ }
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_network_pan;
+ }
+
+ // Tethering direction determines UI strings.
+ void setLocalRole(BluetoothDevice device, int role) {
+ mDeviceRoleMap.put(device, role);
+ }
+
+ boolean isLocalRoleNap(BluetoothDevice device) {
+ if (mDeviceRoleMap.containsKey(device)) {
+ return mDeviceRoleMap.get(device) == BluetoothPan.LOCAL_NAP_ROLE;
+ } else {
+ return false;
+ }
+ }
+
+ protected void finalize() {
+ if (V) Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService);
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up PAN proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
new file mode 100755
index 0000000..a552b24a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothPbap;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * PBAPServer Profile
+ */
+public final class PbapServerProfile implements LocalBluetoothProfile {
+ private static final String TAG = "PbapServerProfile";
+ private static boolean V = true;
+
+ private BluetoothPbap mService;
+ private boolean mIsProfileReady;
+
+ static final String NAME = "PBAP Server";
+
+ // Order of this profile in device profiles list
+ private static final int ORDINAL = 6;
+
+ // The UUIDs indicate that remote device might access pbap server
+ static final ParcelUuid[] PBAB_CLIENT_UUIDS = {
+ BluetoothUuid.HSP,
+ BluetoothUuid.Handsfree,
+ BluetoothUuid.PBAP_PCE
+ };
+
+ // These callbacks run on the main thread.
+ private final class PbapServiceListener
+ implements BluetoothPbap.ServiceListener {
+
+ public void onServiceConnected(BluetoothPbap proxy) {
+ if (V) Log.d(TAG,"Bluetooth service connected");
+ mService = (BluetoothPbap) proxy;
+ mIsProfileReady=true;
+ }
+
+ public void onServiceDisconnected() {
+ if (V) Log.d(TAG,"Bluetooth service disconnected");
+ mIsProfileReady=false;
+ }
+ }
+
+ public boolean isProfileReady() {
+ return mIsProfileReady;
+ }
+
+ PbapServerProfile(Context context) {
+ BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener());
+ }
+
+ public boolean isConnectable() {
+ return true;
+ }
+
+ public boolean isAutoConnectable() {
+ return false;
+ }
+
+ public boolean connect(BluetoothDevice device) {
+ /*Can't connect from server */
+ return false;
+
+ }
+
+ public boolean disconnect(BluetoothDevice device) {
+ if (mService == null) return false;
+ return mService.disconnect();
+ }
+
+ public int getConnectionStatus(BluetoothDevice device) {
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ if (mService.isConnected(device))
+ return BluetoothProfile.STATE_CONNECTED;
+ else
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ public boolean isPreferred(BluetoothDevice device) {
+ return false;
+ }
+
+ public int getPreferred(BluetoothDevice device) {
+ return -1;
+ }
+
+ public void setPreferred(BluetoothDevice device, boolean preferred) {
+ // ignore: isPreferred is always true for PBAP
+ }
+
+ public String toString() {
+ return NAME;
+ }
+
+ public int getOrdinal() {
+ return ORDINAL;
+ }
+
+ public int getNameResource(BluetoothDevice device) {
+ return R.string.bluetooth_profile_pbap;
+ }
+
+ public int getSummaryResourceForDevice(BluetoothDevice device) {
+ return R.string.bluetooth_profile_pbap_summary;
+ }
+
+ public int getDrawableResource(BluetoothClass btClass) {
+ return R.drawable.ic_bt_cellphone;
+ }
+
+ protected void finalize() {
+ if (V) Log.d(TAG, "finalize()");
+ if (mService != null) {
+ try {
+ mService.close();
+ mService = null;
+ }catch (Throwable t) {
+ Log.w(TAG, "Error cleaning up PBAP proxy", t);
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
new file mode 100644
index 0000000..c919426
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java
@@ -0,0 +1,43 @@
+package com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+
+public class Utils {
+ public static final boolean V = false; // verbose logging
+ public static final boolean D = true; // regular logging
+
+ private static ErrorListener sErrorListener;
+
+ public static int getConnectionStateSummary(int connectionState) {
+ switch (connectionState) {
+ case BluetoothProfile.STATE_CONNECTED:
+ return R.string.bluetooth_connected;
+ case BluetoothProfile.STATE_CONNECTING:
+ return R.string.bluetooth_connecting;
+ case BluetoothProfile.STATE_DISCONNECTED:
+ return R.string.bluetooth_disconnected;
+ case BluetoothProfile.STATE_DISCONNECTING:
+ return R.string.bluetooth_disconnecting;
+ default:
+ return 0;
+ }
+ }
+
+ static void showError(Context context, String name, int messageResId) {
+ if (sErrorListener != null) {
+ sErrorListener.onShowError(context, name, messageResId);
+ }
+ }
+
+ public static void setErrorListener(ErrorListener listener) {
+ sErrorListener = listener;
+ }
+
+ public interface ErrorListener {
+ void onShowError(Context context, String name, int messageResId);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 25bab17..c23f45d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -114,7 +114,9 @@
@Override
protected void onDestroy() {
super.onDestroy();
- mDialog.dismiss();
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
}
@Override
@@ -128,7 +130,9 @@
Log.e(TAG, "Error granting projection permission", e);
setResult(RESULT_CANCELED);
} finally {
- mDialog.dismiss();
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
finish();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
index 0863c86..7ca91a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java
@@ -22,7 +22,6 @@
boolean isHotspotEnabled();
boolean isHotspotSupported();
void setHotspotEnabled(boolean enabled);
- boolean isProvisioningNeeded();
public interface Callback {
void onHotspotChanged(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index 5eff5a6..4bfd528 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -16,45 +16,38 @@
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
+import com.android.settingslib.TetherUtil;
+
import java.util.ArrayList;
public class HotspotControllerImpl implements HotspotController {
private static final String TAG = "HotspotController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- // Keep these in sync with Settings TetherService.java
- public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
- public static final String EXTRA_SET_ALARM = "extraSetAlarm";
- public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
- public static final String EXTRA_ENABLE_WIFI_TETHER = "extraEnableWifiTether";
- // Keep this in sync with Settings TetherSettings.java
- public static final int WIFI_TETHERING = 0;
+ private static final Intent TETHER_SERVICE_INTENT = new Intent()
+ .putExtra(TetherUtil.EXTRA_ADD_TETHER_TYPE, TetherUtil.TETHERING_WIFI)
+ .putExtra(TetherUtil.EXTRA_SET_ALARM, true)
+ .putExtra(TetherUtil.EXTRA_RUN_PROVISION, true)
+ .putExtra(TetherUtil.EXTRA_ENABLE_WIFI_TETHER, true)
+ .setComponent(TetherUtil.TETHER_SERVICE);
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
private final Receiver mReceiver = new Receiver();
private final Context mContext;
private final WifiManager mWifiManager;
- private final ConnectivityManager mConnectivityManager;
public HotspotControllerImpl(Context context) {
mContext = context;
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- mConnectivityManager = (ConnectivityManager)
- mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
public void addCallback(Callback callback) {
@@ -78,54 +71,17 @@
@Override
public boolean isHotspotSupported() {
- final boolean isSecondaryUser = ActivityManager.getCurrentUser() != UserHandle.USER_OWNER;
- return !isSecondaryUser && mConnectivityManager.isTetheringSupported();
- }
-
- @Override
- public boolean isProvisioningNeeded() {
- // Keep in sync with other usage of config_mobile_hotspot_provision_app.
- // TetherSettings#isProvisioningNeeded and
- // ConnectivityManager#enforceTetherChangePermission
- String[] provisionApp = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_mobile_hotspot_provision_app);
- if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)
- || provisionApp == null) {
- return false;
- }
- return (provisionApp.length == 2);
+ return TetherUtil.isTetheringSupported(mContext);
}
@Override
public void setHotspotEnabled(boolean enabled) {
final ContentResolver cr = mContext.getContentResolver();
// Call provisioning app which is called when enabling Tethering from Settings
- if (enabled) {
- if (isProvisioningNeeded()) {
- String tetherEnable = mContext.getResources().getString(
- com.android.internal.R.string.config_wifi_tether_enable);
- Intent intent = new Intent();
- intent.putExtra(EXTRA_ADD_TETHER_TYPE, WIFI_TETHERING);
- intent.putExtra(EXTRA_SET_ALARM, true);
- intent.putExtra(EXTRA_RUN_PROVISION, true);
- intent.putExtra(EXTRA_ENABLE_WIFI_TETHER, true);
- intent.setComponent(ComponentName.unflattenFromString(tetherEnable));
- mContext.startServiceAsUser(intent, UserHandle.CURRENT);
- } else {
- int wifiState = mWifiManager.getWifiState();
- if ((wifiState == WifiManager.WIFI_STATE_ENABLING) ||
- (wifiState == WifiManager.WIFI_STATE_ENABLED)) {
- mWifiManager.setWifiEnabled(false);
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 1);
- }
- mWifiManager.setWifiApEnabled(null, true);
- }
+ if (enabled && TetherUtil.isProvisioningNeeded(mContext)) {
+ mContext.startServiceAsUser(TETHER_SERVICE_INTENT, UserHandle.CURRENT);
} else {
- mWifiManager.setWifiApEnabled(null, false);
- if (Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE, 0) == 1) {
- mWifiManager.setWifiEnabled(true);
- Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
- }
+ TetherUtil.setWifiTethering(enabled, mContext);
}
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 4d7ebed..acf4d39 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -2778,7 +2778,22 @@
@Override
public void operationComplete() {
- // Okay, the agent successfully reported back to us!
+ // The agent reported back to us!
+
+ if (mBackupData == null) {
+ // This callback was racing with our timeout, so we've cleaned up the
+ // agent state already and are on to the next thing. We have nothing
+ // further to do here: agent state having been cleared means that we've
+ // initiated the appropriate next operation.
+ final String pkg = (mCurrentPackage != null)
+ ? mCurrentPackage.packageName : "[none]";
+ if (DEBUG) {
+ Slog.i(TAG, "Callback after agent teardown: " + pkg);
+ }
+ addBackupTrace("late opComplete; curPkg = " + pkg);
+ return;
+ }
+
final String pkgName = mCurrentPackage.packageName;
final long filepos = mBackupDataName.length();
FileDescriptor fd = mBackupData.getFileDescriptor();
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 8bd7132..2e84fbe 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -318,6 +318,8 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
BackupManagerService svc = mService;
if (svc != null) {
svc.dump(fd, pw, args);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6229778..008d718 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2398,8 +2398,7 @@
} else {
finishRunningVoiceLocked();
}
- mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity");
- if (r != null) {
+ if (r != null && mStackSupervisor.setFocusedStack(r, reason + " setFocusedActivity")) {
mWindowManager.setFocusedApp(r.appToken, true);
}
applyUpdateLockStateLocked(r);
@@ -2423,6 +2422,7 @@
ActivityRecord r = stack.topRunningActivityLocked(null);
if (r != null) {
setFocusedActivityLocked(r, "setFocusedStack");
+ mStackSupervisor.resumeTopActivitiesLocked(stack, null, null);
}
}
}
@@ -7909,6 +7909,25 @@
}
@Override
+ public void resizeTask(int taskId, Rect bounds) {
+ enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "resizeTask()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (this) {
+ TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId, true);
+ if (task == null) {
+ Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
+ return;
+ }
+ mStackSupervisor.resizeTaskLocked(task, bounds);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public Bitmap getTaskDescriptionIcon(String filename) {
if (!FileUtils.isValidExtFilename(filename)
|| !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 83a7b68..c073df6 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -152,25 +152,25 @@
* The back history of all previous (and possibly still
* running) activities. It contains #TaskRecord objects.
*/
- private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>();
+ private ArrayList<TaskRecord> mTaskHistory = new ArrayList<>();
/**
* Used for validating app tokens with window manager.
*/
- final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<TaskGroup>();
+ final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<>();
/**
* List of running activities, sorted by recent usage.
* The first entry in the list is the least recently used.
* It contains HistoryRecord objects.
*/
- final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>();
/**
* Animations that for the current transition have requested not to
* be considered for the transition animation.
*/
- final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>();
/**
* When we are in the process of pausing an activity, before starting the
@@ -346,6 +346,10 @@
return count;
}
+ int numTasks() {
+ return mTaskHistory.size();
+ }
+
ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer,
RecentTasks recentTasks) {
mActivityContainer = activityContainer;
@@ -492,11 +496,19 @@
final void moveToFront(String reason) {
if (isAttached()) {
- if (isOnHomeDisplay()) {
- mStackSupervisor.moveHomeStack(isHomeStack(), reason);
+ final boolean homeStack = isHomeStack()
+ || (mActivityContainer.mParentActivity != null
+ && mActivityContainer.mParentActivity.isHomeActivity());
+
+ if (!homeStack) {
+ // Need to move this stack to the front before calling
+ // {@link ActivityStackSupervisor#moveHomeStack} below.
+ mStacks.remove(this);
+ mStacks.add(this);
}
- mStacks.remove(this);
- mStacks.add(this);
+ if (isOnHomeDisplay()) {
+ mStackSupervisor.moveHomeStack(homeStack, reason);
+ }
final TaskRecord task = topTask();
if (task != null) {
mWindowManager.moveTaskToTop(task.taskId);
@@ -1154,6 +1166,23 @@
return null;
}
+ private ActivityStack getNextVisibleStackLocked() {
+ ArrayList<ActivityStack> stacks = mStacks;
+ final ActivityRecord parent = mActivityContainer.mParentActivity;
+ if (parent != null) {
+ stacks = parent.task.stack.mStacks;
+ }
+ if (stacks != null) {
+ for (int i = stacks.size() - 1; i >= 0; --i) {
+ ActivityStack stack = stacks.get(i);
+ if (stack != this && stack.isStackVisibleLocked()) {
+ return stack;
+ }
+ }
+ }
+ return null;
+ }
+
// Checks if any of the stacks above this one has a fullscreen activity behind it.
// If so, this stack is hidden, otherwise it is visible.
private boolean isStackVisibleLocked() {
@@ -1482,7 +1511,7 @@
return result;
}
- final boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
+ private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {
if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen("");
if (!mService.mBooting && !mService.mBooted) {
@@ -1510,8 +1539,17 @@
final TaskRecord prevTask = prev != null ? prev.task : null;
if (next == null) {
- // There are no more activities! Let's just start up the
- // Launcher...
+ // There are no more activities!
+ final String reason = "noMoreActivities";
+ if (!mFullscreen) {
+ // Try to move focus to the next visible stack with a running activity if this
+ // stack is not covering the entire screen.
+ final ActivityStack stack = getNextVisibleStackLocked();
+ if (adjustFocusToNextVisibleStackLocked(stack, reason)) {
+ return mStackSupervisor.resumeTopActivitiesLocked(stack, prev, null);
+ }
+ }
+ // Let's just start up the Launcher...
ActivityOptions.abort(options);
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home");
if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
@@ -1519,7 +1557,7 @@
final int returnTaskType = prevTask == null || !prevTask.isOverHomeStack() ?
HOME_ACTIVITY_TYPE : prevTask.getTaskToReturnTo();
return isOnHomeDisplay() &&
- mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, "noMoreActivities");
+ mStackSupervisor.resumeHomeStackTask(returnTaskType, prev, reason);
}
next.delayedResume = false;
@@ -2516,20 +2554,44 @@
private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
ActivityRecord next = topRunningActivityLocked(null);
+ final String myReason = reason + " adjustFocus";
if (next != r) {
final TaskRecord task = r.task;
if (r.frontOfTask && task == topTask() && task.isOverHomeStack()) {
- mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(),
- reason + " adjustFocus");
+ // For non-fullscreen stack, we want to move the focus to the next visible
+ // stack to prevent the home screen from moving to the top and obscuring
+ // other visible stacks.
+ if (!mFullscreen
+ && adjustFocusToNextVisibleStackLocked(null, myReason)) {
+ return;
+ }
+ // Move the home stack to the top if this stack is fullscreen or there is no
+ // other visible stack.
+ mStackSupervisor.moveHomeStackTaskToTop(task.getTaskToReturnTo(), myReason);
}
}
- ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
+
+ final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
if (top != null) {
- mService.setFocusedActivityLocked(top, reason + " adjustTopFocus");
+ mService.setFocusedActivityLocked(top, myReason);
}
}
}
+ private boolean adjustFocusToNextVisibleStackLocked(ActivityStack inStack, String reason) {
+ final ActivityStack stack = (inStack != null) ? inStack : getNextVisibleStackLocked();
+ final String myReason = reason + " adjustFocusToNextVisibleStack";
+ if (stack == null) {
+ return false;
+ }
+ final ActivityRecord top = stack.topRunningActivityLocked(null);
+ if (top == null) {
+ return false;
+ }
+ mService.setFocusedActivityLocked(top, myReason);
+ return true;
+ }
+
final void stopActivityLocked(ActivityRecord r) {
if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r);
if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
@@ -2999,6 +3061,8 @@
* representation) and cleaning things up as a result of its hosting
* processing going away, in which case there is no remaining client-side
* state to destroy so only the cleanup here is needed.
+ *
+ * Note: Call before #removeActivityFromHistoryLocked.
*/
final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices,
boolean setState) {
@@ -3410,7 +3474,7 @@
if (DEBUG_CLEANUP) Slog.v(
TAG, "Record #" + i + " " + r + ": app=" + r.app);
if (r.app == app) {
- boolean remove;
+ final boolean remove;
if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
// Don't currently have state for the activity, or
// it is finishing -- always remove it.
@@ -3444,8 +3508,6 @@
mService.updateUsageStats(r, false);
}
}
- removeActivityFromHistoryLocked(r, "appDied");
-
} else {
// We have the current state for this activity, so
// it can be restarted later when needed.
@@ -3464,8 +3526,10 @@
r.icicle = null;
}
}
-
cleanUpActivityLocked(r, true, true);
+ if (remove) {
+ removeActivityFromHistoryLocked(r, "appDied");
+ }
}
}
}
@@ -3599,8 +3663,7 @@
}
}
- if (DEBUG_TRANSITION) Slog.v(TAG,
- "Prepare to back transition: task=" + taskId);
+ if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to back transition: task=" + taskId);
boolean prevIsHome = false;
if (tr.isOverHomeStack()) {
@@ -4126,8 +4189,14 @@
}
void removeTask(TaskRecord task, String reason) {
+ removeTask(task, reason, true);
+ }
+
+ void removeTask(TaskRecord task, String reason, boolean removeFromWindowManager) {
mStackSupervisor.endLockTaskModeIfTaskEnding(task);
- mWindowManager.removeTask(task.taskId);
+ if (removeFromWindowManager) {
+ mWindowManager.removeTask(task.taskId);
+ }
final ActivityRecord r = mResumedActivity;
if (r != null && r.task == task) {
mResumedActivity = null;
@@ -4161,10 +4230,13 @@
}
if (mTaskHistory.isEmpty()) {
- if (DEBUG_STACK) Slog.i(TAG, "removeTask: moving to back stack=" + this);
+ if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack=" + this);
final boolean notHomeStack = !isHomeStack();
if (isOnHomeDisplay()) {
- mStackSupervisor.moveHomeStack(notHomeStack, reason + " leftTaskHistoryEmpty");
+ String myReason = reason + " leftTaskHistoryEmpty";
+ if (mFullscreen || !adjustFocusToNextVisibleStackLocked(null, myReason)) {
+ mStackSupervisor.moveHomeStack(notHomeStack, myReason);
+ }
}
if (mStacks != null) {
mStacks.remove(this);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 9fe3c48..f6ef295 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -386,8 +386,8 @@
return mLastFocusedStack;
}
- // TODO: Split into two methods isFrontStack for any visible stack and isFrontmostStack for the
- // top of all visible stacks.
+ /** Top of all visible stacks. Use {@link ActivityStack#isStackVisibleLocked} to determine if a
+ * specific stack is visible or not. */
boolean isFrontStack(ActivityStack stack) {
final ActivityRecord parent = stack.mActivityContainer.mParentActivity;
if (parent != null) {
@@ -535,7 +535,7 @@
}
ActivityRecord resumedAppLocked() {
- ActivityStack stack = getFocusedStack();
+ ActivityStack stack = mFocusedStack;
if (stack == null) {
return null;
}
@@ -739,7 +739,7 @@
}
ActivityRecord topRunningActivityLocked() {
- final ActivityStack focusedStack = getFocusedStack();
+ final ActivityStack focusedStack = mFocusedStack;
ActivityRecord r = focusedStack.topRunningActivityLocked(null);
if (r != null) {
return r;
@@ -885,7 +885,7 @@
final ActivityStack stack;
if (container == null || container.mStack.isOnHomeDisplay()) {
- stack = getFocusedStack();
+ stack = mFocusedStack;
} else {
stack = container.mStack;
}
@@ -1502,7 +1502,7 @@
outActivity[0] = r;
}
- final ActivityStack stack = getFocusedStack();
+ final ActivityStack stack = mFocusedStack;
if (voiceSession == null && (stack.mResumedActivity == null
|| stack.mResumedActivity.info.applicationInfo.uid != callingUid)) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
@@ -1541,25 +1541,27 @@
return err;
}
- ActivityStack adjustStackFocus(ActivityRecord r, boolean newTask) {
+ ActivityStack computeStackFocus(ActivityRecord r, boolean newTask) {
final TaskRecord task = r.task;
// On leanback only devices we should keep all activities in the same stack.
if (!mLeanbackOnlyDevice &&
(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
+
+ ActivityStack stack;
+
if (task != null) {
- final ActivityStack taskStack = task.stack;
- if (taskStack.isOnHomeDisplay()) {
- if (mFocusedStack != taskStack) {
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: Setting " +
+ stack = task.stack;
+ if (stack.isOnHomeDisplay()) {
+ if (mFocusedStack != stack) {
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "computeStackFocus: Setting " +
"focused stack to r=" + r + " task=" + task);
- mFocusedStack = taskStack;
} else {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
- "adjustStackFocus: Focused stack already=" + mFocusedStack);
+ "computeStackFocus: Focused stack already=" + mFocusedStack);
}
}
- return taskStack;
+ return stack;
}
final ActivityContainer container = r.mInitialActivityContainer;
@@ -1572,43 +1574,41 @@
if (mFocusedStack != mHomeStack && (!newTask ||
mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
- "adjustStackFocus: Have a focused stack=" + mFocusedStack);
+ "computeStackFocus: Have a focused stack=" + mFocusedStack);
return mFocusedStack;
}
final ArrayList<ActivityStack> homeDisplayStacks = mHomeStack.mStacks;
for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = homeDisplayStacks.get(stackNdx);
+ stack = homeDisplayStacks.get(stackNdx);
if (!stack.isHomeStack()) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
- "adjustStackFocus: Setting focused stack=" + stack);
- mFocusedStack = stack;
- return mFocusedStack;
+ "computeStackFocus: Setting focused stack=" + stack);
+ return stack;
}
}
// Need to create an app stack for this user.
- mFocusedStack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
- if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r +
- " stackId=" + mFocusedStack.mStackId);
- return mFocusedStack;
+ stack = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
+ if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "computeStackFocus: New stack r=" + r +
+ " stackId=" + stack.mStackId);
+ return stack;
}
return mHomeStack;
}
- void setFocusedStack(ActivityRecord r, String reason) {
- if (r != null) {
- final TaskRecord task = r.task;
- boolean isHomeActivity = !r.isApplicationActivity();
- if (!isHomeActivity && task != null) {
- isHomeActivity = !task.isApplicationTask();
- }
- if (!isHomeActivity && task != null) {
- final ActivityRecord parent = task.stack.mActivityContainer.mParentActivity;
- isHomeActivity = parent != null && parent.isHomeActivity();
- }
- moveHomeStack(isHomeActivity, reason);
+ boolean setFocusedStack(ActivityRecord r, String reason) {
+ if (r == null) {
+ // Not sure what you are trying to do, but it is not going to work...
+ return false;
}
+ final TaskRecord task = r.task;
+ if (task == null || task.stack == null) {
+ Slog.w(TAG, "Can't set focus stack for r=" + r + " task=" + task);
+ return false;
+ }
+ task.stack.moveToFront(reason);
+ return true;
}
final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,
@@ -1706,7 +1706,7 @@
if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
ActivityRecord checkedCaller = sourceRecord;
if (checkedCaller == null) {
- checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop);
+ checkedCaller = mFocusedStack.topRunningNonDelayedActivityLocked(notTop);
}
if (!checkedCaller.realActivity.equals(r.realActivity)) {
// Caller is not the same as launcher, so always needed.
@@ -2030,7 +2030,7 @@
// If the activity being launched is the same as the one currently
// at the top, then we need to check if it should only be launched
// once.
- ActivityStack topStack = getFocusedStack();
+ ActivityStack topStack = mFocusedStack;
ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
if (top != null && r.resultTo == null) {
if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
@@ -2082,10 +2082,9 @@
return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
newTask = true;
- targetStack = adjustStackFocus(r, newTask);
- if (!launchTaskBehind) {
- targetStack.moveToFront("startingNewTask");
- }
+ targetStack = computeStackFocus(r, newTask);
+ targetStack.moveToFront("startingNewTask");
+
if (reuseTask == null) {
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
@@ -2206,7 +2205,7 @@
// This not being started from an existing activity, and not part
// of a new task... just put it in the top task, though these days
// this case should never happen.
- targetStack = adjustStackFocus(r, newTask);
+ targetStack = computeStackFocus(r, newTask);
targetStack.moveToFront("addingToTopTask");
ActivityRecord prev = targetStack.topActivity();
r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),
@@ -2482,13 +2481,14 @@
boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target,
Bundle targetOptions) {
if (targetStack == null) {
- targetStack = getFocusedStack();
+ targetStack = mFocusedStack;
}
// Do targetStack first.
boolean result = false;
if (isFrontStack(targetStack)) {
result = targetStack.resumeTopActivityLocked(target, targetOptions);
}
+
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
@@ -2640,6 +2640,48 @@
}
}
+ /** Makes sure the input task is in a stack with the specified bounds by either resizing the
+ * current task stack if it only has one entry, moving the task to a stack that matches the
+ * bounds, or creating a new stack with the required bounds. Also, makes the task resizeable.*/
+ void resizeTaskLocked(TaskRecord task, Rect bounds) {
+ task.mResizeable = true;
+ final ActivityStack currentStack = task.stack;
+ if (currentStack.isHomeStack()) {
+ // Can't move task off the home stack. Sorry!
+ return;
+ }
+
+ final int matchingStackId = mWindowManager.getStackIdWithBounds(bounds);
+ if (matchingStackId != -1) {
+ // There is already a stack with the right bounds!
+ if (currentStack != null && currentStack.mStackId == matchingStackId) {
+ // Nothing to do here. Already in the right stack...
+ return;
+ }
+ // Move task to stack with matching bounds.
+ moveTaskToStackLocked(task.taskId, matchingStackId, true);
+ return;
+ }
+
+ if (currentStack != null && currentStack.numTasks() == 1) {
+ // Just resize the current stack since this is the task in it.
+ resizeStackLocked(currentStack.mStackId, bounds);
+ return;
+ }
+
+ // Create new stack and move the task to it.
+ final int displayId = (currentStack != null && currentStack.mDisplayId != -1)
+ ? currentStack.mDisplayId : Display.DEFAULT_DISPLAY;
+ ActivityStack newStack = createStackOnDisplay(getNextStackId(), displayId);
+
+ if (newStack == null) {
+ Slog.e(TAG, "resizeTaskLocked: Can't create stack for task=" + task);
+ return;
+ }
+ moveTaskToStackLocked(task.taskId, newStack.mStackId, true);
+ resizeStackLocked(newStack.mStackId, bounds);
+ }
+
ActivityStack createStackOnDisplay(int stackId, int displayId) {
ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
if (activityDisplay == null) {
@@ -2718,6 +2760,7 @@
void moveTaskToStackLocked(int taskId, int stackId, boolean toTop) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
+ Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
return;
}
final ActivityStack stack = getStack(stackId);
@@ -2725,9 +2768,11 @@
Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId);
return;
}
- task.stack.removeTask(task, "moveTaskToStack");
+ mWindowManager.moveTaskToStack(taskId, stackId, toTop);
+ if (task.stack != null) {
+ task.stack.removeTask(task, "moveTaskToStack", false);
+ }
stack.addTask(task, toTop, true);
- mWindowManager.addTask(taskId, stackId, toTop);
resumeTopActivitiesLocked();
}
@@ -3068,7 +3113,7 @@
}
boolean switchUserLocked(int userId, UserStartedState uss) {
- mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId());
+ mUserStackInFront.put(mCurrentUser, mFocusedStack.getStackId());
final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
mCurrentUser = userId;
@@ -3192,7 +3237,7 @@
}
ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
- return getFocusedStack().getDumpActivitiesLocked(name);
+ return mFocusedStack.getDumpActivitiesLocked(name);
}
static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 7ab3794..6a29d85 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -187,10 +187,12 @@
public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
mParallelBroadcasts.add(r);
+ r.enqueueClockTime = System.currentTimeMillis();
}
public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
mOrderedBroadcasts.add(r);
+ r.enqueueClockTime = System.currentTimeMillis();
}
public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index b2cfd7a..9a4d7a0 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -52,6 +52,7 @@
final int appOp; // an app op that is associated with this broadcast
final List receivers; // contains BroadcastFilter and ResolveInfo
IIntentReceiver resultTo; // who receives final result if non-null
+ long enqueueClockTime; // the clock time the broadcast was enqueued
long dispatchTime; // when dispatch started on this set of receivers
long dispatchClockTime; // the clock time the dispatch started
long receiverTime; // when current receiver started for timeouts.
@@ -102,7 +103,9 @@
pw.print(prefix); pw.print("requiredPermission="); pw.print(requiredPermission);
pw.print(" appOp="); pw.println(appOp);
}
- pw.print(prefix); pw.print("dispatchClockTime=");
+ pw.print(prefix); pw.print("enqueueClockTime=");
+ pw.print(new Date(enqueueClockTime));
+ pw.print(" dispatchClockTime=");
pw.println(new Date(dispatchClockTime));
pw.print(prefix); pw.print("dispatchTime=");
TimeUtils.formatDuration(dispatchTime, now, pw);
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9786b42..66c2f5f 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -128,6 +128,7 @@
private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4;
private static final int WAKE_LOCK_STAY_AWAKE = 1 << 5; // only set if already awake
private static final int WAKE_LOCK_DOZE = 1 << 6;
+ private static final int WAKE_LOCK_DRAW = 1 << 7;
// Summarizes the user activity state.
private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0;
@@ -1398,12 +1399,15 @@
case PowerManager.DOZE_WAKE_LOCK:
mWakeLockSummary |= WAKE_LOCK_DOZE;
break;
+ case PowerManager.DRAW_WAKE_LOCK:
+ mWakeLockSummary |= WAKE_LOCK_DRAW;
+ break;
}
}
// Cancel wake locks that make no sense based on the current state.
if (mWakefulness != WAKEFULNESS_DOZING) {
- mWakeLockSummary &= ~WAKE_LOCK_DOZE;
+ mWakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
}
if (mWakefulness == WAKEFULNESS_ASLEEP
|| (mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
@@ -1422,6 +1426,9 @@
mWakeLockSummary |= WAKE_LOCK_CPU;
}
}
+ if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+ mWakeLockSummary |= WAKE_LOCK_CPU;
+ }
if (DEBUG_SPEW) {
Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness="
@@ -1845,6 +1852,10 @@
if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
+ if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+ && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+ mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ }
mDisplayPowerRequest.dozeScreenBrightness =
mDozeScreenBrightnessOverrideFromDreamManager;
} else {
@@ -2712,6 +2723,8 @@
return "PROXIMITY_SCREEN_OFF_WAKE_LOCK";
case PowerManager.DOZE_WAKE_LOCK:
return "DOZE_WAKE_LOCK ";
+ case PowerManager.DRAW_WAKE_LOCK:
+ return "DRAW_WAKE_LOCK ";
default:
return "??? ";
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index d68c056..487483e9 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -461,6 +461,16 @@
return mService.getWindowId(window);
}
+ @Override
+ public void pokeDrawLock(IBinder window) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mService.pokeDrawLock(this, window);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
void windowAddedLocked() {
if (mSurfaceSession == null) {
if (WindowManagerService.localLOGV) Slog.v(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a9b26e2..b8f26c9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -72,6 +72,19 @@
mService.mTaskIdToTask.delete(mTaskId);
}
+ void moveTaskToStack(TaskStack stack, boolean toTop) {
+ if (stack == mStack) {
+ return;
+ }
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: removing taskId=" + mTaskId
+ + " from stack=" + mStack);
+ EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, mTaskId, "removeTask");
+ if (mStack != null) {
+ mStack.removeTask(this);
+ }
+ stack.addTask(this, toTop);
+ }
+
boolean removeAppToken(AppWindowToken wtoken) {
boolean removed = mAppTokens.remove(wtoken);
if (mAppTokens.size() == 0) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index de8a2fc..fde0bd5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
import static android.view.WindowManager.LayoutParams.*;
import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -189,6 +190,7 @@
static final boolean DEBUG_TASK_MOVEMENT = false;
static final boolean DEBUG_STACK = false;
static final boolean DEBUG_DISPLAY = false;
+ static final boolean DEBUG_POWER = false;
static final boolean SHOW_SURFACE_ALLOC = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
@@ -327,6 +329,7 @@
final boolean mHaveInputMethods;
final boolean mHasPermanentDpad;
+ final long mDrawLockTimeoutMillis;
final boolean mAllowBootMessages;
@@ -680,11 +683,11 @@
final WindowAnimator mAnimator;
- SparseArray<Task> mTaskIdToTask = new SparseArray<Task>();
+ SparseArray<Task> mTaskIdToTask = new SparseArray<>();
/** All of the TaskStacks in the window manager, unordered. For an ordered list call
* DisplayContent.getStacks(). */
- SparseArray<TaskStack> mStackIdToStack = new SparseArray<TaskStack>();
+ SparseArray<TaskStack> mStackIdToStack = new SparseArray<>();
private final PointerEventDispatcher mPointerEventDispatcher;
@@ -846,6 +849,8 @@
com.android.internal.R.bool.config_hasPermanentDpad);
mInTouchMode = context.getResources().getBoolean(
com.android.internal.R.bool.config_defaultInTouchMode);
+ mDrawLockTimeoutMillis = context.getResources().getInteger(
+ com.android.internal.R.integer.config_drawLockTimeoutMillis);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplaySettings = new DisplaySettings();
@@ -2960,6 +2965,15 @@
}
}
+ public void pokeDrawLock(Session session, IBinder token) {
+ synchronized (mWindowMap) {
+ WindowState window = windowForClientLocked(session, token, false);
+ if (window != null) {
+ window.pokeDrawLockLw(mDrawLockTimeoutMillis);
+ }
+ }
+ }
+
public int relayoutWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
@@ -5075,6 +5089,7 @@
+ " to " + (toTop ? "top" : "bottom"));
Task task = mTaskIdToTask.get(taskId);
if (task == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "addTask: could not find taskId=" + taskId);
return;
}
TaskStack stack = mStackIdToStack.get(stackId);
@@ -5085,6 +5100,27 @@
}
}
+ public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+ synchronized (mWindowMap) {
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: moving taskId=" + taskId
+ + " to stackId=" + stackId + " at " + (toTop ? "top" : "bottom"));
+ Task task = mTaskIdToTask.get(taskId);
+ if (task == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find taskId=" + taskId);
+ return;
+ }
+ TaskStack stack = mStackIdToStack.get(stackId);
+ if (stack == null) {
+ if (DEBUG_STACK) Slog.i(TAG, "moveTaskToStack: could not find stackId=" + stackId);
+ return;
+ }
+ task.moveTaskToStack(stack, toTop);
+ final DisplayContent displayContent = stack.getDisplayContent();
+ displayContent.layoutNeeded = true;
+ performLayoutAndPlaceSurfacesLocked();
+ }
+ }
+
/**
* Re-sizes the specified stack and its containing windows.
* Returns a {@link Configuration} object that contains configurations settings
@@ -5115,6 +5151,24 @@
bounds.setEmpty();
}
+ /** Returns the id of an application (non-home stack) stack that match the input bounds.
+ * -1 if no stack matches.*/
+ public int getStackIdWithBounds(Rect bounds) {
+ Rect stackBounds = new Rect();
+ synchronized (mWindowMap) {
+ for (int i = mStackIdToStack.size() - 1; i >= 0; --i) {
+ TaskStack stack = mStackIdToStack.valueAt(i);
+ if (stack.mStackId != HOME_STACK_ID) {
+ stack.getBounds(stackBounds);
+ if (stackBounds.equals(bounds)) {
+ return stack.mStackId;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+
/** Forces the stack to fullscreen if input is true, else un-forces the stack from fullscreen.
* Returns a {@link Configuration} object that contains configurations settings
* that should be overridden due to the operation.
@@ -9957,7 +10011,9 @@
if (mAllowTheaterModeWakeFromLayout
|| Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.THEATER_MODE_ON, 0) == 0) {
- if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!");
+ if (DEBUG_VISIBILITY || DEBUG_POWER) {
+ Slog.v(TAG, "Turning screen on after layout!");
+ }
mPowerManager.wakeUp(SystemClock.uptimeMillis());
}
mTurnOnScreen = false;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d58b2b0..04aea84 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -19,9 +19,9 @@
import static com.android.server.wm.WindowManagerService.DEBUG_CONFIGURATION;
import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_POWER;
import static com.android.server.wm.WindowManagerService.DEBUG_RESIZE;
import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
-
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -34,12 +34,15 @@
import android.app.AppOpsManager;
import android.os.Debug;
+import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.SystemClock;
+import android.os.WorkSource;
import android.util.TimeUtils;
import android.view.Display;
import android.view.IWindowFocusObserver;
import android.view.IWindowId;
+
import com.android.server.input.InputWindowHandle;
import android.content.Context;
@@ -343,6 +346,15 @@
/** When true this window can be displayed on screens owther than mOwnerUid's */
private boolean mShowToOwnerOnly;
+ /**
+ * Wake lock for drawing.
+ * Even though it's slightly more expensive to do so, we will use a separate wake lock
+ * for each app that is requesting to draw while dozing so that we can accurately track
+ * who is preventing the system from suspending.
+ * This lock is only acquired on first use.
+ */
+ PowerManager.WakeLock mDrawLock;
+
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, final DisplayContent displayContent) {
@@ -1269,6 +1281,33 @@
}
}
+ public void pokeDrawLockLw(long timeout) {
+ if (isVisibleOrAdding()) {
+ if (mDrawLock == null) {
+ // We want the tag name to be somewhat stable so that it is easier to correlate
+ // in wake lock statistics. So in particular, we don't want to include the
+ // window's hash code as in toString().
+ CharSequence tag = mAttrs.getTitle();
+ if (tag == null) {
+ tag = mAttrs.packageName;
+ }
+ mDrawLock = mService.mPowerManager.newWakeLock(
+ PowerManager.DRAW_WAKE_LOCK, "Window:" + tag);
+ mDrawLock.setReferenceCounted(false);
+ mDrawLock.setWorkSource(new WorkSource(mOwnerUid, mAttrs.packageName));
+ }
+ // Each call to acquire resets the timeout.
+ if (DEBUG_POWER) {
+ Slog.d(TAG, "pokeDrawLock: poking draw lock on behalf of visible window owned by "
+ + mAttrs.packageName);
+ }
+ mDrawLock.acquire(timeout);
+ } else if (DEBUG_POWER) {
+ Slog.d(TAG, "pokeDrawLock: suppressed draw lock request for invisible window "
+ + "owned by " + mAttrs.packageName);
+ }
+ }
+
@Override
public boolean isAlive() {
return mClient.asBinder().isBinderAlive();
@@ -1642,6 +1681,9 @@
pw.print(" mWallpaperDisplayOffsetY=");
pw.println(mWallpaperDisplayOffsetY);
}
+ if (mDrawLock != null) {
+ pw.println("mDrawLock=" + mDrawLock);
+ }
}
String makeInputChannelName() {
diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
index 36102f1..7e4ff69 100644
--- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
+++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java
@@ -179,7 +179,7 @@
XmlPullParser parser = ParserFactory.create(f);
BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser(
- parser, bridgeContext, false);
+ parser, bridgeContext, value.isFramework());
return inflate(bridgeParser, root);
} catch (Exception e) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 62a03e1..4289689 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -206,4 +206,9 @@
// pass for now.
return null;
}
+
+ @Override
+ public void pokeDrawLock(IBinder window) {
+ // pass for now.
+ }
}