diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 5225b9be70f..0af68664a1d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -487,6 +487,25 @@ + + + + + + + + + + + diff --git a/res/layout/bluetooth_discoverable.xml b/res/layout/bluetooth_discoverable.xml new file mode 100644 index 00000000000..3673774a994 --- /dev/null +++ b/res/layout/bluetooth_discoverable.xml @@ -0,0 +1,33 @@ + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 7282c0314fa..feeb3f08120 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -15,6 +15,11 @@ --> + + "Yes" + + + "No" @@ -227,6 +232,21 @@ Bluetooth device picker + + "Bluetooth permission request" + + + "An application on your phone is requesting permission to turn on Bluetooth. Do you want to do this?" + + + "An application on your phone is requesting permission to make your phone discoverable by other Bluetooth devices for %1$d seconds. Do you want to do this?" + + + "An application on your phone is requesting permission to turn on Bluetooth and to make your phone discoverable by other devices for %1$d seconds. Do you want to do this?" + + + "Turning on Bluetooth\u2026" + Empty button\u2026 diff --git a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java index 166088fa624..eec0ad8efe9 100644 --- a/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothDiscoverableEnabler.java @@ -39,9 +39,9 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan private static final String SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT = "debug.bt.discoverable_time"; - private static final int DISCOVERABLE_TIMEOUT = 120; + /* package */ static final int DEFAULT_DISCOVERABLE_TIMEOUT = 120; - private static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP = + /* package */ static final String SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP = "discoverable_end_timestamp"; private final Context mContext; @@ -135,7 +135,7 @@ public class BluetoothDiscoverableEnabler implements Preference.OnPreferenceChan private int getDiscoverableTimeout() { int timeout = SystemProperties.getInt(SYSTEM_PROPERTY_DISCOVERABLE_TIMEOUT, -1); if (timeout <= 0) { - timeout = DISCOVERABLE_TIMEOUT; + timeout = DEFAULT_DISCOVERABLE_TIMEOUT; } return timeout; diff --git a/src/com/android/settings/bluetooth/RequestPermissionActivity.java b/src/com/android/settings/bluetooth/RequestPermissionActivity.java new file mode 100644 index 00000000000..d1567a95203 --- /dev/null +++ b/src/com/android/settings/bluetooth/RequestPermissionActivity.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2009 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.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; +import com.android.settings.R; + +/** + * RequestPermissionActivity asks the user whether to enable discovery. This is + * usually started by an application wanted to start bluetooth and or discovery + */ +public class RequestPermissionActivity extends AlertActivity implements + DialogInterface.OnClickListener { + // Command line to test this + // adb shell am start -a android.bluetooth.adapter.action.REQUEST_ENABLE + // adb shell am start -a android.bluetooth.adapter.action.REQUEST_DISCOVERABLE + + private static final String TAG = "RequestPermissionActivity"; + + private static final int MAX_DISCOVERABLE_TIMEOUT = 300; + + // Result code: Error + public static final int RESULT_ERROR = -2; + + // Result code: User rejected the request + public static final int RESULT_USER_DENIED = -1; + + // Non-error return code: BT is starting or has started successfully. Used + // by this Activity and RequestPermissionHelperActivity + /* package */ static final int RESULT_BT_STARTING_OR_STARTED = -1000; + + private static final int REQUEST_CODE_START_BT = 1; + + private LocalBluetoothManager mLocalManager; + + private int mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT; + + /* + * True if bluetooth wasn't enabled and RequestPermissionHelperActivity was + * started to ask the user and start bt. + * + * If/when that activity returns successfully, display please wait msg then + * go away when bt has started and discovery mode has been enabled. + */ + private boolean mNeededToEnableBluetooth; + + // True if requesting BT to be turned on + // False if requesting BT to be turned on + discoverable mode + private boolean mEnableOnly = false; + + private boolean mUserConfirmed = false; + + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent == null) + return; + if (mNeededToEnableBluetooth + && BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR); + if (state == BluetoothAdapter.STATE_ON) { + if (mUserConfirmed) { + proceedAndFinish(false); + } + } + } + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (parseIntent()) { + finish(); + return; + } + + int btState = mLocalManager.getBluetoothState(); + + switch (btState) { + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + case BluetoothAdapter.STATE_TURNING_ON: + /* + * Strictly speaking STATE_TURNING_ON belong with STATE_ON; + * however, BT may not be ready when the user clicks yes and we + * would fail to turn on discovery mode. By kicking this to the + * RequestPermissionHelperActivity, this class will handle that + * case via the broadcast receiver. + */ + + /* + * Start the helper activity to: + * 1) ask the user about enabling bt AND discovery + * 2) enable BT upon confirmation + */ + registerReceiver(mReceiver, + new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); + Intent i = new Intent(); + i.setClass(this, RequestPermissionHelperActivity.class); + if (mEnableOnly) { + i.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON); + } else { + i.setAction(RequestPermissionHelperActivity. + ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE); + i.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout); + } + startActivityForResult(i, REQUEST_CODE_START_BT); + mNeededToEnableBluetooth = true; + break; + case BluetoothAdapter.STATE_ON: + if (mEnableOnly) { + // Nothing to do. Already enabled. + proceedAndFinish(false); + return; + } else { + // Ask the user about enabling discovery mode + createDialog(); + break; + } + } + } + + private void createDialog() { + final AlertController.AlertParams p = mAlertParams; + p.mIconId = android.R.drawable.ic_dialog_info; + p.mTitle = getString(R.string.bluetooth_permission_request); + + View view = getLayoutInflater().inflate(R.layout.bluetooth_discoverable, null); + p.mView = view; + TextView tv = (TextView) view.findViewById(R.id.message); + + if (mNeededToEnableBluetooth) { + // RequestPermissionHelperActivity has gotten confirmation from user + // to turn on BT + tv.setText(getString(R.string.bluetooth_turning_on)); + } else { + // Ask the user whether to turn on discovery mode or not + tv.setText(getString(R.string.bluetooth_ask_enablement_and_discovery, mTimeout)); + p.mPositiveButtonText = getString(R.string.yes); + p.mPositiveButtonListener = this; + p.mNegativeButtonText = getString(R.string.no); + p.mNegativeButtonListener = this; + } + + setupAlert(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode != REQUEST_CODE_START_BT) { + Log.e(TAG, "Unexpected onActivityResult " + requestCode + " " + resultCode); + setResult(RESULT_ERROR); + finish(); + return; + } + if (resultCode != RESULT_BT_STARTING_OR_STARTED) { + setResult(resultCode); + finish(); + } + + // Back from RequestPermissionHelperActivity. User confirmed to enable + // BT and discoverable mode. + mUserConfirmed = true; + + if (mLocalManager.getBluetoothState() == BluetoothAdapter.STATE_ON) { + proceedAndFinish(false); + } else { + // If BT is not up yet, show "Turning on Bluetooth..." + createDialog(); + } + } + + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + proceedAndFinish(true); + break; + + case DialogInterface.BUTTON_NEGATIVE: + setResult(RESULT_USER_DENIED); + break; + } + } + + private void proceedAndFinish(boolean buttonPressed) { + int returnCode; + + if (mEnableOnly) { + // BT enabled. Done + returnCode = 0; + } else if (mLocalManager.getBluetoothAdapter().setScanMode( + BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout)) { + // If already in discoverable mode, this will extend the timeout. + persistDiscoverableEndTimestamp(System.currentTimeMillis() + mTimeout * 1000); + returnCode = mTimeout; + } else { + returnCode = RESULT_ERROR; + } + + setResult(returnCode); + if (!buttonPressed) { + finish(); + } + } + + private boolean parseIntent() { + Intent intent = getIntent(); + if (intent != null && intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_ENABLE)) { + mEnableOnly = true; + } else if (intent != null + && intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)) { + mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, + BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT); + + Log.e(TAG, "Timeout = " + mTimeout); + + if (mTimeout > MAX_DISCOVERABLE_TIMEOUT) { + mTimeout = MAX_DISCOVERABLE_TIMEOUT; + } else if (mTimeout <= 0) { + mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT; + } + } else { + Log.e(TAG, "Error: this activity may be started only with intent " + + BluetoothAdapter.ACTION_REQUEST_ENABLE + " or " + + BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); + setResult(RESULT_ERROR); + return true; + } + + mLocalManager = LocalBluetoothManager.getInstance(this); + if (mLocalManager == null) { + Log.e(TAG, "Error: there's a problem starting bluetooth"); + setResult(RESULT_ERROR); + return true; + } + + return false; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mNeededToEnableBluetooth) unregisterReceiver(mReceiver); + } + + private void persistDiscoverableEndTimestamp(long endTimestamp) { + SharedPreferences.Editor editor = mLocalManager.getSharedPreferences().edit(); + editor.putLong( + BluetoothDiscoverableEnabler.SHARED_PREFERENCES_KEY_DISCOVERABLE_END_TIMESTAMP, + endTimestamp); + editor.commit(); + } + + @Override + public void onBackPressed() { + setResult(RequestPermissionActivity.RESULT_USER_DENIED); + super.onBackPressed(); + } +} diff --git a/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java new file mode 100644 index 00000000000..b8e0672d1b3 --- /dev/null +++ b/src/com/android/settings/bluetooth/RequestPermissionHelperActivity.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2009 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.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; +import com.android.settings.R; + +/** + * RequestPermissionHelperActivity asks the user whether to enable discovery. + * This is usually started by RequestPermissionActivity. + */ +public class RequestPermissionHelperActivity extends AlertActivity implements + DialogInterface.OnClickListener { + private static final String TAG = "RequestPermissionHelperActivity"; + + public static final String ACTION_INTERNAL_REQUEST_BT_ON = + "com.android.settings.bluetooth.ACTION_INTERNAL_REQUEST_BT_ON"; + + public static final String ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE = + "com.android.settings.bluetooth.ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE"; + + private LocalBluetoothManager mLocalManager; + + private int mTimeout; + + // True if requesting BT to be turned on + // False if requesting BT to be turned on + discoverable mode + private boolean mEnableOnly; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (parseIntent()) { + finish(); + return; + } + + createDialog(); + } + + void createDialog() { + final AlertController.AlertParams p = mAlertParams; + p.mIconId = android.R.drawable.ic_dialog_info; + p.mTitle = getString(R.string.bluetooth_permission_request); + + View view = getLayoutInflater().inflate(R.layout.bluetooth_discoverable, null); + p.mView = view; + TextView tv = (TextView) view.findViewById(R.id.message); + + if (mEnableOnly) { + tv.setText(getString(R.string.bluetooth_ask_enablement)); + } else { + tv.setText(getString(R.string.bluetooth_ask_enablement_and_discovery, mTimeout)); + } + + p.mPositiveButtonText = getString(R.string.yes); + p.mPositiveButtonListener = this; + p.mNegativeButtonText = getString(R.string.no); + p.mNegativeButtonListener = this; + + setupAlert(); + } + + public void onClick(DialogInterface dialog, int which) { + int returnCode; + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + int btState = 0; + + try { + // TODO There's a better way. + int retryCount = 30; + do { + btState = mLocalManager.getBluetoothState(); + Thread.sleep(100); + } while (btState == BluetoothAdapter.STATE_TURNING_OFF && --retryCount > 0); + } catch (InterruptedException e) { + // don't care + } + + if (btState == BluetoothAdapter.STATE_TURNING_ON + || btState == BluetoothAdapter.STATE_ON + || mLocalManager.getBluetoothAdapter().enable()) { + returnCode = RequestPermissionActivity.RESULT_BT_STARTING_OR_STARTED; + } else { + returnCode = RequestPermissionActivity.RESULT_ERROR; + } + break; + + case DialogInterface.BUTTON_NEGATIVE: + returnCode = RequestPermissionActivity.RESULT_USER_DENIED; + break; + default: + return; + } + setResult(returnCode); + } + + private boolean parseIntent() { + Intent intent = getIntent(); + if (intent != null && intent.getAction().equals(ACTION_INTERNAL_REQUEST_BT_ON)) { + mEnableOnly = true; + } else if (intent != null + && intent.getAction().equals(ACTION_INTERNAL_REQUEST_BT_ON_AND_DISCOVERABLE)) { + mEnableOnly = false; + // Value used for display purposes. Not range checking. + mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, + BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT); + } else { + setResult(RequestPermissionActivity.RESULT_ERROR); + return true; + } + + mLocalManager = LocalBluetoothManager.getInstance(this); + if (mLocalManager == null) { + Log.e(TAG, "Error: there's a problem starting bluetooth"); + setResult(RequestPermissionActivity.RESULT_ERROR); + return true; + } + + return false; + } + + @Override + public void onBackPressed() { + setResult(RequestPermissionActivity.RESULT_USER_DENIED); + super.onBackPressed(); + } +}