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