1 /* 2 * Copyright 2018 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.car.settings.bluetooth; 18 19 import android.app.AlertDialog; 20 import android.app.Dialog; 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.os.Bundle; 24 import android.text.Editable; 25 import android.text.InputFilter; 26 import android.text.InputType; 27 import android.text.TextUtils; 28 import android.text.TextWatcher; 29 import android.view.KeyEvent; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.inputmethod.EditorInfo; 33 import android.view.inputmethod.InputMethodManager; 34 import android.widget.Button; 35 import android.widget.EditText; 36 import android.widget.TextView; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.annotation.StringRes; 41 import androidx.fragment.app.DialogFragment; 42 43 import com.android.car.settings.R; 44 45 import java.util.Objects; 46 47 /** Dialog fragment for renaming a Bluetooth device. */ 48 public abstract class BluetoothRenameDialogFragment extends DialogFragment implements TextWatcher, 49 TextView.OnEditorActionListener { 50 51 // Keys to save the edited name and edit status for restoring after configuration change. 52 private static final String KEY_NAME = "device_name"; 53 54 private static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248; 55 56 private AlertDialog mAlertDialog; 57 private EditText mDeviceNameView; 58 private Button mRenameButton; 59 60 /** Returns the title to use for the dialog. */ 61 @StringRes getDialogTitle()62 protected abstract int getDialogTitle(); 63 64 /** Returns the current name used for this device or {@code null} if a name is not available. */ 65 @Nullable getDeviceName()66 protected abstract String getDeviceName(); 67 68 /** 69 * Set the device to the given name. 70 * 71 * @param deviceName the name to use. 72 */ setDeviceName(String deviceName)73 protected abstract void setDeviceName(String deviceName); 74 75 @Override 76 @NonNull onCreateDialog(Bundle savedInstanceState)77 public Dialog onCreateDialog(Bundle savedInstanceState) { 78 String deviceName = getDeviceName(); 79 if (savedInstanceState != null) { 80 deviceName = savedInstanceState.getString(KEY_NAME, deviceName); 81 } 82 AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()) 83 .setTitle(getDialogTitle()) 84 .setView(createDialogView(deviceName)) 85 .setPositiveButton(R.string.bluetooth_rename_button, 86 (dialog, which) -> setDeviceName( 87 mDeviceNameView.getText().toString().trim())) 88 .setNegativeButton(android.R.string.cancel, /* listener= */ null); 89 mAlertDialog = builder.create(); 90 mAlertDialog.setOnShowListener(d -> { 91 if (mDeviceNameView.requestFocus()) { 92 InputMethodManager imm = (InputMethodManager) requireContext().getSystemService( 93 Context.INPUT_METHOD_SERVICE); 94 if (imm != null) { 95 imm.showSoftInput(mDeviceNameView, InputMethodManager.SHOW_IMPLICIT); 96 } 97 } 98 }); 99 100 return mAlertDialog; 101 } 102 103 @Override onSaveInstanceState(@onNull Bundle outState)104 public void onSaveInstanceState(@NonNull Bundle outState) { 105 outState.putString(KEY_NAME, mDeviceNameView.getText().toString()); 106 } 107 createDialogView(String deviceName)108 private View createDialogView(String deviceName) { 109 final LayoutInflater layoutInflater = (LayoutInflater) requireActivity().getSystemService( 110 Context.LAYOUT_INFLATER_SERVICE); 111 // TODO: use dialog layout defined in preference theme. 112 View view = layoutInflater.inflate(R.layout.preference_dialog_edittext, /* root= */ null); 113 mDeviceNameView = view.findViewById(android.R.id.edit); 114 mDeviceNameView.setFilters(new InputFilter[]{ 115 new Utf8ByteLengthFilter(BLUETOOTH_NAME_MAX_LENGTH_BYTES) 116 }); 117 mDeviceNameView.setText(deviceName); // Set initial value before adding listener. 118 if (!TextUtils.isEmpty(deviceName)) { 119 mDeviceNameView.setSelection(deviceName.length()); 120 } 121 mDeviceNameView.addTextChangedListener(this); 122 mDeviceNameView.setOnEditorActionListener(this); 123 mDeviceNameView.setRawInputType(InputType.TYPE_CLASS_TEXT); 124 mDeviceNameView.setImeOptions(EditorInfo.IME_ACTION_DONE); 125 126 return view; 127 } 128 129 @Override onStart()130 public void onStart() { 131 super.onStart(); 132 mRenameButton = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE); 133 refreshRenameButton(); 134 } 135 136 @Override onDestroy()137 public void onDestroy() { 138 super.onDestroy(); 139 mAlertDialog = null; 140 mDeviceNameView = null; 141 mRenameButton = null; 142 } 143 144 /** Refreshes the displayed device name with the latest value from {@link #getDeviceName()}. */ updateDeviceName()145 protected void updateDeviceName() { 146 String name = getDeviceName(); 147 if (name != null) { 148 mDeviceNameView.setText(name); 149 } 150 } 151 152 @Override beforeTextChanged(CharSequence s, int start, int count, int after)153 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 154 } 155 156 @Override onTextChanged(CharSequence s, int start, int before, int count)157 public void onTextChanged(CharSequence s, int start, int before, int count) { 158 } 159 160 @Override afterTextChanged(Editable s)161 public void afterTextChanged(Editable s) { 162 refreshRenameButton(); 163 } 164 165 @Override onEditorAction(TextView v, int actionId, KeyEvent event)166 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 167 if (actionId == EditorInfo.IME_ACTION_DONE) { 168 String editedName = getEditedName(); 169 if (TextUtils.isEmpty(editedName)) { 170 return false; 171 } 172 setDeviceName(editedName); 173 if (mAlertDialog != null && mAlertDialog.isShowing()) { 174 mAlertDialog.dismiss(); 175 } 176 return true; 177 } 178 return false; 179 } 180 refreshRenameButton()181 private void refreshRenameButton() { 182 String editedName = getEditedName(); 183 mRenameButton.setEnabled( 184 !TextUtils.isEmpty(editedName) && !Objects.equals(editedName, getDeviceName())); 185 } 186 getEditedName()187 private String getEditedName() { 188 return mDeviceNameView.getText().toString().trim(); 189 } 190 } 191