Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.bluetooth;
     18 
     19 import android.annotation.NonNull;
     20 import android.app.Activity;
     21 import android.app.AlertDialog;
     22 import android.bluetooth.BluetoothAdapter;
     23 import android.bluetooth.BluetoothDevice;
     24 import android.content.BroadcastReceiver;
     25 import android.content.Context;
     26 import android.content.DialogInterface;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.content.pm.ApplicationInfo;
     30 import android.content.pm.PackageManager;
     31 import android.os.Bundle;
     32 import android.text.TextUtils;
     33 import android.util.Log;
     34 
     35 import com.android.settings.R;
     36 import com.android.settingslib.bluetooth.BluetoothDiscoverableTimeoutReceiver;
     37 import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
     38 import com.android.settingslib.bluetooth.LocalBluetoothManager;
     39 
     40 /**
     41  * RequestPermissionActivity asks the user whether to enable discovery. This is
     42  * usually started by an application wanted to start bluetooth and or discovery
     43  */
     44 public class RequestPermissionActivity extends Activity implements
     45         DialogInterface.OnClickListener {
     46     // Command line to test this
     47     // adb shell am start -a android.bluetooth.adapter.action.REQUEST_ENABLE
     48     // adb shell am start -a android.bluetooth.adapter.action.REQUEST_DISCOVERABLE
     49     // adb shell am start -a android.bluetooth.adapter.action.REQUEST_DISABLE
     50 
     51     private static final String TAG = "RequestPermissionActivity";
     52 
     53     private static final int MAX_DISCOVERABLE_TIMEOUT = 3600; // 1 hr
     54 
     55     static final int REQUEST_ENABLE = 1;
     56     static final int REQUEST_ENABLE_DISCOVERABLE = 2;
     57     static final int REQUEST_DISABLE = 3;
     58 
     59     private LocalBluetoothAdapter mLocalAdapter;
     60 
     61     private int mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT;
     62 
     63     private int mRequest;
     64 
     65     private AlertDialog mDialog;
     66 
     67     private BroadcastReceiver mReceiver;
     68 
     69     private @NonNull CharSequence mAppLabel;
     70 
     71     @Override
     72     protected void onCreate(Bundle savedInstanceState) {
     73         super.onCreate(savedInstanceState);
     74 
     75         setResult(Activity.RESULT_CANCELED);
     76 
     77         // Note: initializes mLocalAdapter and returns true on error
     78         if (parseIntent()) {
     79             finish();
     80             return;
     81         }
     82 
     83         int btState = mLocalAdapter.getState();
     84 
     85         if (mRequest == REQUEST_DISABLE) {
     86             switch (btState) {
     87                 case BluetoothAdapter.STATE_OFF:
     88                 case BluetoothAdapter.STATE_TURNING_OFF: {
     89                     proceedAndFinish();
     90                 } break;
     91 
     92                 case BluetoothAdapter.STATE_ON:
     93                 case BluetoothAdapter.STATE_TURNING_ON: {
     94                     Intent intent = new Intent(this, RequestPermissionHelperActivity.class);
     95                     intent.putExtra(RequestPermissionHelperActivity.EXTRA_APP_LABEL, mAppLabel);
     96                     intent.setAction(RequestPermissionHelperActivity
     97                                 .ACTION_INTERNAL_REQUEST_BT_OFF);
     98 
     99                     startActivityForResult(intent, 0);
    100                 } break;
    101 
    102                 default: {
    103                     Log.e(TAG, "Unknown adapter state: " + btState);
    104                     cancelAndFinish();
    105                 } break;
    106             }
    107         } else {
    108             switch (btState) {
    109                 case BluetoothAdapter.STATE_OFF:
    110                 case BluetoothAdapter.STATE_TURNING_OFF:
    111                 case BluetoothAdapter.STATE_TURNING_ON: {
    112                     /*
    113                      * Strictly speaking STATE_TURNING_ON belong with STATE_ON;
    114                      * however, BT may not be ready when the user clicks yes and we
    115                      * would fail to turn on discovery mode. By kicking this to the
    116                      * RequestPermissionHelperActivity, this class will handle that
    117                      * case via the broadcast receiver.
    118                      */
    119 
    120                     /*
    121                      * Start the helper activity to:
    122                      * 1) ask the user about enabling bt AND discovery
    123                      * 2) enable BT upon confirmation
    124                      */
    125                     Intent intent = new Intent(this, RequestPermissionHelperActivity.class);
    126                     intent.setAction(RequestPermissionHelperActivity.ACTION_INTERNAL_REQUEST_BT_ON);
    127                     intent.putExtra(RequestPermissionHelperActivity.EXTRA_APP_LABEL, mAppLabel);
    128                     if (mRequest == REQUEST_ENABLE_DISCOVERABLE) {
    129                         intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, mTimeout);
    130                     }
    131                     startActivityForResult(intent, 0);
    132                 } break;
    133 
    134                 case BluetoothAdapter.STATE_ON: {
    135                     if (mRequest == REQUEST_ENABLE) {
    136                         // Nothing to do. Already enabled.
    137                         proceedAndFinish();
    138                     } else {
    139                         // Ask the user about enabling discovery mode
    140                         createDialog();
    141                     }
    142                 } break;
    143 
    144                 default: {
    145                     Log.e(TAG, "Unknown adapter state: " + btState);
    146                     cancelAndFinish();
    147                 } break;
    148             }
    149         }
    150     }
    151 
    152     private void createDialog() {
    153         if (getResources().getBoolean(R.bool.auto_confirm_bluetooth_activation_dialog)) {
    154             onClick(null, DialogInterface.BUTTON_POSITIVE);
    155             return;
    156         }
    157 
    158         AlertDialog.Builder builder = new AlertDialog.Builder(this);
    159 
    160         // Non-null receiver means we are toggling
    161         if (mReceiver != null) {
    162             switch (mRequest) {
    163                 case REQUEST_ENABLE:
    164                 case REQUEST_ENABLE_DISCOVERABLE: {
    165                     builder.setMessage(getString(R.string.bluetooth_turning_on));
    166                 } break;
    167 
    168                 default: {
    169                     builder.setMessage(getString(R.string.bluetooth_turning_off));
    170                 } break;
    171             }
    172             builder.setCancelable(false);
    173         } else {
    174             // Ask the user whether to turn on discovery mode or not
    175             // For lasting discoverable mode there is a different message
    176             if (mTimeout == BluetoothDiscoverableEnabler.DISCOVERABLE_TIMEOUT_NEVER) {
    177                 CharSequence message = mAppLabel != null
    178                         ? getString(R.string.bluetooth_ask_lasting_discovery, mAppLabel)
    179                         : getString(R.string.bluetooth_ask_lasting_discovery_no_name);
    180                 builder.setMessage(message);
    181             } else {
    182                 CharSequence message = mAppLabel != null
    183                         ? getString(R.string.bluetooth_ask_discovery, mAppLabel, mTimeout)
    184                         : getString(R.string.bluetooth_ask_discovery_no_name, mTimeout);
    185                 builder.setMessage(message);
    186             }
    187             builder.setPositiveButton(getString(R.string.allow), this);
    188             builder.setNegativeButton(getString(R.string.deny), this);
    189         }
    190 
    191         mDialog = builder.create();
    192         mDialog.show();
    193     }
    194 
    195     @Override
    196     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    197         if (resultCode != Activity.RESULT_OK) {
    198             cancelAndFinish();
    199             return;
    200         }
    201 
    202         switch (mRequest) {
    203             case REQUEST_ENABLE:
    204             case REQUEST_ENABLE_DISCOVERABLE: {
    205                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
    206                     proceedAndFinish();
    207                 } else {
    208                     // If BT is not up yet, show "Turning on Bluetooth..."
    209                     mReceiver = new StateChangeReceiver();
    210                     registerReceiver(mReceiver, new IntentFilter(
    211                             BluetoothAdapter.ACTION_STATE_CHANGED));
    212                     createDialog();
    213                 }
    214             } break;
    215 
    216             case REQUEST_DISABLE: {
    217                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_OFF) {
    218                     proceedAndFinish();
    219                 } else {
    220                     // If BT is not up yet, show "Turning off Bluetooth..."
    221                     mReceiver = new StateChangeReceiver();
    222                     registerReceiver(mReceiver, new IntentFilter(
    223                             BluetoothAdapter.ACTION_STATE_CHANGED));
    224                     createDialog();
    225                 }
    226             } break;
    227 
    228             default: {
    229                 cancelAndFinish();
    230             } break;
    231         }
    232     }
    233 
    234     public void onClick(DialogInterface dialog, int which) {
    235         switch (which) {
    236             case DialogInterface.BUTTON_POSITIVE:
    237                 proceedAndFinish();
    238                 break;
    239 
    240             case DialogInterface.BUTTON_NEGATIVE:
    241                 setResult(RESULT_CANCELED);
    242                 finish();
    243                 break;
    244         }
    245     }
    246 
    247     private void proceedAndFinish() {
    248         int returnCode;
    249 
    250         if (mRequest == REQUEST_ENABLE || mRequest == REQUEST_DISABLE) {
    251             // BT toggled. Done
    252             returnCode = RESULT_OK;
    253         } else if (mLocalAdapter.setScanMode(
    254                 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout)) {
    255             // If already in discoverable mode, this will extend the timeout.
    256             long endTime = System.currentTimeMillis() + (long) mTimeout * 1000;
    257             LocalBluetoothPreferences.persistDiscoverableEndTimestamp(
    258                     this, endTime);
    259             if (0 < mTimeout) {
    260                BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(this, endTime);
    261             }
    262             returnCode = mTimeout;
    263             // Activity.RESULT_FIRST_USER should be 1
    264             if (returnCode < RESULT_FIRST_USER) {
    265                 returnCode = RESULT_FIRST_USER;
    266             }
    267         } else {
    268             returnCode = RESULT_CANCELED;
    269         }
    270 
    271         if (mDialog != null) {
    272             mDialog.dismiss();
    273         }
    274 
    275         setResult(returnCode);
    276         finish();
    277     }
    278 
    279     private void cancelAndFinish() {
    280         setResult(Activity.RESULT_CANCELED);
    281         finish();
    282     }
    283 
    284     /**
    285      * Parse the received Intent and initialize mLocalBluetoothAdapter.
    286      * @return true if an error occurred; false otherwise
    287      */
    288     private boolean parseIntent() {
    289         Intent intent = getIntent();
    290         if (intent == null) {
    291             return true;
    292         }
    293         if (intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
    294             mRequest = REQUEST_ENABLE;
    295         } else if (intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_DISABLE)) {
    296             mRequest = REQUEST_DISABLE;
    297         } else if (intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)) {
    298             mRequest = REQUEST_ENABLE_DISCOVERABLE;
    299             mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,
    300                     BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT);
    301 
    302             Log.d(TAG, "Setting Bluetooth Discoverable Timeout = " + mTimeout);
    303 
    304             if (mTimeout < 1 || mTimeout > MAX_DISCOVERABLE_TIMEOUT) {
    305                 mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT;
    306             }
    307         } else {
    308             Log.e(TAG, "Error: this activity may be started only with intent "
    309                     + BluetoothAdapter.ACTION_REQUEST_ENABLE + " or "
    310                     + BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
    311             setResult(RESULT_CANCELED);
    312             return true;
    313         }
    314 
    315         LocalBluetoothManager manager = Utils.getLocalBtManager(this);
    316         if (manager == null) {
    317             Log.e(TAG, "Error: there's a problem starting Bluetooth");
    318             setResult(RESULT_CANCELED);
    319             return true;
    320         }
    321 
    322         String packageName = getCallingPackage();
    323         if (TextUtils.isEmpty(packageName)) {
    324             packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
    325         }
    326         if (!TextUtils.isEmpty(packageName)) {
    327             try {
    328                 ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
    329                         packageName, 0);
    330                 mAppLabel = applicationInfo.loadSafeLabel(getPackageManager());
    331             } catch (PackageManager.NameNotFoundException e) {
    332                 Log.e(TAG, "Couldn't find app with package name " + packageName);
    333                 setResult(RESULT_CANCELED);
    334                 return true;
    335             }
    336         }
    337 
    338         mLocalAdapter = manager.getBluetoothAdapter();
    339 
    340         return false;
    341     }
    342 
    343     @Override
    344     protected void onDestroy() {
    345         super.onDestroy();
    346         if (mReceiver != null) {
    347             unregisterReceiver(mReceiver);
    348             mReceiver = null;
    349         }
    350     }
    351 
    352     @Override
    353     public void onBackPressed() {
    354         setResult(RESULT_CANCELED);
    355         super.onBackPressed();
    356     }
    357 
    358     private final class StateChangeReceiver extends BroadcastReceiver {
    359         private static final long TOGGLE_TIMEOUT_MILLIS = 10000; // 10 sec
    360 
    361         public StateChangeReceiver() {
    362             getWindow().getDecorView().postDelayed(() -> {
    363                 if (!isFinishing() && !isDestroyed()) {
    364                     cancelAndFinish();
    365                 }
    366             }, TOGGLE_TIMEOUT_MILLIS);
    367         }
    368 
    369         public void onReceive(Context context, Intent intent) {
    370             if (intent == null) {
    371                 return;
    372             }
    373             final int currentState = intent.getIntExtra(
    374                     BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
    375             switch (mRequest) {
    376                 case REQUEST_ENABLE:
    377                 case REQUEST_ENABLE_DISCOVERABLE: {
    378                     if (currentState == BluetoothAdapter.STATE_ON) {
    379                         proceedAndFinish();
    380                     }
    381                 } break;
    382 
    383                 case REQUEST_DISABLE: {
    384                     if (currentState == BluetoothAdapter.STATE_OFF) {
    385                         proceedAndFinish();
    386                     }
    387                 } break;
    388             }
    389         }
    390     }
    391 }
    392