• 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.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 import static android.view.WindowManager.LayoutParams.PRIVATE_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 {
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 = "RequestPermissionActivity";
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 LocalBluetoothAdapter mLocalAdapter;
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().addPrivateFlags(PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
78 
79         setResult(Activity.RESULT_CANCELED);
80 
81         // Note: initializes mLocalAdapter and returns true on error
82         if (parseIntent()) {
83             finish();
84             return;
85         }
86 
87         int btState = mLocalAdapter.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         mDialog = builder.create();
196         mDialog.show();
197     }
198 
199     @Override
onActivityResult(int requestCode, int resultCode, Intent data)200     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
201         if (resultCode != Activity.RESULT_OK) {
202             cancelAndFinish();
203             return;
204         }
205 
206         switch (mRequest) {
207             case REQUEST_ENABLE:
208             case REQUEST_ENABLE_DISCOVERABLE: {
209                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
210                     proceedAndFinish();
211                 } else {
212                     // If BT is not up yet, show "Turning on Bluetooth..."
213                     mReceiver = new StateChangeReceiver();
214                     registerReceiver(mReceiver, new IntentFilter(
215                             BluetoothAdapter.ACTION_STATE_CHANGED));
216                     createDialog();
217                 }
218             } break;
219 
220             case REQUEST_DISABLE: {
221                 if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_OFF) {
222                     proceedAndFinish();
223                 } else {
224                     // If BT is not up yet, show "Turning off Bluetooth..."
225                     mReceiver = new StateChangeReceiver();
226                     registerReceiver(mReceiver, new IntentFilter(
227                             BluetoothAdapter.ACTION_STATE_CHANGED));
228                     createDialog();
229                 }
230             } break;
231 
232             default: {
233                 cancelAndFinish();
234             } break;
235         }
236     }
237 
onClick(DialogInterface dialog, int which)238     public void onClick(DialogInterface dialog, int which) {
239         switch (which) {
240             case DialogInterface.BUTTON_POSITIVE:
241                 proceedAndFinish();
242                 break;
243 
244             case DialogInterface.BUTTON_NEGATIVE:
245                 setResult(RESULT_CANCELED);
246                 finish();
247                 break;
248         }
249     }
250 
proceedAndFinish()251     private void proceedAndFinish() {
252         int returnCode;
253 
254         if (mRequest == REQUEST_ENABLE || mRequest == REQUEST_DISABLE) {
255             // BT toggled. Done
256             returnCode = RESULT_OK;
257         } else if (mLocalAdapter.setScanMode(
258                 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, mTimeout)) {
259             // If already in discoverable mode, this will extend the timeout.
260             long endTime = System.currentTimeMillis() + (long) mTimeout * 1000;
261             LocalBluetoothPreferences.persistDiscoverableEndTimestamp(
262                     this, endTime);
263             if (0 < mTimeout) {
264                BluetoothDiscoverableTimeoutReceiver.setDiscoverableAlarm(this, endTime);
265             }
266             returnCode = mTimeout;
267             // Activity.RESULT_FIRST_USER should be 1
268             if (returnCode < RESULT_FIRST_USER) {
269                 returnCode = RESULT_FIRST_USER;
270             }
271         } else {
272             returnCode = RESULT_CANCELED;
273         }
274 
275         if (mDialog != null) {
276             mDialog.dismiss();
277         }
278 
279         setResult(returnCode);
280         finish();
281     }
282 
cancelAndFinish()283     private void cancelAndFinish() {
284         setResult(Activity.RESULT_CANCELED);
285         finish();
286     }
287 
288     /**
289      * Parse the received Intent and initialize mLocalBluetoothAdapter.
290      * @return true if an error occurred; false otherwise
291      */
parseIntent()292     private boolean parseIntent() {
293         Intent intent = getIntent();
294         if (intent == null) {
295             return true;
296         }
297         if (intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_ENABLE)) {
298             mRequest = REQUEST_ENABLE;
299         } else if (intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_DISABLE)) {
300             mRequest = REQUEST_DISABLE;
301         } else if (intent.getAction().equals(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE)) {
302             mRequest = REQUEST_ENABLE_DISCOVERABLE;
303             mTimeout = intent.getIntExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,
304                     BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT);
305 
306             Log.d(TAG, "Setting Bluetooth Discoverable Timeout = " + mTimeout);
307 
308             if (mTimeout < 1 || mTimeout > MAX_DISCOVERABLE_TIMEOUT) {
309                 mTimeout = BluetoothDiscoverableEnabler.DEFAULT_DISCOVERABLE_TIMEOUT;
310             }
311         } else {
312             Log.e(TAG, "Error: this activity may be started only with intent "
313                     + BluetoothAdapter.ACTION_REQUEST_ENABLE + " or "
314                     + BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
315             setResult(RESULT_CANCELED);
316             return true;
317         }
318 
319         LocalBluetoothManager manager = Utils.getLocalBtManager(this);
320         if (manager == null) {
321             Log.e(TAG, "Error: there's a problem starting Bluetooth");
322             setResult(RESULT_CANCELED);
323             return true;
324         }
325 
326         String packageName = getCallingPackage();
327         if (TextUtils.isEmpty(packageName)) {
328             packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
329         }
330         if (!TextUtils.isEmpty(packageName)) {
331             try {
332                 ApplicationInfo applicationInfo = getPackageManager().getApplicationInfo(
333                         packageName, 0);
334                 mAppLabel = applicationInfo.loadSafeLabel(getPackageManager());
335             } catch (PackageManager.NameNotFoundException e) {
336                 Log.e(TAG, "Couldn't find app with package name " + packageName);
337                 setResult(RESULT_CANCELED);
338                 return true;
339             }
340         }
341 
342         mLocalAdapter = manager.getBluetoothAdapter();
343 
344         return false;
345     }
346 
347     @Override
onDestroy()348     protected void onDestroy() {
349         super.onDestroy();
350         if (mReceiver != null) {
351             unregisterReceiver(mReceiver);
352             mReceiver = null;
353         }
354     }
355 
356     @Override
onBackPressed()357     public void onBackPressed() {
358         setResult(RESULT_CANCELED);
359         super.onBackPressed();
360     }
361 
362     private final class StateChangeReceiver extends BroadcastReceiver {
363         private static final long TOGGLE_TIMEOUT_MILLIS = 10000; // 10 sec
364 
StateChangeReceiver()365         public StateChangeReceiver() {
366             getWindow().getDecorView().postDelayed(() -> {
367                 if (!isFinishing() && !isDestroyed()) {
368                     cancelAndFinish();
369                 }
370             }, TOGGLE_TIMEOUT_MILLIS);
371         }
372 
onReceive(Context context, Intent intent)373         public void onReceive(Context context, Intent intent) {
374             if (intent == null) {
375                 return;
376             }
377             final int currentState = intent.getIntExtra(
378                     BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
379             switch (mRequest) {
380                 case REQUEST_ENABLE:
381                 case REQUEST_ENABLE_DISCOVERABLE: {
382                     if (currentState == BluetoothAdapter.STATE_ON) {
383                         proceedAndFinish();
384                     }
385                 } break;
386 
387                 case REQUEST_DISABLE: {
388                     if (currentState == BluetoothAdapter.STATE_OFF) {
389                         proceedAndFinish();
390                     }
391                 } break;
392             }
393         }
394     }
395 }
396