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    public static final int NOTIFICATION_ID = android.R.drawable.stat_sys_data_bluetooth;
40
41    Context mContext;
42    int mRequestType;
43    BluetoothDevice mDevice;
44    String mReturnPackage = null;
45    String mReturnClass = null;
46
47    @Override
48    public void onReceive(Context context, Intent intent) {
49        mContext = context;
50        String action = intent.getAction();
51
52        if (DEBUG) Log.d(TAG, "onReceive");
53
54        if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
55            // convert broadcast intent into activity intent (same action string)
56            mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
57            mRequestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
58                                                 BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION);
59            mReturnPackage = intent.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
60            mReturnClass = intent.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
61
62            Intent connectionAccessIntent = new Intent(action);
63            connectionAccessIntent.setClass(context, BluetoothPermissionActivity.class);
64            connectionAccessIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
65            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
66                                            mRequestType);
67            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
68            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, mReturnPackage);
69            connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, mReturnClass);
70
71            // Check if user had made decisions on accepting or rejecting the phonebook access
72            // request. If there is, reply the request and return, no need to start permission
73            // activity dialog or notification.
74            if (checkUserChoice()) {
75                return;
76            }
77
78            String deviceAddress = mDevice != null ? mDevice.getAddress() : null;
79
80            PowerManager powerManager =
81                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
82
83            if (powerManager.isScreenOn() &&
84                LocalBluetoothPreferences.shouldShowDialogInForeground(context, deviceAddress) ) {
85                context.startActivity(connectionAccessIntent);
86            } else {
87                // Put up a notification that leads to the dialog
88
89                // Create an intent triggered by clicking on the
90                // "Clear All Notifications" button
91
92                Intent deleteIntent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
93                deleteIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
94                deleteIntent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
95                        BluetoothDevice.CONNECTION_ACCESS_NO);
96
97                Notification notification = new Notification(
98                    android.R.drawable.stat_sys_data_bluetooth,
99                    context.getString(R.string.bluetooth_connection_permission_request),
100                    System.currentTimeMillis());
101                String deviceName = mDevice != null ? mDevice.getAliasName() : null;
102                notification.setLatestEventInfo(context,
103                    context.getString(R.string.bluetooth_connection_permission_request),
104                    context.getString(R.string.bluetooth_connection_notif_message, deviceName),
105                    PendingIntent.getActivity(context, 0, connectionAccessIntent, 0));
106                notification.flags = Notification.FLAG_AUTO_CANCEL |
107                                     Notification.FLAG_ONLY_ALERT_ONCE;
108                notification.defaults = Notification.DEFAULT_SOUND;
109                notification.deleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
110
111                NotificationManager notificationManager =
112                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
113                notificationManager.notify(NOTIFICATION_ID, notification);
114            }
115        } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
116            // Remove the notification
117            NotificationManager manager = (NotificationManager) context
118                .getSystemService(Context.NOTIFICATION_SERVICE);
119            manager.cancel(NOTIFICATION_ID);
120        }
121    }
122
123    /**
124     * @return true user had made a choice, this method replies to the request according
125     *              to user's previous decision
126     *         false user hadnot made any choice on this device
127     */
128    private boolean checkUserChoice() {
129        boolean processed = false;
130
131        // we only remember PHONEBOOK permission
132        if (mRequestType != BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
133            return processed;
134        }
135
136        LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(mContext);
137        CachedBluetoothDeviceManager cachedDeviceManager =
138            bluetoothManager.getCachedDeviceManager();
139        CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
140
141        if (cachedDevice == null) {
142            cachedDevice = cachedDeviceManager.addDevice(bluetoothManager.getBluetoothAdapter(),
143                bluetoothManager.getProfileManager(), mDevice);
144        }
145
146        int phonebookPermission = cachedDevice.getPhonebookPermissionChoice();
147
148        if (phonebookPermission == CachedBluetoothDevice.PHONEBOOK_ACCESS_UNKNOWN) {
149            return processed;
150        }
151
152        String intentName = BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY;
153        if (phonebookPermission == CachedBluetoothDevice.PHONEBOOK_ACCESS_ALLOWED) {
154            sendIntentToReceiver(intentName, true, BluetoothDevice.EXTRA_ALWAYS_ALLOWED, true);
155            processed = true;
156        } else if (phonebookPermission == CachedBluetoothDevice.PHONEBOOK_ACCESS_REJECTED) {
157            sendIntentToReceiver(intentName, false,
158                                 null, false // dummy value, no effect since previous param is null
159                                 );
160            processed = true;
161        } else {
162            Log.e(TAG, "Bad phonebookPermission: " + phonebookPermission);
163        }
164        return processed;
165    }
166
167    private void sendIntentToReceiver(final String intentName, final boolean allowed,
168                                      final String extraName, final boolean extraValue) {
169        Intent intent = new Intent(intentName);
170
171        if (mReturnPackage != null && mReturnClass != null) {
172            intent.setClassName(mReturnPackage, mReturnClass);
173        }
174
175        intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
176                        allowed ? BluetoothDevice.CONNECTION_ACCESS_YES :
177                        BluetoothDevice.CONNECTION_ACCESS_NO);
178
179        if (extraName != null) {
180            intent.putExtra(extraName, extraValue);
181        }
182        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
183        mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN);
184    }
185}
186