diff options
12 files changed, 669 insertions, 4 deletions
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index c8b45c7..3601b39 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -61,6 +61,8 @@ interface IInputManager { // Registers an input devices changed listener. void registerInputDevicesChangedListener(IInputDevicesChangedListener listener); + // Queries whether the device is currently in tablet mode + int isInTabletMode(); // Registers a tablet mode change listener void registerTabletModeChangedListener(ITabletModeChangedListener listener); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index a754d6b..618864f 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -19,6 +19,7 @@ package android.hardware.input; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; +import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.content.Context; @@ -39,6 +40,8 @@ import android.util.SparseArray; import android.view.InputDevice; import android.view.InputEvent; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -179,6 +182,31 @@ public final class InputManager { */ public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({SWITCH_STATE_UNKNOWN, SWITCH_STATE_OFF, SWITCH_STATE_ON}) + public @interface SwitchState {} + + /** + * Switch State: Unknown. + * + * The system has yet to report a valid value for the switch. + * @hide + */ + public static final int SWITCH_STATE_UNKNOWN = -1; + + /** + * Switch State: Off. + * @hide + */ + public static final int SWITCH_STATE_OFF = 0; + + /** + * Switch State: On. + * @hide + */ + public static final int SWITCH_STATE_ON = 1; + private InputManager(IInputManager im) { mIm = im; } @@ -339,6 +367,23 @@ public final class InputManager { } /** + * Queries whether the device is in tablet mode. + * + * @return The tablet switch state which is one of {@link #SWITCH_STATE_UNKNOWN}, + * {@link #SWITCH_STATE_OFF} or {@link #SWITCH_STATE_ON}. + * @hide + */ + @SwitchState + public int isInTabletMode() { + try { + return mIm.isInTabletMode(); + } catch (RemoteException ex) { + Log.w(TAG, "Could not get tablet mode state", ex); + return SWITCH_STATE_UNKNOWN; + } + } + + /** * Register a tablet mode changed listener. * * @param listener The listener to register. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f13f821..4f71699 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2059,10 +2059,11 @@ <permission android:name="android.permission.SET_KEYBOARD_LAYOUT" android:protectionLevel="signature" /> - <!-- Allows an application to monitor changes in tablet mode. + <!-- Allows an application to query tablet mode state and monitor changes + in it. <p>Not for use by third-party applications. @hide --> - <permission android:name="android.permission.TABLET_MODE_LISTENER" + <permission android:name="android.permission.TABLET_MODE" android:protectionLevel="signature" /> <!-- Allows an application to request installing packages. Apps diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6e8f81e..6c96f84 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2322,4 +2322,8 @@ <!-- Allow the gesture to double tap the power button twice to start the camera while the device is non-interactive. --> <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool> + + <!-- The BT name of the keyboard packaged with the device. If this is defined, SystemUI will + automatically try to pair with it when the device exits tablet mode. --> + <string translatable="false" name="config_packagedKeyboardName"></string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 95cd143..d469233 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2329,4 +2329,6 @@ <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" /> <java-symbol type="drawable" name="platlogo_m" /> + + <java-symbol type="string" name="config_packagedKeyboardName" /> </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java index 0380e21..f935f31 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -19,6 +19,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.BluetoothLeScanner; import android.content.Context; import android.os.ParcelUuid; import android.util.Log; @@ -106,6 +107,10 @@ public final class LocalBluetoothAdapter { return mAdapter.getScanMode(); } + public BluetoothLeScanner getBluetoothLeScanner() { + return mAdapter.getBluetoothLeScanner(); + } + public int getState() { return mAdapter.getState(); } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 372fa03..80f4d4c 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -127,6 +127,9 @@ <!-- Assist --> <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" /> + <!-- Listen for keyboard attachment / detachment --> + <uses-permission android:name="android.permission.TABLET_MODE" /> + <!-- Self permission for internal broadcasts. --> <permission android:name="com.android.systemui.permission.SELF" android:protectionLevel="signature" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index dfa85ce..dc9c9fd 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1126,4 +1126,14 @@ <!-- Dialog asking if the tuner should really be removed from settings [CHAR LIMIT=NONE]--> <string name="remove_from_settings_prompt">Remove System UI Tuner from Settings and stop using all of its features?"</string> + <!-- Dialog title asking if Bluetooth should be enabled [CHAR LIMIT=NONE] --> + <string name="enable_bluetooth_title">Turn on Bluetooth?</string> + + <!-- Dialog message explaining why Bluetooth should be enabled when a packaged keyboard is + conncted to the device [CHAR LIMIT=NONE] --> + <string name="enable_bluetooth_message">To connect your keyboard with your tablet, you first have to turn on Bluetooth.</string> + + <!-- Bluetooth enablement ok text [CHAR LIMIT=40] --> + <string name="enable_bluetooth_confirmation_ok">Turn on</string> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 33bd726..0b066af 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -48,6 +48,7 @@ public class SystemUIApplication extends Application { com.android.systemui.usb.StorageNotification.class, com.android.systemui.power.PowerUI.class, com.android.systemui.media.RingtonePlayer.class, + com.android.systemui.keyboard.KeyboardUI.class, }; /** diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java new file mode 100644 index 0000000..64f3e13 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java @@ -0,0 +1,34 @@ +/* + * 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.systemui.keyboard; + +import android.app.AlertDialog; +import android.content.Context; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.SystemUIDialog; + +public class BluetoothDialog extends SystemUIDialog { + + public BluetoothDialog(Context context) { + super(context); + + getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + setShowForAllUsers(true); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java new file mode 100644 index 0000000..a43d520 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -0,0 +1,549 @@ +/* + * 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.systemui.keyboard; + +import android.app.AlertDialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.Slog; +import android.view.WindowManager; + +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.R; +import com.android.systemui.SystemUI; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeChangedListener { + private static final String TAG = "KeyboardUI"; + private static final boolean DEBUG = false; + + // Give BT some time to start after SyUI comes up. This avoids flashing a dialog in the user's + // face because BT starts a little bit later in the boot process than SysUI and it takes some + // time for us to receive the signal that it's starting. + private static final long BLUETOOTH_START_DELAY_MILLIS = 10 * 1000; + + private static final int STATE_NOT_ENABLED = -1; + private static final int STATE_UNKNOWN = 0; + private static final int STATE_WAITING_FOR_BOOT_COMPLETED = 1; + private static final int STATE_WAITING_FOR_TABLET_MODE_EXIT = 2; + private static final int STATE_WAITING_FOR_DEVICE_DISCOVERY = 3; + private static final int STATE_WAITING_FOR_BLUETOOTH = 4; + private static final int STATE_WAITING_FOR_STATE_PAIRED = 5; + private static final int STATE_PAIRING = 6; + private static final int STATE_PAIRED = 7; + private static final int STATE_USER_CANCELLED = 8; + private static final int STATE_DEVICE_NOT_FOUND = 9; + + private static final int MSG_INIT = 0; + private static final int MSG_ON_BOOT_COMPLETED = 1; + private static final int MSG_PROCESS_KEYBOARD_STATE = 2; + private static final int MSG_ENABLE_BLUETOOTH = 3; + private static final int MSG_ON_BLUETOOTH_STATE_CHANGED = 4; + private static final int MSG_ON_DEVICE_BOND_STATE_CHANGED = 5; + private static final int MSG_ON_BLUETOOTH_DEVICE_ADDED = 6; + private static final int MSG_ON_BLE_SCAN_FAILED = 7; + private static final int MSG_SHOW_BLUETOOTH_DIALOG = 8; + private static final int MSG_DISMISS_BLUETOOTH_DIALOG = 9; + + private volatile KeyboardHandler mHandler; + private volatile KeyboardUIHandler mUIHandler; + + protected volatile Context mContext; + + private boolean mEnabled; + private String mKeyboardName; + private CachedBluetoothDeviceManager mCachedDeviceManager; + private LocalBluetoothAdapter mLocalBluetoothAdapter; + private LocalBluetoothProfileManager mProfileManager; + private boolean mBootCompleted; + private long mBootCompletedTime; + + private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN; + private ScanCallback mScanCallback; + private BluetoothDialog mDialog; + + private int mState; + + @Override + public void start() { + mContext = super.mContext; + HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + mHandler = new KeyboardHandler(thread.getLooper()); + mHandler.sendEmptyMessage(MSG_INIT); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("KeyboardUI:"); + pw.println(" mEnabled=" + mEnabled); + pw.println(" mBootCompleted=" + mEnabled); + pw.println(" mBootCompletedTime=" + mBootCompletedTime); + pw.println(" mKeyboardName=" + mKeyboardName); + pw.println(" mInTabletMode=" + mInTabletMode); + pw.println(" mState=" + stateToString(mState)); + } + + @Override + protected void onBootCompleted() { + mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED); + } + + @Override + public void onTabletModeChanged(long whenNanos, boolean inTabletMode) { + if (DEBUG) { + Slog.d(TAG, "onTabletModeChanged(" + whenNanos + ", " + inTabletMode + ")"); + } + + if (inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_ON + || !inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_OFF) { + mInTabletMode = inTabletMode ? + InputManager.SWITCH_STATE_ON : InputManager.SWITCH_STATE_OFF; + processKeyboardState(); + } + } + + // Shoud only be called on the handler thread + private void init() { + Context context = mContext; + mKeyboardName = + context.getString(com.android.internal.R.string.config_packagedKeyboardName); + if (TextUtils.isEmpty(mKeyboardName)) { + if (DEBUG) { + Slog.d(TAG, "No packaged keyboard name given."); + } + return; + } + + LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(context, null); + if (bluetoothManager == null) { + if (DEBUG) { + Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance"); + } + return; + } + mEnabled = true; + mCachedDeviceManager = bluetoothManager.getCachedDeviceManager(); + mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter(); + mProfileManager = bluetoothManager.getProfileManager(); + bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler()); + + InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + im.registerOnTabletModeChangedListener(this, mHandler); + mInTabletMode = im.isInTabletMode(); + + processKeyboardState(); + mUIHandler = new KeyboardUIHandler(); + } + + // Should only be called on the handler thread + private void processKeyboardState() { + mHandler.removeMessages(MSG_PROCESS_KEYBOARD_STATE); + + if (!mEnabled) { + mState = STATE_NOT_ENABLED; + return; + } + + if (!mBootCompleted) { + mState = STATE_WAITING_FOR_BOOT_COMPLETED; + return; + } + + if (mInTabletMode != InputManager.SWITCH_STATE_OFF) { + if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) { + stopScanning(); + } + mState = STATE_WAITING_FOR_TABLET_MODE_EXIT; + return; + } + + final int btState = mLocalBluetoothAdapter.getState(); + if (btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON + && mState == STATE_WAITING_FOR_BLUETOOTH) { + // If we're waiting for bluetooth but it has come on in the meantime, or is coming + // on, just dismiss the dialog. This frequently happens during device startup. + mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG); + } + + if (btState == BluetoothAdapter.STATE_TURNING_ON) { + mState = STATE_WAITING_FOR_BLUETOOTH; + // Wait for bluetooth to fully come on. + return; + } + + if (btState != BluetoothAdapter.STATE_ON) { + mState = STATE_WAITING_FOR_BLUETOOTH; + showBluetoothDialog(); + return; + } + + CachedBluetoothDevice device = getPairedKeyboard(); + if ((mState == STATE_WAITING_FOR_TABLET_MODE_EXIT || mState == STATE_WAITING_FOR_BLUETOOTH) + && device != null) { + // If we're just coming out of tablet mode or BT just turned on, + // then we want to go ahead and automatically connect to the + // keyboard. We want to avoid this in other cases because we might + // be spuriously called after the user has manually disconnected + // the keyboard, meaning we shouldn't try to automtically connect + // it again. + mState = STATE_PAIRED; + device.connect(false); + return; + } + + device = getDiscoveredKeyboard(); + if (device != null) { + mState = STATE_PAIRING; + device.startPairing(); + } else { + mState = STATE_WAITING_FOR_DEVICE_DISCOVERY; + startScanning(); + } + } + + // Should only be called on the handler thread + public void onBootCompletedInternal() { + mBootCompleted = true; + mBootCompletedTime = SystemClock.uptimeMillis(); + if (mState == STATE_WAITING_FOR_BOOT_COMPLETED) { + processKeyboardState(); + } + } + + // Should only be called on the handler thread + private void showBluetoothDialog() { + if (isUserSetupComplete()) { + long now = SystemClock.uptimeMillis(); + long earliestDialogTime = mBootCompletedTime + BLUETOOTH_START_DELAY_MILLIS; + if (earliestDialogTime < now) { + mUIHandler.sendEmptyMessage(MSG_SHOW_BLUETOOTH_DIALOG); + } else { + mHandler.sendEmptyMessageAtTime(MSG_PROCESS_KEYBOARD_STATE, earliestDialogTime); + } + } else { + // If we're in setup wizard and the keyboard is docked, just automatically enable BT. + mLocalBluetoothAdapter.enable(); + } + } + + private boolean isUserSetupComplete() { + ContentResolver resolver = mContext.getContentResolver(); + return Secure.getIntForUser( + resolver, Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; + } + + private CachedBluetoothDevice getPairedKeyboard() { + Set<BluetoothDevice> devices = mLocalBluetoothAdapter.getBondedDevices(); + for (BluetoothDevice d : devices) { + if (mKeyboardName.equals(d.getName())) { + return getCachedBluetoothDevice(d); + } + } + return null; + } + + private CachedBluetoothDevice getDiscoveredKeyboard() { + Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy(); + for (CachedBluetoothDevice d : devices) { + if (d.getName().equals(mKeyboardName)) { + return d; + } + } + return null; + } + + + private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice d) { + CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(d); + if (cachedDevice == null) { + cachedDevice = mCachedDeviceManager.addDevice( + mLocalBluetoothAdapter, mProfileManager, d); + } + return cachedDevice; + } + + private void startScanning() { + BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner(); + ScanFilter filter = (new ScanFilter.Builder()).setDeviceName(mKeyboardName).build(); + ScanSettings settings = (new ScanSettings.Builder()) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setReportDelay(0) + .build(); + mScanCallback = new KeyboardScanCallback(); + scanner.startScan(Arrays.asList(filter), settings, mScanCallback); + } + + private void stopScanning() { + if (mScanCallback != null) { + mLocalBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback); + mScanCallback = null; + } + } + + // Should only be called on the handler thread + private void onDeviceAddedInternal(CachedBluetoothDevice d) { + if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && d.getName().equals(mKeyboardName)) { + stopScanning(); + d.startPairing(); + mState = STATE_PAIRING; + } + } + + // Should only be called on the handler thread + private void onBluetoothStateChangedInternal(int bluetoothState) { + if (bluetoothState == BluetoothAdapter.STATE_ON && mState == STATE_WAITING_FOR_BLUETOOTH) { + processKeyboardState(); + } + } + + // Should only be called on the handler thread + private void onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState) { + if (d.getName().equals(mKeyboardName) && bondState == BluetoothDevice.BOND_BONDED) { + // We don't need to manually connect to the device here because it will automatically + // try to connect after it has been paired. + mState = STATE_PAIRED; + } + } + + // Should only be called on the handler thread + private void onBleScanFailedInternal() { + mScanCallback = null; + if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) { + mState = STATE_DEVICE_NOT_FOUND; + } + } + + private final class KeyboardUIHandler extends Handler { + public KeyboardUIHandler() { + super(Looper.getMainLooper(), null, true /*async*/); + } + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_SHOW_BLUETOOTH_DIALOG: { + DialogInterface.OnClickListener listener = new BluetoothDialogClickListener(); + mDialog = new BluetoothDialog(mContext); + mDialog.setTitle(R.string.enable_bluetooth_title); + mDialog.setMessage(R.string.enable_bluetooth_message); + mDialog.setPositiveButton(R.string.enable_bluetooth_confirmation_ok, listener); + mDialog.setNegativeButton(android.R.string.cancel, listener); + mDialog.show(); + break; + } + case MSG_DISMISS_BLUETOOTH_DIALOG: { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + break; + } + } + } + } + + private final class KeyboardHandler extends Handler { + public KeyboardHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_INIT: { + init(); + break; + } + case MSG_ON_BOOT_COMPLETED: { + onBootCompletedInternal(); + break; + } + case MSG_PROCESS_KEYBOARD_STATE: { + processKeyboardState(); + break; + } + case MSG_ENABLE_BLUETOOTH: { + boolean enable = msg.arg1 == 1; + if (enable) { + mLocalBluetoothAdapter.enable(); + } else { + mState = STATE_USER_CANCELLED; + } + } + case MSG_ON_BLUETOOTH_STATE_CHANGED: { + int bluetoothState = msg.arg1; + onBluetoothStateChangedInternal(bluetoothState); + break; + } + case MSG_ON_DEVICE_BOND_STATE_CHANGED: { + CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj; + int bondState = msg.arg1; + onDeviceBondStateChangedInternal(d, bondState); + break; + } + case MSG_ON_BLUETOOTH_DEVICE_ADDED: { + BluetoothDevice d = (BluetoothDevice)msg.obj; + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d); + onDeviceAddedInternal(cachedDevice); + break; + + } + case MSG_ON_BLE_SCAN_FAILED: { + onBleScanFailedInternal(); + break; + } + } + } + } + + private final class BluetoothDialogClickListener implements DialogInterface.OnClickListener { + @Override + public void onClick(DialogInterface dialog, int which) { + int enable = DialogInterface.BUTTON_POSITIVE == which ? 1 : 0; + mHandler.obtainMessage(MSG_ENABLE_BLUETOOTH, enable, 0).sendToTarget(); + mDialog = null; + } + } + + private final class KeyboardScanCallback extends ScanCallback { + @Override + public void onBatchScanResults(List<ScanResult> results) { + if (DEBUG) { + Slog.d(TAG, "onBatchScanResults(" + results.size() + ")"); + } + if (!results.isEmpty()) { + BluetoothDevice bestDevice = results.get(0).getDevice(); + int bestRssi = results.get(0).getRssi(); + final int N = results.size(); + for (int i = 0; i < N; i++) { + ScanResult r = results.get(i); + if (r.getRssi() > bestRssi) { + bestDevice = r.getDevice(); + } + } + mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, bestDevice).sendToTarget(); + } + } + + @Override + public void onScanFailed(int errorCode) { + if (DEBUG) { + Slog.d(TAG, "onScanFailed(" + errorCode + ")"); + } + mHandler.obtainMessage(MSG_ON_BLE_SCAN_FAILED).sendToTarget(); + } + + @Override + public void onScanResult(int callbackType, ScanResult result) { + if (DEBUG) { + Slog.d(TAG, "onScanResult(" + callbackType + ", " + result + ")"); + } + mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, + result.getDevice()).sendToTarget(); + } + } + + private final class BluetoothCallbackHandler implements BluetoothCallback { + @Override + public void onBluetoothStateChanged(int bluetoothState) { + mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED, + bluetoothState, 0).sendToTarget(); + } + + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED, + bondState, 0, cachedDevice).sendToTarget(); + } + + @Override + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { } + @Override + public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { } + @Override + public void onScanningStateChanged(boolean started) { } + @Override + public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { } + } + + private static String stateToString(int state) { + switch (state) { + case STATE_NOT_ENABLED: + return "STATE_NOT_ENABLED"; + case STATE_WAITING_FOR_BOOT_COMPLETED: + return "STATE_WAITING_FOR_BOOT_COMPLETED"; + case STATE_WAITING_FOR_TABLET_MODE_EXIT: + return "STATE_WAITING_FOR_TABLET_MODE_EXIT"; + case STATE_WAITING_FOR_DEVICE_DISCOVERY: + return "STATE_WAITING_FOR_DEVICE_DISCOVERY"; + case STATE_WAITING_FOR_BLUETOOTH: + return "STATE_WAITING_FOR_BLUETOOTH"; + case STATE_WAITING_FOR_STATE_PAIRED: + return "STATE_WAITING_FOR_STATE_PAIRED"; + case STATE_PAIRING: + return "STATE_PAIRING"; + case STATE_PAIRED: + return "STATE_PAIRED"; + case STATE_USER_CANCELLED: + return "STATE_USER_CANCELLED"; + case STATE_DEVICE_NOT_FOUND: + return "STATE_DEVICE_NOT_FOUND"; + case STATE_UNKNOWN: + default: + return "STATE_UNKNOWN"; + } + } +} diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 5a13672..0205a20 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -792,8 +792,17 @@ public class InputManagerService extends IInputManager.Stub } @Override // Binder call + public int isInTabletMode() { + if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE, + "isInTabletMode()")) { + throw new SecurityException("Requires TABLET_MODE permission"); + } + return getSwitchState(-1, InputDevice.SOURCE_ANY, SW_TABLET_MODE); + } + + @Override // Binder call public void registerTabletModeChangedListener(ITabletModeChangedListener listener) { - if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE_LISTENER, + if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE, "registerTabletModeChangedListener()")) { throw new SecurityException("Requires TABLET_MODE_LISTENER permission"); } @@ -1488,7 +1497,7 @@ public class InputManagerService extends IInputManager.Stub switchMask); } - if ((switchMask & SW_TABLET_MODE) != 0) { + if ((switchMask & SW_TABLET_MODE_BIT) != 0) { SomeArgs args = SomeArgs.obtain(); args.argi1 = (int) (whenNanos & 0xFFFFFFFF); args.argi2 = (int) (whenNanos >> 32); |