Home | History | Annotate | Download | only in accessories
      1 /*
      2  * Copyright (C) 2014 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.tv.settings.accessories;
     18 
     19 import android.view.WindowManager;
     20 import com.android.tv.settings.R;
     21 import com.android.tv.settings.dialog.old.Action;
     22 import com.android.tv.settings.dialog.old.ActionAdapter;
     23 import com.android.tv.settings.dialog.old.ActionFragment;
     24 import com.android.tv.settings.dialog.old.DialogActivity;
     25 
     26 import android.app.Fragment;
     27 import android.bluetooth.BluetoothDevice;
     28 import android.content.BroadcastReceiver;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.IntentFilter;
     32 import android.graphics.drawable.ColorDrawable;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.Message;
     36 import android.text.Html;
     37 import android.text.InputFilter;
     38 import android.text.InputType;
     39 import android.text.InputFilter.LengthFilter;
     40 import android.util.Log;
     41 import android.view.KeyEvent;
     42 import android.view.LayoutInflater;
     43 import android.view.View;
     44 import android.view.ViewGroup;
     45 import android.widget.EditText;
     46 import android.widget.RelativeLayout;
     47 import android.widget.TextView;
     48 
     49 import com.android.tv.settings.util.AccessibilityHelper;
     50 
     51 import java.util.ArrayList;
     52 import java.util.Locale;
     53 
     54 /**
     55  * BluetoothPairingDialog asks the user to enter a PIN / Passkey / simple
     56  * confirmation for pairing with a remote Bluetooth device.
     57  */
     58 public class BluetoothPairingDialog extends DialogActivity {
     59 
     60     private static final String KEY_PAIR = "action_pair";
     61     private static final String KEY_CANCEL = "action_cancel";
     62 
     63     private static final String TAG = "aah.BluetoothPairingDialog";
     64     private static final boolean DEBUG = false;
     65 
     66     private static final int BLUETOOTH_PIN_MAX_LENGTH = 16;
     67     private static final int BLUETOOTH_PASSKEY_MAX_LENGTH = 6;
     68 
     69     private BluetoothDevice mDevice;
     70     private int mType;
     71     private String mPairingKey;
     72 
     73     private ActionFragment mActionFragment;
     74     private Fragment mContentFragment;
     75     private ArrayList<Action> mActions;
     76 
     77     private RelativeLayout mTopLayout;
     78     protected ColorDrawable mBgDrawable = new ColorDrawable();
     79     private TextView mTitleText;
     80     private TextView mInstructionText;
     81     private EditText mTextInput;
     82 
     83 
     84     /**
     85      * Dismiss the dialog if the bond state changes to bonded or none, or if
     86      * pairing was canceled for {@link #mDevice}.
     87      */
     88     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
     89         @Override
     90         public void onReceive(Context context, Intent intent) {
     91             String action = intent.getAction();
     92             if (DEBUG) {
     93                 Log.d(TAG, "onReceive. Broadcast Intent = " + intent.toString());
     94             }
     95             if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
     96                 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
     97                         BluetoothDevice.ERROR);
     98                 if (bondState == BluetoothDevice.BOND_BONDED ||
     99                         bondState == BluetoothDevice.BOND_NONE) {
    100                     dismiss();
    101                 }
    102             } else if (BluetoothDevice.ACTION_PAIRING_CANCEL.equals(action)) {
    103                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    104                 if (device == null || device.equals(mDevice)) {
    105                     dismiss();
    106                 }
    107             }
    108         }
    109     };
    110 
    111     @Override
    112     protected void onCreate(Bundle savedInstanceState) {
    113         super.onCreate(savedInstanceState);
    114 
    115         Intent intent = getIntent();
    116         if (!BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) {
    117             Log.e(TAG, "Error: this activity may be started only with intent " +
    118                     BluetoothDevice.ACTION_PAIRING_REQUEST);
    119             finish();
    120             return;
    121         }
    122 
    123         mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    124         mType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
    125 
    126         if (DEBUG) {
    127             Log.d(TAG, "Requested pairing Type = " + mType + " , Device = " + mDevice);
    128         }
    129 
    130         mActions = new ArrayList<Action>();
    131 
    132         switch (mType) {
    133             case BluetoothDevice.PAIRING_VARIANT_PIN:
    134             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    135                 createUserEntryDialog();
    136                 break;
    137 
    138             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    139                 int passkey =
    140                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
    141                 if (passkey == BluetoothDevice.ERROR) {
    142                     Log.e(TAG, "Invalid Confirmation Passkey received, not showing any dialog");
    143                     finish();
    144                     return;
    145                 }
    146                 mPairingKey = String.format(Locale.US, "%06d", passkey);
    147                 createConfirmationDialog();
    148                 break;
    149 
    150             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    151             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    152                 createConfirmationDialog();
    153                 break;
    154 
    155             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    156             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    157                 int pairingKey =
    158                     intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR);
    159                 if (pairingKey == BluetoothDevice.ERROR) {
    160                     Log.e(TAG,
    161                             "Invalid Confirmation Passkey or PIN received, not showing any dialog");
    162                     finish();
    163                     return;
    164                 }
    165                 if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
    166                     mPairingKey = String.format("%06d", pairingKey);
    167                 } else {
    168                     mPairingKey = String.format("%04d", pairingKey);
    169                 }
    170                 createConfirmationDialog();
    171                 break;
    172 
    173             default:
    174                 Log.e(TAG, "Incorrect pairing type received, not showing any dialog");
    175                 finish();
    176                 return;
    177         }
    178 
    179         ViewGroup contentView = (ViewGroup) findViewById(android.R.id.content);
    180         mTopLayout = (RelativeLayout) contentView.getChildAt(0);
    181 
    182         // Fade out the old activity, and fade in the new activity.
    183         overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
    184 
    185         // Set the activity background
    186         int bgColor = getResources().getColor(R.color.dialog_activity_background);
    187         mBgDrawable.setColor(bgColor);
    188         mBgDrawable.setAlpha(255);
    189         mTopLayout.setBackground(mBgDrawable);
    190 
    191         // Make sure pairing wakes up day dream
    192         getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
    193                 WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
    194                 WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
    195                 WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    196     }
    197 
    198     @Override
    199     protected void onResume() {
    200         super.onResume();
    201 
    202         IntentFilter filter = new IntentFilter();
    203         filter.addAction(BluetoothDevice.ACTION_PAIRING_CANCEL);
    204         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
    205         registerReceiver(mReceiver, filter);
    206     }
    207 
    208     @Override
    209     protected void onPause() {
    210         unregisterReceiver(mReceiver);
    211 
    212         // Finish the activity if we get placed in the background and cancel pairing
    213         cancelPairing();
    214         dismiss();
    215 
    216         super.onPause();
    217     }
    218 
    219     @Override
    220     public void onActionClicked(Action action) {
    221         String key = action.getKey();
    222         if (KEY_PAIR.equals(key)) {
    223             onPair(null);
    224             dismiss();
    225         } else if (KEY_CANCEL.equals(key)) {
    226             cancelPairing();
    227         }
    228     }
    229 
    230     @Override
    231     public boolean onKeyDown(int keyCode, KeyEvent event) {
    232         if (keyCode == KeyEvent.KEYCODE_BACK) {
    233             cancelPairing();
    234         }
    235         return super.onKeyDown(keyCode, event);
    236     }
    237 
    238     private ArrayList<Action> getActions() {
    239         ArrayList<Action> actions = new ArrayList<Action>();
    240 
    241         switch (mType) {
    242             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    243             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    244             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    245                 actions.add(new Action.Builder()
    246                         .key(KEY_PAIR)
    247                         .title(getString(R.string.bluetooth_pair))
    248                         .build());
    249 
    250                 actions.add(new Action.Builder()
    251                         .key(KEY_CANCEL)
    252                         .title(getString(R.string.bluetooth_cancel))
    253                         .build());
    254                 break;
    255             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    256             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    257                 actions.add(new Action.Builder()
    258                         .key(KEY_CANCEL)
    259                         .title(getString(R.string.bluetooth_cancel))
    260                         .build());
    261                 break;
    262         }
    263 
    264         return actions;
    265     }
    266 
    267     private void dismiss() {
    268         finish();
    269     }
    270 
    271     private void cancelPairing() {
    272         if (DEBUG) {
    273             Log.d(TAG, "cancelPairing");
    274         }
    275         mDevice.cancelPairingUserInput();
    276     }
    277 
    278     private void createUserEntryDialog() {
    279         setContentView(R.layout.bt_pairing_passkey_entry);
    280 
    281         mTitleText = (TextView) findViewById(R.id.title_text);
    282         mTextInput = (EditText) findViewById(R.id.text_input);
    283 
    284         String instructions = getString(R.string.bluetooth_confirm_passkey_msg,
    285                 mDevice.getName(), mPairingKey);
    286         int maxLength;
    287         switch (mType) {
    288             case BluetoothDevice.PAIRING_VARIANT_PIN:
    289                 instructions = getString(R.string.bluetooth_enter_pin_msg, mDevice.getName());
    290                 mInstructionText = (TextView) findViewById(R.id.hint_text);
    291                 mInstructionText.setText(getString(R.string.bluetooth_pin_values_hint));
    292                 // Maximum of 16 characters in a PIN
    293                 maxLength = BLUETOOTH_PIN_MAX_LENGTH;
    294                 mTextInput.setInputType(InputType.TYPE_CLASS_NUMBER);
    295                 break;
    296 
    297             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    298                 instructions = getString(R.string.bluetooth_enter_passkey_msg, mDevice.getName());
    299                 // Maximum of 6 digits for passkey
    300                 maxLength = BLUETOOTH_PASSKEY_MAX_LENGTH;
    301                 mTextInput.setInputType(InputType.TYPE_CLASS_TEXT);
    302                 break;
    303 
    304             default:
    305                 Log.e(TAG, "Incorrect pairing type for createPinEntryView: " + mType);
    306                 dismiss();
    307                 return;
    308         }
    309 
    310         mTitleText.setText(Html.fromHtml(instructions));
    311 
    312         mTextInput.setFilters(new InputFilter[] { new LengthFilter(maxLength) });
    313     }
    314 
    315     private void createConfirmationDialog() {
    316         // Build a Dialog activity view, with Action Fragment
    317 
    318         mActions = getActions();
    319 
    320         mActionFragment = ActionFragment.newInstance(mActions);
    321         mContentFragment = new Fragment() {
    322             @Override
    323             public View onCreateView(LayoutInflater inflater, ViewGroup container,
    324                     Bundle savedInstanceState) {
    325                 View v = inflater.inflate(R.layout.bt_pairing_passkey_display, container, false);
    326 
    327                 mTitleText = (TextView) v.findViewById(R.id.title);
    328                 mInstructionText = (TextView) v.findViewById(R.id.pairing_instructions);
    329 
    330                 mTitleText.setText(getString(R.string.bluetooth_pairing_request));
    331 
    332                 if (AccessibilityHelper.forceFocusableViews(getActivity())) {
    333                     mTitleText.setFocusable(true);
    334                     mTitleText.setFocusableInTouchMode(true);
    335                     mInstructionText.setFocusable(true);
    336                     mInstructionText.setFocusableInTouchMode(true);
    337                 }
    338 
    339                 String instructions;
    340 
    341                 switch (mType) {
    342                     case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    343                     case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    344                         instructions = getString(R.string.bluetooth_display_passkey_pin_msg,
    345                                 mDevice.getName(), mPairingKey);
    346 
    347                         // Since its only a notification, send an OK to the framework,
    348                         // indicating that the dialog has been displayed.
    349                         if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY) {
    350                             mDevice.setPairingConfirmation(true);
    351                         } else if (mType == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
    352                             byte[] pinBytes = BluetoothDevice.convertPinToBytes(mPairingKey);
    353                             mDevice.setPin(pinBytes);
    354                         }
    355                         break;
    356 
    357                     case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    358                         instructions = getString(R.string.bluetooth_confirm_passkey_msg,
    359                                 mDevice.getName(), mPairingKey);
    360                         break;
    361 
    362                     case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    363                     case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    364                         instructions = getString(R.string.bluetooth_incoming_pairing_msg,
    365                                 mDevice.getName());
    366 
    367                         break;
    368                     default:
    369                         instructions = new String();
    370                 }
    371 
    372                 mInstructionText.setText(Html.fromHtml(instructions));
    373 
    374                 return v;
    375             }
    376         };
    377 
    378         setContentAndActionFragments(mContentFragment, mActionFragment);
    379     }
    380 
    381     private void onPair(String value) {
    382         if (DEBUG) {
    383             Log.d(TAG, "onPair: " + value);
    384         }
    385         switch (mType) {
    386             case BluetoothDevice.PAIRING_VARIANT_PIN:
    387                 byte[] pinBytes = BluetoothDevice.convertPinToBytes(value);
    388                 if (pinBytes == null) {
    389                     return;
    390                 }
    391                 mDevice.setPin(pinBytes);
    392                 break;
    393 
    394             case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
    395                 int passkey = Integer.parseInt(value);
    396                 mDevice.setPasskey(passkey);
    397                 break;
    398 
    399             case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
    400             case BluetoothDevice.PAIRING_VARIANT_CONSENT:
    401                 mDevice.setPairingConfirmation(true);
    402                 break;
    403 
    404             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
    405             case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
    406                 // Do nothing.
    407                 break;
    408 
    409             case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
    410                 mDevice.setRemoteOutOfBandData();
    411                 break;
    412 
    413             default:
    414                 Log.e(TAG, "Incorrect pairing type received");
    415         }
    416     }
    417 
    418 }
    419