Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2008 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.app.AlertDialog;
     20 import android.app.Dialog;
     21 
     22 import android.bluetooth.BluetoothAdapter;
     23 import android.content.BroadcastReceiver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.preference.EditTextPreference;
     28 import android.text.Editable;
     29 import android.text.InputFilter;
     30 import android.text.Spanned;
     31 import android.text.TextWatcher;
     32 import android.util.AttributeSet;
     33 import android.widget.Button;
     34 import android.widget.EditText;
     35 
     36 /**
     37  * BluetoothNamePreference is the preference type for editing the device's
     38  * Bluetooth name. It asks the user for a name, and persists it via the
     39  * Bluetooth API.
     40  */
     41 public class BluetoothNamePreference extends EditTextPreference implements TextWatcher {
     42     private static final String TAG = "BluetoothNamePreference";
     43     // max. length reduced from 248 to 246 bytes to work around Bluez bug
     44     private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 246;
     45 
     46     private LocalBluetoothManager mLocalManager;
     47 
     48     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
     49         @Override
     50         public void onReceive(Context context, Intent intent) {
     51             String action = intent.getAction();
     52             if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
     53                 setSummaryToName();
     54             } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED) &&
     55                     (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) ==
     56                             BluetoothAdapter.STATE_ON)) {
     57                 setSummaryToName();
     58             }
     59         }
     60     };
     61 
     62     public BluetoothNamePreference(Context context, AttributeSet attrs) {
     63         super(context, attrs);
     64 
     65         mLocalManager = LocalBluetoothManager.getInstance(context);
     66 
     67         setSummaryToName();
     68     }
     69 
     70     public void resume() {
     71         IntentFilter filter = new IntentFilter();
     72         filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
     73         filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
     74         getContext().registerReceiver(mReceiver, filter);
     75 
     76         // Make sure the OK button is disabled (if necessary) after rotation
     77         EditText et = getEditText();
     78         if (et != null) {
     79             et.setFilters(new InputFilter[] {
     80                     new Utf8ByteLengthFilter(BLUETOOTH_NAME_MAX_LENGTH_BYTES)
     81             });
     82 
     83             et.addTextChangedListener(this);
     84             Dialog d = getDialog();
     85             if (d instanceof AlertDialog) {
     86                 Button b = ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE);
     87                 b.setEnabled(et.getText().length() > 0);
     88             }
     89         }
     90     }
     91 
     92     public void pause() {
     93         EditText et = getEditText();
     94         if (et != null) {
     95             et.removeTextChangedListener(this);
     96         }
     97         getContext().unregisterReceiver(mReceiver);
     98     }
     99 
    100     private void setSummaryToName() {
    101         BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter();
    102         if (adapter.isEnabled()) {
    103             setSummary(adapter.getName());
    104         }
    105     }
    106 
    107     @Override
    108     protected boolean persistString(String value) {
    109         BluetoothAdapter adapter = mLocalManager.getBluetoothAdapter();
    110         adapter.setName(value);
    111         return true;
    112     }
    113 
    114     @Override
    115     protected void onClick() {
    116         super.onClick();
    117 
    118         // The dialog should be created by now
    119         EditText et = getEditText();
    120         if (et != null) {
    121             et.setText(mLocalManager.getBluetoothAdapter().getName());
    122         }
    123     }
    124 
    125     // TextWatcher interface
    126     public void afterTextChanged(Editable s) {
    127         Dialog d = getDialog();
    128         if (d instanceof AlertDialog) {
    129             ((AlertDialog) d).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(s.length() > 0);
    130         }
    131     }
    132 
    133     // TextWatcher interface
    134     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    135         // not used
    136     }
    137 
    138     // TextWatcher interface
    139     public void onTextChanged(CharSequence s, int start, int before, int count) {
    140         // not used
    141     }
    142 
    143     /**
    144      * This filter will constrain edits so that the text length is not
    145      * greater than the specified number of bytes using UTF-8 encoding.
    146      * <p>The JNI method used by {@link android.server.BluetoothService}
    147      * to convert UTF-16 to UTF-8 doesn't support surrogate pairs,
    148      * therefore code points outside of the basic multilingual plane
    149      * (0000-FFFF) will be encoded as a pair of 3-byte UTF-8 characters,
    150      * rather than a single 4-byte UTF-8 encoding. Dalvik implements this
    151      * conversion in {@code convertUtf16ToUtf8()} in
    152      * {@code dalvik/vm/UtfString.c}.
    153      * <p>This JNI method is unlikely to change in the future due to
    154      * backwards compatibility requirements. It's also unclear whether
    155      * the installed base of Bluetooth devices would correctly handle the
    156      * encoding of surrogate pairs in UTF-8 as 4 bytes rather than 6.
    157      * However, this filter will still work in scenarios where surrogate
    158      * pairs are encoded as 4 bytes, with the caveat that the maximum
    159      * length will be constrained more conservatively than necessary.
    160      */
    161     public static class Utf8ByteLengthFilter implements InputFilter {
    162         private int mMaxBytes;
    163 
    164         public Utf8ByteLengthFilter(int maxBytes) {
    165             mMaxBytes = maxBytes;
    166         }
    167 
    168         public CharSequence filter(CharSequence source, int start, int end,
    169                                    Spanned dest, int dstart, int dend) {
    170             int srcByteCount = 0;
    171             // count UTF-8 bytes in source substring
    172             for (int i = start; i < end; i++) {
    173                 char c = source.charAt(i);
    174                 srcByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3);
    175             }
    176             int destLen = dest.length();
    177             int destByteCount = 0;
    178             // count UTF-8 bytes in destination excluding replaced section
    179             for (int i = 0; i < destLen; i++) {
    180                 if (i < dstart || i >= dend) {
    181                     char c = dest.charAt(i);
    182                     destByteCount += (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3);
    183                 }
    184             }
    185             int keepBytes = mMaxBytes - destByteCount;
    186             if (keepBytes <= 0) {
    187                 return "";
    188             } else if (keepBytes >= srcByteCount) {
    189                 return null; // use original dest string
    190             } else {
    191                 // find end position of largest sequence that fits in keepBytes
    192                 for (int i = start; i < end; i++) {
    193                     char c = source.charAt(i);
    194                     keepBytes -= (c < 0x0080) ? 1 : (c < 0x0800 ? 2 : 3);
    195                     if (keepBytes < 0) {
    196                         return source.subSequence(start, i);
    197                     }
    198                 }
    199                 // If the entire substring fits, we should have returned null
    200                 // above, so this line should not be reached. If for some
    201                 // reason it is, return null to use the original dest string.
    202                 return null;
    203             }
    204         }
    205     }
    206 }
    207