summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorJohn Spurlock <jspurlock@google.com>2014-07-07 08:37:56 -0400
committerJohn Spurlock <jspurlock@google.com>2014-07-12 18:41:57 -0400
commit486b78e42652466f6241eb87d5bed60040db7a25 (patch)
tree6d11449bd70d79355d489558f8145e3655e0e672 /packages
parent030f0b2340877b0a466ae112466a88456fcb5a0e (diff)
downloadframeworks_base-486b78e42652466f6241eb87d5bed60040db7a25.zip
frameworks_base-486b78e42652466f6241eb87d5bed60040db7a25.tar.gz
frameworks_base-486b78e42652466f6241eb87d5bed60040db7a25.tar.bz2
QS: Introduce bluetooth control panel.
- Factor out common detail item panel view, share with Wifi. - Add an empty state (large icon + text) - Implement connect / disconnect for supported BT profiles. - Wire up "scanning" state, but still waiting on asset. - Add BT controller info to dump. Bug:16235253 Change-Id: Icf854cafba962fe4b63767d7206e309d80b7b87b
Diffstat (limited to 'packages')
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml28
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_cancel.xml28
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml28
-rw-r--r--packages/SystemUI/res/layout/qs_detail_item.xml14
-rw-r--r--packages/SystemUI/res/layout/qs_detail_items.xml49
-rw-r--r--packages/SystemUI/res/values/colors.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml6
-rw-r--r--packages/SystemUI/res/values/styles.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java209
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java275
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java226
18 files changed, 1067 insertions, 128 deletions
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
new file mode 100644
index 0000000..aa0b369
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="@color/qs_detail_empty"
+ android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cancel.xml b/packages/SystemUI/res/drawable/ic_qs_cancel.xml
new file mode 100644
index 0000000..de72f13
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_cancel.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="24dp"
+ android:height="24dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M24.0,4.0C12.9,4.0 4.0,12.9 4.0,24.0s8.9,20.0 20.0,20.0c11.1,0.0 20.0,-8.9 20.0,-20.0S35.1,4.0 24.0,4.0zM34.0,31.2L31.2,34.0L24.0,26.8L16.8,34.0L14.0,31.2l7.2,-7.2L14.0,16.8l2.8,-2.8l7.2,7.2l7.2,-7.2l2.8,2.8L26.8,24.0L34.0,31.2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
new file mode 100644
index 0000000..16fa30b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:pathData="M24.0,4.0C15.0,4.0 6.7,7.0 0.0,12.0l24.0,32.0l24.0,-32.0C41.3,7.0 33.0,4.0 24.0,4.0z"
+ android:fill="@color/qs_detail_empty" />
+</vector>
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
index c5eaed9..55139fb 100644
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -17,9 +17,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_detail_item_height"
- android:gravity="center_vertical"
android:background="@drawable/btn_borderless_rect"
android:clickable="true"
+ android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
@@ -29,9 +29,10 @@
android:layout_marginEnd="12dp" />
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
+ android:layout_weight="1"
android:orientation="vertical" >
<TextView
@@ -48,4 +49,13 @@
android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
</LinearLayout>
+ <ImageView
+ android:id="@android:id/icon2"
+ style="@style/QSBorderlessButton"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:clickable="true"
+ android:scaleType="center"
+ android:src="@drawable/ic_qs_cancel" />
+
</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
new file mode 100644
index 0000000..b64005f
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- extends FrameLayout -->
+<com.android.systemui.qs.QSDetailItems xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+ <LinearLayout
+ android:id="@android:id/empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal"
+ android:orientation="vertical" >
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="56dp"
+ android:layout_height="56dp" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:textAppearance="@style/TextAppearance.QS.DetailEmpty" />
+ </LinearLayout>
+
+</com.android.systemui.qs.QSDetailItems> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4cc0bb5..bae7ed7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -41,6 +41,7 @@
<color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
<color name="qs_tile_text">#B3FFFFFF</color><!-- 70% white -->
<color name="qs_subhead">#66FFFFFF</color><!-- 40% white -->
+ <color name="qs_detail_empty">#24B0BEC5</color><!-- 14% blue grey 200-->
<color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
<color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
<color name="status_bar_clock_color">#33FFFFFF</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 48670fb..b0f2133 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -486,6 +486,8 @@
<string name="quick_settings_bluetooth_multiple_devices_label">Bluetooth (<xliff:g id="number">%d</xliff:g> Devices)</string>
<!-- QuickSettings: Bluetooth (Off) [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_off_label">Bluetooth Off</string>
+ <!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string>
<!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
<string name="quick_settings_brightness_label">Brightness</string>
<!-- QuickSettings: Rotation Unlocked [CHAR LIMIT=NONE] -->
@@ -522,6 +524,8 @@
<string name="quick_settings_wifi_no_network">No Network</string>
<!-- QuickSettings: Wifi (Off) [CHAR LIMIT=NONE] -->
<string name="quick_settings_wifi_off_label">Wi-Fi Off</string>
+ <!-- QuickSettings: Wifi detail panel, text when there are no items [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_wifi_detail_empty_text">No saved networks available</string>
<!-- QuickSettings: Remote display [CHAR LIMIT=NONE] -->
<string name="quick_settings_remote_display_no_connection_label">Cast screen</string>
<!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
@@ -538,6 +542,8 @@
<string name="quick_settings_done">Done</string>
<!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] -->
<string name="quick_settings_connected">Connected</string>
+ <!-- QuickSettings: Control panel: Label for connecting device. [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_connecting">Connecting...</string>
<!-- QuickSettings: Tethering. [CHAR LIMIT=NONE] -->
<string name="quick_settings_tethering_label">Tethering</string>
<!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f99b68b..7da6c22 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -179,6 +179,11 @@
<item name="android:gravity">center</item>
</style>
+ <style name="TextAppearance.QS.DetailEmpty">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/qs_subhead</item>
+ </style>
+
<style name="TextAppearance.QS.Subhead">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
new file mode 100644
index 0000000..24c1378
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * Quick settings common detail view with line items.
+ */
+public class QSDetailItems extends FrameLayout {
+ private static final String TAG = "QSDetailItems";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final H mHandler = new H();
+
+ private String mTag;
+ private Callback mCallback;
+ private boolean mItemsVisible = true;
+ private LinearLayout mItems;
+ private View mEmpty;
+ private TextView mEmptyText;
+ private ImageView mEmptyIcon;
+
+ public QSDetailItems(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mTag = TAG;
+ }
+
+ public static QSDetailItems convertOrInflate(Context context, View convert, ViewGroup parent) {
+ if (convert instanceof QSDetailItems) {
+ return (QSDetailItems) convert;
+ }
+ return (QSDetailItems) LayoutInflater.from(context).inflate(R.layout.qs_detail_items,
+ parent, false);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mItems = (LinearLayout) findViewById(android.R.id.list);
+ mItems.setVisibility(GONE);
+ mEmpty = findViewById(android.R.id.empty);
+ mEmpty.setVisibility(GONE);
+ mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
+ mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
+ }
+
+ public void setTagSuffix(String suffix) {
+ mTag = TAG + "." + suffix;
+ }
+
+ public void setEmptyState(int icon, int text) {
+ mEmptyIcon.setImageResource(icon);
+ mEmptyText.setText(text);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (DEBUG) Log.d(mTag, "onAttachedToWindow");
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+ mCallback = null;
+ }
+
+ public void setCallback(Callback callback) {
+ mHandler.removeMessages(H.SET_CALLBACK);
+ mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
+ }
+
+ public void setItems(Item[] items) {
+ mHandler.removeMessages(H.SET_ITEMS);
+ mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget();
+ }
+
+ public void setItemsVisible(boolean visible) {
+ mHandler.removeMessages(H.SET_ITEMS_VISIBLE);
+ mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ }
+
+ private void handleSetCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ private void handleSetItems(Item[] items) {
+ final int itemCount = items != null ? items.length : 0;
+ mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
+ mItems.setVisibility(itemCount == 0 ? GONE : VISIBLE);
+ for (int i = mItems.getChildCount() - 1; i >= itemCount; i--) {
+ mItems.removeViewAt(i);
+ }
+ for (int i = 0; i < itemCount; i++) {
+ bind(items[i], mItems.getChildAt(i));
+ }
+ }
+
+ private void handleSetItemsVisible(boolean visible) {
+ if (mItemsVisible == visible) return;
+ mItemsVisible = visible;
+ for (int i = 0; i < mItems.getChildCount(); i++) {
+ mItems.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ }
+ }
+
+ private void bind(final Item item, View view) {
+ if (view == null) {
+ view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, this, false);
+ mItems.addView(view);
+ }
+ view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
+ iv.setImageResource(item.icon);
+ final TextView title = (TextView) view.findViewById(android.R.id.title);
+ title.setText(item.line1);
+ final TextView summary = (TextView) view.findViewById(android.R.id.summary);
+ final boolean twoLines = !TextUtils.isEmpty(item.line2);
+ summary.setVisibility(twoLines ? VISIBLE : GONE);
+ summary.setText(twoLines ? item.line2 : null);
+ view.setMinimumHeight(mContext.getResources() .getDimensionPixelSize(
+ twoLines ? R.dimen.qs_detail_item_height_twoline : R.dimen.qs_detail_item_height));
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDetailItemClick(item);
+ }
+ }
+ });
+ final ImageView disconnect = (ImageView) view.findViewById(android.R.id.icon2);
+ disconnect.setVisibility(item.canDisconnect ? VISIBLE : GONE);
+ disconnect.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDetailItemDisconnect(item);
+ }
+ }
+ });
+ }
+
+ private class H extends Handler {
+ private static final int SET_ITEMS = 1;
+ private static final int SET_CALLBACK = 2;
+ private static final int SET_ITEMS_VISIBLE = 3;
+
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == SET_ITEMS) {
+ handleSetItems((Item[]) msg.obj);
+ } else if (msg.what == SET_CALLBACK) {
+ handleSetCallback((QSDetailItems.Callback) msg.obj);
+ } else if (msg.what == SET_ITEMS_VISIBLE) {
+ handleSetItemsVisible(msg.arg1 != 0);
+ }
+ }
+ }
+
+ public static class Item {
+ public int icon;
+ public String line1;
+ public String line2;
+ public Object tag;
+ public boolean canDisconnect;
+ }
+
+ public interface Callback {
+ void onDetailItemClick(Item item);
+ void onDetailItemDisconnect(Item item);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 36cd388..449cc1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -168,6 +168,12 @@ public class QSPanel extends ViewGroup {
fireToggleStateChanged(state);
}
}
+ @Override
+ public void onScanStateChanged(boolean state) {
+ if (mDetailRecord == r) {
+ fireScanStateChanged(state);
+ }
+ }
});
final View.OnClickListener click = new View.OnClickListener() {
@Override
@@ -195,6 +201,7 @@ public class QSPanel extends ViewGroup {
if (mDetailRecord != null) return; // already showing something in detail
r.detailAdapter = r.tile.getDetailAdapter();
if (r.detailAdapter == null) return;
+ mDetailRecord = r;
r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
if (r.detailView == null) throw new IllegalStateException("Must return detail view");
mDetailDoneButton.setOnClickListener(new OnClickListener() {
@@ -211,7 +218,6 @@ public class QSPanel extends ViewGroup {
mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
}
});
- mDetailRecord = r;
mDetailContent.removeAllViews();
mDetail.bringToFront();
mDetailContent.addView(r.detailView);
@@ -312,6 +318,12 @@ public class QSPanel extends ViewGroup {
}
}
+ private void fireScanStateChanged(boolean state) {
+ if (mCallback != null) {
+ mCallback.onScanStateChanged(state);
+ }
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
@@ -344,5 +356,6 @@ public class QSPanel extends ViewGroup {
public interface Callback {
void onShowingDetail(QSTile.DetailAdapter detail);
void onToggleStateChanged(boolean state);
+ void onScanStateChanged(boolean state);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 62c9d9f..fc08cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -50,7 +50,7 @@ import java.util.Objects;
*/
public abstract class QSTile<TState extends State> implements Listenable {
protected final String TAG = "QSTile." + getClass().getSimpleName();
- protected static final boolean DEBUG = false;
+ protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
protected final Host mHost;
protected final Context mContext;
@@ -129,6 +129,10 @@ public abstract class QSTile<TState extends State> implements Listenable {
mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
}
+ public void fireScanStateChanged(boolean state) {
+ mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
+ }
+
// call only on tile worker looper
private void handleSetCallback(Callback callback) {
@@ -166,6 +170,12 @@ public abstract class QSTile<TState extends State> implements Listenable {
}
}
+ private void handleScanStateChanged(boolean state) {
+ if (mCallback != null) {
+ mCallback.onScanStateChanged(state);
+ }
+ }
+
protected void handleUserSwitch(int newUserId) {
handleRefreshState(null);
}
@@ -178,6 +188,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
private static final int SHOW_DETAIL = 5;
private static final int USER_SWITCH = 6;
private static final int TOGGLE_STATE_CHANGED = 7;
+ private static final int SCAN_STATE_CHANGED = 8;
private H(Looper looper) {
super(looper);
@@ -208,6 +219,9 @@ public abstract class QSTile<TState extends State> implements Listenable {
} else if (msg.what == TOGGLE_STATE_CHANGED) {
name = "handleToggleStateChanged";
handleToggleStateChanged(msg.arg1 != 0);
+ } else if (msg.what == SCAN_STATE_CHANGED) {
+ name = "handleScanStateChanged";
+ handleScanStateChanged(msg.arg1 != 0);
}
} catch (Throwable t) {
final String error = "Error in " + name;
@@ -221,6 +235,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
void onStateChanged(State state);
void onShowDetail(boolean show);
void onToggleStateChanged(boolean state);
+ void onScanStateChanged(boolean state);
}
public interface Host {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 7431e69..1250d21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,24 +16,33 @@
package com.android.systemui.qs.tiles;
-import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
+import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice;
+
+import java.util.Set;
/** Quick settings tile: Bluetooth **/
public class BluetoothTile extends QSTile<QSTile.BooleanState> {
private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
private final BluetoothController mController;
+ private final BluetoothDetailAdapter mDetailAdapter;
public BluetoothTile(Host host) {
super(host);
mController = host.getBluetoothController();
+ mDetailAdapter = new BluetoothDetailAdapter();
}
@Override
@@ -42,6 +51,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
protected BooleanState newTileState() {
return new BooleanState();
}
@@ -63,7 +77,10 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleSecondaryClick() {
- mHost.startSettingsActivity(BLUETOOTH_SETTINGS);
+ if (!mState.value) {
+ mController.setBluetoothEnabled(true);
+ }
+ showDetail(true);
}
@Override
@@ -83,7 +100,8 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
state.label = mController.getLastDeviceName();
} else if (connecting) {
state.iconId = R.drawable.ic_qs_bluetooth_connecting;
- stateContentDescription = mContext.getString(R.string.accessibility_desc_connecting);
+ stateContentDescription =
+ mContext.getString(R.string.accessibility_desc_connecting);
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
} else {
state.iconId = R.drawable.ic_qs_bluetooth_on;
@@ -106,5 +124,100 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
public void onBluetoothStateChange(boolean enabled, boolean connecting) {
refreshState();
}
+ @Override
+ public void onBluetoothPairedDevicesChanged() {
+ mUiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mDetailAdapter.updateItems();
+ }
+ });
+ }
};
+
+ private final class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+ private QSDetailItems mItems;
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_bluetooth_label;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return mState.value;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return BLUETOOTH_SETTINGS;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ mController.setBluetoothEnabled(state);
+ showDetail(false);
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
+ mItems.setTagSuffix("Bluetooth");
+ mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
+ R.string.quick_settings_bluetooth_detail_empty_text);
+ mItems.setCallback(this);
+ updateItems();
+ setItemsVisible(mState.value);
+ return mItems;
+ }
+
+ public void setItemsVisible(boolean visible) {
+ if (mItems == null) return;
+ mItems.setItemsVisible(visible);
+ }
+
+ private void updateItems() {
+ if (mItems == null) return;
+ Item[] items = null;
+ final Set<PairedDevice> devices = mController.getPairedDevices();
+ if (devices != null) {
+ items = new Item[devices.size()];
+ int i = 0;
+ for (PairedDevice device : devices) {
+ final Item item = new Item();
+ item.icon = R.drawable.ic_qs_bluetooth_on;
+ item.line1 = device.name;
+ if (device.state == PairedDevice.STATE_CONNECTED) {
+ item.icon = R.drawable.ic_qs_bluetooth_connected;
+ item.line2 = mContext.getString(R.string.quick_settings_connected);
+ item.canDisconnect = true;
+ } else if (device.state == PairedDevice.STATE_CONNECTING) {
+ item.icon = R.drawable.ic_qs_bluetooth_connecting;
+ item.line2 = mContext.getString(R.string.quick_settings_connecting);
+ }
+ item.tag = device;
+ items[i++] = item;
+ }
+ }
+ mItems.setItems(items);
+ }
+
+ @Override
+ public void onDetailItemClick(Item item) {
+ if (item == null || item.tag == null) return;
+ final PairedDevice device = (PairedDevice) item.tag;
+ if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) {
+ mController.connect(device);
+ }
+ }
+
+ @Override
+ public void onDetailItemDisconnect(Item item) {
+ if (item == null || item.tag == null) return;
+ final PairedDevice device = (PairedDevice) item.tag;
+ if (device != null) {
+ mController.disconnect(device);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 2876607..900c7b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -21,15 +21,12 @@ import android.content.Intent;
import android.content.res.Resources;
import android.provider.Settings;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.SignalTileView;
@@ -40,7 +37,6 @@ import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChan
/** Quick settings tile: Wifi **/
public class WifiTile extends QSTile<QSTile.SignalState> {
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
- private static final int MAX_ITEMS = 4; // TODO temporary visual restriction
private final NetworkController mController;
private final WifiDetailAdapter mDetailAdapter;
@@ -66,7 +62,6 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
if (listening) {
mController.addNetworkSignalChangedCallback(mCallback);
mController.addAccessPointCallback(mDetailAdapter);
- mController.scanForAccessPoints();
} else {
mController.removeNetworkSignalChangedCallback(mCallback);
mController.removeAccessPointCallback(mDetailAdapter);
@@ -108,7 +103,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null);
boolean enabledChanging = state.enabled != cb.enabled;
if (enabledChanging) {
- mDetailAdapter.postUpdateItems();
+ mDetailAdapter.setItemsVisible(cb.enabled);
fireToggleStateChanged(cb.enabled);
}
state.enabled = cb.enabled;
@@ -204,9 +199,9 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
};
private final class WifiDetailAdapter implements DetailAdapter,
- NetworkController.AccessPointCallback {
+ NetworkController.AccessPointCallback, QSDetailItems.Callback {
- private LinearLayout mItems;
+ private QSDetailItems mItems;
private AccessPoint[] mAccessPoints;
@Override
@@ -232,66 +227,67 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
@Override
public View createDetailView(Context context, View convertView, ViewGroup parent) {
- if (convertView != null) return convertView;
- mItems = new LinearLayout(context);
- mItems.setOrientation(LinearLayout.VERTICAL);
+ if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null));
+ mAccessPoints = null;
+ mController.scanForAccessPoints();
+ fireScanStateChanged(true);
+ mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
+ mItems.setTagSuffix("Wifi");
+ mItems.setCallback(this);
+ mItems.setEmptyState(R.drawable.ic_qs_wifi_detail_empty,
+ R.string.quick_settings_wifi_detail_empty_text);
updateItems();
+ setItemsVisible(mState.enabled);
return mItems;
}
@Override
public void onAccessPointsChanged(final AccessPoint[] accessPoints) {
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- mAccessPoints = accessPoints;
- updateItems();
- }
- });
+ mAccessPoints = accessPoints;
+ updateItems();
+ if (accessPoints != null && accessPoints.length > 0) {
+ fireScanStateChanged(false);
+ }
}
- public void postUpdateItems() {
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- updateItems();
- }
- });
+ @Override
+ public void onDetailItemClick(Item item) {
+ if (item == null || item.tag == null) return;
+ final AccessPoint ap = (AccessPoint) item.tag;
+ if (!ap.isConnected) {
+ mController.connect(ap);
+ }
+ showDetail(false);
+ }
+
+ @Override
+ public void onDetailItemDisconnect(Item item) {
+ // noop
+ }
+
+ public void setItemsVisible(boolean visible) {
+ if (mItems == null) return;
+ mItems.setItemsVisible(visible);
}
private void updateItems() {
if (mItems == null) return;
- mItems.removeAllViews();
- if (mAccessPoints == null || mAccessPoints.length == 0 || !mState.enabled) return;
- for (int i = 0; i < mAccessPoints.length; i++) {
- final AccessPoint ap = mAccessPoints[i];
- if (ap == null) continue;
- final View item = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item,
- mItems, false);
- final ImageView iv = (ImageView) item.findViewById(android.R.id.icon);
- iv.setImageResource(ap.iconId);
- final TextView title = (TextView) item.findViewById(android.R.id.title);
- title.setText(ap.ssid);
- final TextView summary = (TextView) item.findViewById(android.R.id.summary);
- if (ap.isConnected) {
- item.setMinimumHeight(mContext.getResources()
- .getDimensionPixelSize(R.dimen.qs_detail_item_height_twoline));
- summary.setText(R.string.quick_settings_connected);
- } else {
- summary.setVisibility(View.GONE);
- }
- item.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (!ap.isConnected) {
- mController.connect(ap);
- }
- showDetail(false);
+ Item[] items = null;
+ if (mAccessPoints != null) {
+ items = new Item[mAccessPoints.length];
+ for (int i = 0; i < mAccessPoints.length; i++) {
+ final AccessPoint ap = mAccessPoints[i];
+ final Item item = new Item();
+ item.tag = ap;
+ item.icon = ap.iconId;
+ item.line1 = ap.ssid;
+ if (ap.isConnected) {
+ item.line2 = mContext.getString(R.string.quick_settings_connected);
}
- });
- mItems.addView(item);
- if (mItems.getChildCount() == MAX_ITEMS) break;
+ items[i] = item;
+ }
}
+ mItems.setItems(items);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 04f9c72..9d7d933 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2316,7 +2316,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mGestureRec.dump(fd, pw, args);
}
- mNetworkController.dump(fd, pw, args);
+ if (mNetworkController != null) {
+ mNetworkController.dump(fd, pw, args);
+ }
+ if (mBluetoothController != null) {
+ mBluetoothController.dump(fd, pw, args);
+ }
}
private String hunStateToString(Entry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 52d3cd3..33d1b15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -63,6 +63,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
private View mSignalCluster;
private View mSettingsButton;
private View mQsDetailHeader;
+ private TextView mQsDetailHeaderTitle;
+ private Switch mQsDetailHeaderSwitch;
private View mEmergencyCallsOnly;
private TextView mBatteryLevel;
@@ -120,6 +122,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
mSettingsButton.setOnClickListener(this);
mQsDetailHeader = findViewById(R.id.qs_detail_header);
mQsDetailHeader.setAlpha(0);
+ mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
+ mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
mEmergencyCallsOnly = findViewById(R.id.header_emergency_calls_only);
mBatteryLevel = (TextView) findViewById(R.id.battery_level);
loadDimens();
@@ -550,10 +554,22 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
});
}
+ @Override
+ public void onScanStateChanged(final boolean state) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ handleScanStateChanged(state);
+ }
+ });
+ }
+
private void handleToggleStateChanged(boolean state) {
- final Switch headerSwitch = (Switch)
- mQsDetailHeader.findViewById(android.R.id.toggle);
- headerSwitch.setChecked(state);
+ mQsDetailHeaderSwitch.setChecked(state);
+ }
+
+ private void handleScanStateChanged(boolean state) {
+ // TODO - waiting on framework asset
}
private void handleShowingDetail(final QSTile.DetailAdapter detail) {
@@ -561,18 +577,14 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
transition(mDateTime, !showingDetail);
transition(mQsDetailHeader, showingDetail);
if (showingDetail) {
- final TextView headerTitle = (TextView)
- mQsDetailHeader.findViewById(android.R.id.title);
- headerTitle.setText(detail.getTitle());
- final Switch headerSwitch = (Switch)
- mQsDetailHeader.findViewById(android.R.id.toggle);
+ mQsDetailHeaderTitle.setText(detail.getTitle());
final Boolean toggleState = detail.getToggleState();
if (toggleState == null) {
- headerSwitch.setVisibility(INVISIBLE);
+ mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
mQsDetailHeader.setClickable(false);
} else {
- headerSwitch.setVisibility(VISIBLE);
- headerSwitch.setChecked(toggleState);
+ mQsDetailHeaderSwitch.setVisibility(VISIBLE);
+ mQsDetailHeaderSwitch.setChecked(toggleState);
mQsDetailHeader.setClickable(true);
mQsDetailHeader.setOnClickListener(new OnClickListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 8e9fb30..cbdd138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import java.util.Set;
+
public interface BluetoothController {
void addStateChangedCallback(Callback callback);
void removeStateChangedCallback(Callback callback);
@@ -26,8 +28,32 @@ public interface BluetoothController {
boolean isBluetoothConnecting();
String getLastDeviceName();
void setBluetoothEnabled(boolean enabled);
+ Set<PairedDevice> getPairedDevices();
+ void connect(PairedDevice device);
+ void disconnect(PairedDevice device);
public interface Callback {
void onBluetoothStateChange(boolean enabled, boolean connecting);
+ void onBluetoothPairedDevicesChanged();
+ }
+
+ public static final class PairedDevice {
+ public static int STATE_DISCONNECTED = 0;
+ public static int STATE_CONNECTING = 1;
+ public static int STATE_CONNECTED = 2;
+ public static int STATE_DISCONNECTING = 3;
+
+ public String id;
+ public String name;
+ public int state = STATE_DISCONNECTED;
+ public Object tag;
+
+ public static String stateToString(int state) {
+ if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+ if (state == STATE_CONNECTING) return "STATE_CONNECTING";
+ if (state == STATE_CONNECTED) return "STATE_CONNECTED";
+ if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+ return "UNKNOWN";
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 379b509..f021623 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,49 +16,91 @@
package com.android.systemui.statusbar.policy;
+import static android.bluetooth.BluetoothAdapter.ERROR;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.profileStateToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.ParcelUuid;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.Set;
-public class BluetoothControllerImpl extends BroadcastReceiver implements BluetoothController {
- private static final String TAG = "StatusBar.BluetoothController";
+public class BluetoothControllerImpl implements BluetoothController {
+ private static final String TAG = "BluetoothController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final Context mContext;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private final Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>();
private final BluetoothAdapter mAdapter;
+ private final Receiver mReceiver = new Receiver();
+ private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
private boolean mEnabled;
private boolean mConnecting;
private BluetoothDevice mLastDevice;
public BluetoothControllerImpl(Context context) {
- mAdapter = BluetoothAdapter.getDefaultAdapter();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
- context.registerReceiver(this, filter);
-
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- handleAdapterStateChange(adapter.getState());
+ mContext = context;
+ final BluetoothManager bluetoothManager =
+ (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ mAdapter = bluetoothManager.getAdapter();
+ if (mAdapter == null) {
+ Log.w(TAG, "Default BT adapter not found");
+ return;
}
- fireCallbacks();
+
+ mReceiver.register();
+ setAdapterState(mAdapter.getState());
updateBondedBluetoothDevices();
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("BluetoothController state:");
+ pw.print(" mAdapter="); pw.println(mAdapter);
+ pw.print(" mEnabled="); pw.println(mEnabled);
+ pw.print(" mConnecting="); pw.println(mConnecting);
+ pw.print(" mLastDevice="); pw.println(mLastDevice);
+ pw.print(" mCallbacks.size="); pw.println(mCallbacks.size());
+ pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size());
+ for (int i = 0; i < mDeviceInfo.size(); i++) {
+ final BluetoothDevice device = mDeviceInfo.keyAt(i);
+ final DeviceInfo info = mDeviceInfo.valueAt(i);
+ pw.print(" "); pw.print(deviceToString(device));
+ pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
+ pw.print(" "); pw.println(infoToString(info));
+ }
+ }
+
+ private static String infoToString(DeviceInfo info) {
+ return info == null ? null : ("connectionState=" +
+ connectionStateToString(info.connectionState) + ",bonded=" + info.bonded);
+ }
+
public void addStateChangedCallback(Callback cb) {
mCallbacks.add(cb);
- fireCallback(cb);
+ fireStateChange(cb);
}
@Override
@@ -99,64 +141,191 @@ public class BluetoothControllerImpl extends BroadcastReceiver implements Blueto
return mAdapter != null;
}
- public Set<BluetoothDevice> getBondedBluetoothDevices() {
- return mBondedDevices;
+ @Override
+ public ArraySet<PairedDevice> getPairedDevices() {
+ final ArraySet<PairedDevice> rt = new ArraySet<>();
+ for (int i = 0; i < mDeviceInfo.size(); i++) {
+ final BluetoothDevice device = mDeviceInfo.keyAt(i);
+ final DeviceInfo info = mDeviceInfo.valueAt(i);
+ if (!info.bonded) continue;
+ final PairedDevice paired = new PairedDevice();
+ paired.id = device.getAddress();
+ paired.tag = device;
+ paired.name = device.getAliasName();
+ paired.state = connectionStateToPairedDeviceState(info.connectionState);
+ rt.add(paired);
+ }
+ return rt;
+ }
+
+ private static int connectionStateToPairedDeviceState(int state) {
+ if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
+ if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
+ if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
+ return PairedDevice.STATE_DISCONNECTED;
}
@Override
- public String getLastDeviceName() {
- return mLastDevice != null ? mLastDevice.getAliasName()
- : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getAliasName()
- : null;
+ public void connect(final PairedDevice pd) {
+ connect(pd, true);
}
@Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
+ public void disconnect(PairedDevice pd) {
+ connect(pd, false);
+ }
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- handleAdapterStateChange(
- intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
- }
- if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
- mConnecting = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)
- == BluetoothAdapter.STATE_CONNECTING;
- mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ private void connect(PairedDevice pd, final boolean connect) {
+ if (mAdapter == null || pd == null || pd.tag == null) return;
+ final BluetoothDevice device = (BluetoothDevice) pd.tag;
+ final String action = connect ? "connect" : "disconnect";
+ if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
+ final SparseBooleanArray profiles = new SparseBooleanArray();
+ for (ParcelUuid uuid : device.getUuids()) {
+ final int profile = uuidToProfile(uuid);
+ if (profile == 0) {
+ Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
+ + uuidToString(uuid));
+ continue;
+ }
+ final int profileState = mAdapter.getProfileConnectionState(profile);
+ if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile)
+ + " state = " + profileStateToString(profileState));
+ final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED;
+ if (connect != connected) {
+ profiles.put(profile, true);
+ }
}
- if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
- mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ for (int i = 0; i < profiles.size(); i++) {
+ final int profile = profiles.keyAt(i);
+ mAdapter.getProfileProxy(mContext, new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile));
+ final Profile p = BluetoothUtil.getProfile(proxy);
+ if (p == null) {
+ Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
+ } else {
+ final boolean ok = connect ? p.connect(device) : p.disconnect(device);
+ if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
+ + (ok ? "succeeded" : "failed"));
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
+ }
+ }, profile);
}
- fireCallbacks();
- updateBondedBluetoothDevices();
+ }
+
+ @Override
+ public String getLastDeviceName() {
+ return mLastDevice != null ? mLastDevice.getAliasName() : null;
}
private void updateBondedBluetoothDevices() {
- mBondedDevices.clear();
-
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- Set<BluetoothDevice> devices = adapter.getBondedDevices();
- if (devices != null) {
- for (BluetoothDevice device : devices) {
- if (device.getBondState() != BluetoothDevice.BOND_NONE) {
- mBondedDevices.add(device);
- }
+ if (mAdapter == null) return;
+ final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ for (DeviceInfo info : mDeviceInfo.values()) {
+ info.bonded = false;
+ }
+ int bondedCount = 0;
+ BluetoothDevice lastBonded = null;
+ if (bondedDevices != null) {
+ for (BluetoothDevice bondedDevice : bondedDevices) {
+ final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
+ updateInfo(bondedDevice).bonded = bonded;
+ if (bonded) {
+ bondedCount++;
+ lastBonded = bondedDevice;
}
}
}
+ if (mLastDevice == null && bondedCount == 1) {
+ mLastDevice = lastBonded;
+ }
+ firePairedDevicesChanged();
+ }
+
+ private void firePairedDevicesChanged() {
+ for (Callback cb : mCallbacks) {
+ cb.onBluetoothPairedDevicesChanged();
+ }
+ }
+
+ private void setAdapterState(int adapterState) {
+ final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
+ if (mEnabled == enabled) return;
+ mEnabled = enabled;
+ fireStateChange();
}
- private void handleAdapterStateChange(int adapterState) {
- mEnabled = (adapterState == BluetoothAdapter.STATE_ON);
+ private void setConnecting(boolean connecting) {
+ if (mConnecting == connecting) return;
+ mConnecting = connecting;
+ fireStateChange();
}
- private void fireCallbacks() {
+ private void fireStateChange() {
for (Callback cb : mCallbacks) {
- fireCallback(cb);
+ fireStateChange(cb);
}
}
- private void fireCallback(Callback cb) {
+ private void fireStateChange(Callback cb) {
cb.onBluetoothStateChange(mEnabled, mConnecting);
}
+
+ private final class Receiver extends BroadcastReceiver {
+ public void register() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
+ mContext.registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
+ if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
+ } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+ final DeviceInfo info = updateInfo(device);
+ final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+ ERROR);
+ if (state != ERROR) {
+ info.connectionState = state;
+ }
+ mLastDevice = device;
+ if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
+ + connectionStateToString(state) + " " + deviceToString(device));
+ setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING);
+ } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
+ updateInfo(device);
+ mLastDevice = device;
+ } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
+ if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
+ // we'll update all bonded devices below
+ }
+ updateBondedBluetoothDevices();
+ }
+ }
+
+ private DeviceInfo updateInfo(BluetoothDevice device) {
+ DeviceInfo info = mDeviceInfo.get(device);
+ info = info != null ? info : new DeviceInfo();
+ mDeviceInfo.put(device, info);
+ return info;
+ }
+
+ private static class DeviceInfo {
+ int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ boolean bonded; // per getBondedDevices
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
new file mode 100644
index 0000000..1b4be85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.text.TextUtils;
+
+public class BluetoothUtil {
+
+ public static String profileToString(int profile) {
+ if (profile == BluetoothProfile.HEADSET) return "HEADSET";
+ if (profile == BluetoothProfile.A2DP) return "A2DP";
+ if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
+ return "UNKNOWN";
+ }
+
+ public static String profileStateToString(int state) {
+ if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED";
+ if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING";
+ if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+ if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+ return "STATE_UNKNOWN";
+ }
+
+ public static String uuidToString(ParcelUuid uuid) {
+ if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink";
+ if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource";
+ if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist";
+ if (BluetoothUuid.HSP.equals(uuid)) return "HSP";
+ if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG";
+ if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree";
+ if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG";
+ if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController";
+ if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget";
+ if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush";
+ if (BluetoothUuid.Hid.equals(uuid)) return "Hid";
+ if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp";
+ if (BluetoothUuid.PANU.equals(uuid)) return "PANU";
+ if (BluetoothUuid.NAP.equals(uuid)) return "NAP";
+ if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP";
+ if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE";
+ if (BluetoothUuid.MAP.equals(uuid)) return "MAP";
+ if (BluetoothUuid.MNS.equals(uuid)) return "MNS";
+ if (BluetoothUuid.MAS.equals(uuid)) return "MAS";
+ return uuid != null ? uuid.toString() : null;
+ }
+
+ public static String connectionStateToString(int connectionState) {
+ if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+ if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED";
+ if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+ if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING";
+ return "ERROR";
+ }
+
+ public static String deviceToString(BluetoothDevice device) {
+ return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']');
+ }
+
+ public static String uuidsToString(BluetoothDevice device) {
+ if (device == null) return null;
+ final ParcelUuid[] ids = device.getUuids();
+ if (ids == null) return null;
+ final String[] tokens = new String[ids.length];
+ for (int i = 0; i < tokens.length; i++) {
+ tokens[i] = uuidToString(ids[i]);
+ }
+ return TextUtils.join(",", tokens);
+ }
+
+ public static int uuidToProfile(ParcelUuid uuid) {
+ if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP;
+ if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP;
+
+ if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET;
+ if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET;
+
+ if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP;
+ if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP;
+ if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP;
+
+ if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;
+
+ return 0;
+ }
+
+ public static Profile getProfile(BluetoothProfile p) {
+ if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p);
+ if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p);
+ if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p);
+ if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p);
+ if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p);
+ if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p);
+ if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p);
+ return null;
+ }
+
+ private static Profile newProfile(final BluetoothA2dp a2dp) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return a2dp.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return a2dp.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothHeadset headset) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return headset.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return headset.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothA2dpSink sink) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return sink.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return sink.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothHeadsetClient client) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return client.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return client.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothInputDevice input) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return input.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return input.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothMap map) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return map.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return map.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothPan pan) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return pan.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return pan.disconnect(device);
+ }
+ };
+ }
+
+ // common abstraction for supported profiles
+ public interface Profile {
+ boolean connect(BluetoothDevice device);
+ boolean disconnect(BluetoothDevice device);
+ }
+}