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.bluetooth.BluetoothDevice;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.DialogInterface;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.os.Bundle;
26import android.preference.Preference;
27import android.util.Log;
28import android.view.View;
29import android.widget.CheckBox;
30import android.widget.CompoundButton;
31import android.widget.TextView;
32import android.widget.Button;
33import android.widget.CompoundButton.OnCheckedChangeListener;
34
35import com.android.internal.app.AlertActivity;
36import com.android.internal.app.AlertController;
37
38import com.android.settings.R;
39
40/**
41 * BluetoothPermissionActivity shows a dialog for accepting incoming
42 * profile connection request from untrusted devices.
43 * It is also used to show a dialogue for accepting incoming phonebook
44 * read request. The request could be initiated by PBAP PCE or by HF AT+CPBR.
45 */
46public class BluetoothPermissionActivity extends AlertActivity implements
47        DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener {
48    private static final String TAG = "BluetoothPermissionActivity";
49    private static final boolean DEBUG = Utils.D;
50
51    private View mView;
52    private TextView messageView;
53    private Button mOkButton;
54    private BluetoothDevice mDevice;
55    private String mReturnPackage = null;
56    private String mReturnClass = null;
57
58    private int mRequestType = 0;
59    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
60        @Override
61        public void onReceive(Context context, Intent intent) {
62            String action = intent.getAction();
63            if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL)) {
64                int requestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
65                                               BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
66                if (requestType != mRequestType) return;
67                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
68                if (mDevice.equals(device)) dismissDialog();
69            }
70        }
71    };
72    private boolean mReceiverRegistered = false;
73
74    private void dismissDialog() {
75        this.dismiss();
76    }
77
78    @Override
79    protected void onCreate(Bundle savedInstanceState) {
80        super.onCreate(savedInstanceState);
81
82        Intent i = getIntent();
83        String action = i.getAction();
84        if (!action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST)) {
85            Log.e(TAG, "Error: this activity may be started only with intent "
86                  + "ACTION_CONNECTION_ACCESS_REQUEST");
87            finish();
88            return;
89        }
90
91        mDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
92        mReturnPackage = i.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME);
93        mReturnClass = i.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME);
94        mRequestType = i.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
95                                     BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
96
97        if(DEBUG) Log.i(TAG, "onCreate() Request type: " + mRequestType);
98
99        if (mRequestType == BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION) {
100            showDialog(getString(R.string.bluetooth_connection_permission_request), mRequestType);
101        } else if (mRequestType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS) {
102            showDialog(getString(R.string.bluetooth_phonebook_request), mRequestType);
103        } else if (mRequestType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS) {
104            showDialog(getString(R.string.bluetooth_map_request), mRequestType);
105        }
106        else {
107            Log.e(TAG, "Error: bad request type: " + mRequestType);
108            finish();
109            return;
110        }
111        registerReceiver(mReceiver,
112                         new IntentFilter(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL));
113        mReceiverRegistered = true;
114    }
115
116
117    private void showDialog(String title, int requestType)
118    {
119        final AlertController.AlertParams p = mAlertParams;
120        p.mIconId = android.R.drawable.ic_dialog_info;
121        p.mTitle = title;
122        if(DEBUG) Log.i(TAG, "showDialog() Request type: " + mRequestType + " this: " + this);
123        switch(requestType)
124        {
125        case BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION:
126            p.mView = createConnectionDialogView();
127            break;
128        case BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS:
129            p.mView = createPhonebookDialogView();
130            break;
131        case BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS:
132            p.mView = createMapDialogView();
133            break;
134        }
135        p.mPositiveButtonText = getString(R.string.yes);
136        p.mPositiveButtonListener = this;
137        p.mNegativeButtonText = getString(R.string.no);
138        p.mNegativeButtonListener = this;
139        mOkButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
140        setupAlert();
141
142    }
143    @Override
144    public void onBackPressed() {
145        /*we need an answer so ignore back button presses during auth */
146        if(DEBUG) Log.i(TAG, "Back button pressed! ignoring");
147        return;
148    }
149    private String createRemoteName()
150    {
151        String mRemoteName = mDevice != null ? mDevice.getAliasName() : null;
152
153        if (mRemoteName == null) mRemoteName = getString(R.string.unknown);
154        return mRemoteName;
155    }
156
157    // TODO(edjee): createConnectionDialogView, createPhonebookDialogView and createMapDialogView
158    // are similar. Refactor them into one method.
159    // Also, the string resources bluetooth_remember_choice and bluetooth_pb_remember_choice should
160    // be removed.
161    private View createConnectionDialogView() {
162        String mRemoteName = createRemoteName();
163        mView = getLayoutInflater().inflate(R.layout.bluetooth_access, null);
164        messageView = (TextView)mView.findViewById(R.id.message);
165        messageView.setText(getString(R.string.bluetooth_connection_dialog_text,
166                mRemoteName));
167        return mView;
168    }
169
170    private View createPhonebookDialogView() {
171        String mRemoteName = createRemoteName();
172        mView = getLayoutInflater().inflate(R.layout.bluetooth_access, null);
173        messageView = (TextView)mView.findViewById(R.id.message);
174        messageView.setText(getString(R.string.bluetooth_pb_acceptance_dialog_text,
175                mRemoteName, mRemoteName));
176        return mView;
177    }
178
179    private View createMapDialogView() {
180        String mRemoteName = createRemoteName();
181        mView = getLayoutInflater().inflate(R.layout.bluetooth_access, null);
182        messageView = (TextView)mView.findViewById(R.id.message);
183        messageView.setText(getString(R.string.bluetooth_map_acceptance_dialog_text,
184                mRemoteName, mRemoteName));
185        return mView;
186    }
187
188    private void onPositive() {
189        if (DEBUG) Log.d(TAG, "onPositive");
190        savePermissionChoice(mRequestType, CachedBluetoothDevice.ACCESS_ALLOWED);
191        // TODO(edjee): Now that we always save the user's choice,
192        // we can get rid of BluetoothDevice#EXTRA_ALWAYS_ALLOWED.
193        sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, true,
194                             BluetoothDevice.EXTRA_ALWAYS_ALLOWED, true);
195        finish();
196    }
197
198    private void onNegative() {
199        if (DEBUG) Log.d(TAG, "onNegative");
200        savePermissionChoice(mRequestType, CachedBluetoothDevice.ACCESS_REJECTED);
201        sendIntentToReceiver(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY, false,
202                             null, false // dummy value, no effect since last param is null
203                             );
204        finish();
205    }
206
207    private void sendIntentToReceiver(final String intentName, final boolean allowed,
208                                      final String extraName, final boolean extraValue) {
209        Intent intent = new Intent(intentName);
210
211        if (mReturnPackage != null && mReturnClass != null) {
212            intent.setClassName(mReturnPackage, mReturnClass);
213        }
214        if(DEBUG) Log.i(TAG, "sendIntentToReceiver() Request type: " + mRequestType +
215                " mReturnPackage" + mReturnPackage + " mReturnClass" + mReturnClass);
216
217        intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
218                        allowed ? BluetoothDevice.CONNECTION_ACCESS_YES :
219                                  BluetoothDevice.CONNECTION_ACCESS_NO);
220
221        if (extraName != null) {
222            intent.putExtra(extraName, extraValue);
223        }
224        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
225        intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType);
226        sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN);
227    }
228
229    public void onClick(DialogInterface dialog, int which) {
230        switch (which) {
231            case DialogInterface.BUTTON_POSITIVE:
232                onPositive();
233                break;
234
235            case DialogInterface.BUTTON_NEGATIVE:
236                onNegative();
237                break;
238            default:
239                break;
240        }
241    }
242
243    @Override
244    protected void onDestroy() {
245        super.onDestroy();
246        if (mReceiverRegistered) {
247            unregisterReceiver(mReceiver);
248            mReceiverRegistered = false;
249        }
250    }
251
252    public boolean onPreferenceChange(Preference preference, Object newValue) {
253        return true;
254    }
255
256    private void savePermissionChoice(int permissionType, int permissionChoice) {
257        LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(this);
258        CachedBluetoothDeviceManager cachedDeviceManager =
259            bluetoothManager.getCachedDeviceManager();
260        CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice);
261        if (DEBUG) Log.d(TAG, "savePermissionChoice permissionType: " + permissionType);
262        if (cachedDevice == null ) {
263            cachedDevice = cachedDeviceManager.addDevice(bluetoothManager.getBluetoothAdapter(),
264                                                         bluetoothManager.getProfileManager(),
265                                                         mDevice);
266        }
267        if(permissionType == BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS){
268            cachedDevice.setPhonebookPermissionChoice(permissionChoice);
269        }else if (permissionType == BluetoothDevice.REQUEST_TYPE_MESSAGE_ACCESS){
270            cachedDevice.setMessagePermissionChoice(permissionChoice);
271        }
272    }
273
274}
275