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