1/*
2 * Copyright (C) 2011 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
17package com.android.settings.bluetooth;
18
19import android.app.Notification;
20import android.app.NotificationManager;
21import android.app.PendingIntent;
22import android.bluetooth.BluetoothDevice;
23import android.content.BroadcastReceiver;
24import android.content.Context;
25import android.content.Intent;
26import android.os.PowerManager;
27import android.util.Log;
28
29import com.android.settings.R;
30
31/**
32 * BluetoothPermissionRequest is a receiver to receive Bluetooth connection
33 * access request.
34 */
35public final class BluetoothPermissionRequest extends BroadcastReceiver {
36
37    private static final String TAG = "BluetoothPermissionRequest";
38    private static final boolean DEBUG = Utils.V;
39    private static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
40
41    private static final String NOTIFICATION_TAG_PBAP = "Phonebook Access" ;
42    private static final String NOTIFICATION_TAG_MAP = "Message Access";
43
44
45    Context mContext;
46    int mRequestType;
47    BluetoothDevice mDevice;
48    String mReturnPackage = null;
49    String mReturnClass = null;
50
51    @Override
52    public void onReceive(Context context, Intent intent) {
53        mContext = context;
54        String action = intent.getAction();
55
56        if (DEBUG) Log.d(TAG, "onReceive" + action);
57
58        if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
59            // convert broadcast intent into activity intent (same action string)
60            mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
61            mRequestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
62                                                 BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
63            mReturnPackage = intent.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
64            mReturnClass = intent.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
65
66            if (DEBUG) Log.d(TAG, "onReceive request type: " + mRequestType + " return "
67                    + mReturnPackage + "," + mReturnClass);
68
69            // Even if the user has already made the choice, Bluetooth still may not know that if
70            // the user preference data have not been migrated from Settings app's shared
71            // preferences to Bluetooth app's. In that case, Bluetooth app broadcasts an
72            // ACTION_CONNECTION_ACCESS_REQUEST intent to ask to Settings app.
73            //
74            // If that happens, 'checkUserChoice()' here will do migration because it finds or
75            // creates a 'CachedBluetoothDevice' object for the device.
76            //
77            // After migration is done, 'checkUserChoice()' replies to the request by sending an
78            // ACTION_CONNECTION_ACCESS_REPLY intent. And we don't need to start permission activity
79            // dialog or notification.
80            if (checkUserChoice()) {
81                return;
82            }
83
84            Intent connectionAccessIntent = new Intent(action);
85            connectionAccessIntent.setClass(context, BluetoothPermissionActivity.class);
86            // We use the FLAG_ACTIVITY_MULTIPLE_TASK since we can have multiple concurrent access
87            // requests.
88            connectionAccessIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
89                    | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
90            // This is needed to create two pending intents to the same activity. The value is not
91            // used in the activity.
92            connectionAccessIntent.setType(Integer.toString(mRequestType));
93            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
94                                            mRequestType);
95            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
96            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, mReturnPackage);
97            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, mReturnClass);
98
99            String deviceAddress = mDevice != null ? mDevice.getAddress() : null;
100            String title = null;
101            String message = null;
102            PowerManager powerManager =
103                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
104
105            if (powerManager.isScreenOn()
106                    && LocalBluetoothPreferences.shouldShowDialogInForeground(
107                            context, deviceAddress)) {
108                context.startActivity(connectionAccessIntent);
109            } else {
110                // Put up a notification that leads to the dialog
111
112                // Create an intent triggered by clicking on the
113                // "Clear All Notifications" button
114
115                Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
116                deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
117                deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
118                        BluetoothDevice.CONNECTION_ACCESS_NO);
119                deleteIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType);
120                String deviceName = mDevice != null ? mDevice.getAliasName() : null;
121                switch (mRequestType) {
122                    case BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS:
123                        title = context.getString(R.string.bluetooth_phonebook_request);
124                        message = context.getString(R.string.bluetooth_pb_acceptance_dialog_text,
125                                deviceName, deviceName);
126                        break;
127                    case BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS:
128                        title = context.getString(R.string.bluetooth_map_request);
129                        message = context.getString(R.string.bluetooth_map_acceptance_dialog_text,
130                                deviceName, deviceName);
131                        break;
132                    default:
133                        title = context.getString(R.string.bluetooth_connection_permission_request);
134                        message = context.getString(R.string.bluetooth_connection_dialog_text,
135                                deviceName, deviceName);
136                        break;
137                }
138                Notification notification = new Notification.Builder(context)
139                        .setContentTitle(title)
140                        .setTicker(message)
141                        .setContentText(message)
142                        .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
143                        .setAutoCancel(true)
144                        .setPriority(Notification.PRIORITY_MAX)
145                        .setOnlyAlertOnce(false)
146                        .setDefaults(Notification.DEFAULT_ALL)
147                        .setContentIntent(PendingIntent.getActivity(context, 0,
148                                connectionAccessIntent, 0))
149                        .setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent, 0))
150                        .setColor(context.getResources().getColor(
151                                com.android.internal.R.color.system_notification_accent_color))
152                        .build();
153
154                notification.flags |= Notification.FLAG_NO_CLEAR; // Cannot be set with the builder.
155
156                NotificationManager notificationManager =
157                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
158
159                notificationManager.notify(getNotificationTag(mRequestType), NOTIFICATION_ID,
160                        notification);
161            }
162        } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
163            // Remove the notification
164            NotificationManager manager = (NotificationManager) context
165                .getSystemService(Context.NOTIFICATION_SERVICE);
166            mRequestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
167                                        BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
168            manager.cancel(getNotificationTag(mRequestType), NOTIFICATION_ID);
169        }
170    }
171
172    private String getNotificationTag(int requestType) {
173        if(requestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
174            return NOTIFICATION_TAG_PBAP;
175        } else if(mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
176            return NOTIFICATION_TAG_MAP;
177        }
178        return null;
179    }
180
181    /**
182     * @return true user had made a choice, this method replies to the request according
183     *              to user's previous decision
184     *         false user hadnot made any choice on this device
185     */
186    private boolean checkUserChoice() {
187        boolean processed = false;
188
189        // ignore if it is something else than phonebook/message settings it wants us to remember
190        if (mRequestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS
191                && mRequestType != BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
192            if (DEBUG) Log.d(TAG, "checkUserChoice(): Unknown RequestType " + mRequestType);
193            return processed;
194        }
195
196        LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(mContext);
197        CachedBluetoothDeviceManager cachedDeviceManager =
198            bluetoothManager.getCachedDeviceManager();
199        CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
200        if (cachedDevice == null) {
201            cachedDevice = cachedDeviceManager.addDevice(bluetoothManager.getBluetoothAdapter(),
202                bluetoothManager.getProfileManager(), mDevice);
203        }
204
205        String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY;
206
207        if (mRequestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
208            int phonebookPermission = cachedDevice.getPhonebookPermissionChoice();
209
210            if (phonebookPermission == CachedBluetoothDevice.ACCESS_UNKNOWN) {
211                // Leave 'processed' as false.
212            } else if (phonebookPermission == CachedBluetoothDevice.ACCESS_ALLOWED) {
213                sendReplyIntentToReceiver(true);
214                processed = true;
215            } else if (phonebookPermission == CachedBluetoothDevice.ACCESS_REJECTED) {
216                sendReplyIntentToReceiver(false);
217                processed = true;
218            } else {
219                Log.e(TAG, "Bad phonebookPermission: " + phonebookPermission);
220            }
221        } else if (mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
222            int messagePermission = cachedDevice.getMessagePermissionChoice();
223
224            if (messagePermission == CachedBluetoothDevice.ACCESS_UNKNOWN) {
225                // Leave 'processed' as false.
226            } else if (messagePermission == CachedBluetoothDevice.ACCESS_ALLOWED) {
227                sendReplyIntentToReceiver(true);
228                processed = true;
229            } else if (messagePermission == CachedBluetoothDevice.ACCESS_REJECTED) {
230                sendReplyIntentToReceiver(false);
231                processed = true;
232            } else {
233                Log.e(TAG, "Bad messagePermission: " + messagePermission);
234            }
235        }
236        if (DEBUG) Log.d(TAG,"checkUserChoice(): returning " + processed);
237        return processed;
238    }
239
240    private void sendReplyIntentToReceiver(final boolean allowed) {
241        Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
242
243        if (mReturnPackage != null && mReturnClass != null) {
244            intent.setClassName(mReturnPackage, mReturnClass);
245        }
246
247        intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
248                        allowed ? BluetoothDevice.CONNECTION_ACCESS_YES
249                                : BluetoothDevice.CONNECTION_ACCESS_NO);
250        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
251        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType);
252        mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN);
253    }
254}
255