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 42 import com.android.car.settings.R; 43 import com.android.car.ui.preference.CarUiDialogFragment; 44 45 import java.util.Objects; 46 47 /** Dialog fragment for renaming a Bluetooth device. */ 48 public abstract class BluetoothRenameDialogFragment extends CarUiDialogFragment implements 49 TextWatcher, 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 onDialogClosed(boolean positiveResult)104 protected void onDialogClosed(boolean positiveResult) { 105 } 106 107 @Override onSaveInstanceState(@onNull Bundle outState)108 public void onSaveInstanceState(@NonNull Bundle outState) { 109 outState.putString(KEY_NAME, mDeviceNameView.getText().toString()); 110 } 111 createDialogView(String deviceName)112 private View createDialogView(String deviceName) { 113 final LayoutInflater layoutInflater = (LayoutInflater) requireActivity().getSystemService( 114 Context.LAYOUT_INFLATER_SERVICE); 115 // TODO: use dialog layout defined in preference theme. 116 View view = layoutInflater.inflate(R.layout.preference_dialog_edittext, /* root= */ null); 117 mDeviceNameView = view.findViewById(android.R.id.edit); 118 mDeviceNameView.setFilters(new InputFilter[]{ 119 new Utf8ByteLengthFilter(BLUETOOTH_NAME_MAX_LENGTH_BYTES) 120 }); 121 mDeviceNameView.setText(deviceName); // Set initial value before adding listener. 122 if (!TextUtils.isEmpty(deviceName)) { 123 mDeviceNameView.setSelection(deviceName.length()); 124 } 125 mDeviceNameView.addTextChangedListener(this); 126 mDeviceNameView.setOnEditorActionListener(this); 127 mDeviceNameView.setRawInputType(InputType.TYPE_CLASS_TEXT); 128 mDeviceNameView.setImeOptions(EditorInfo.IME_ACTION_DONE); 129 130 return view; 131 } 132 133 @Override onStart()134 public void onStart() { 135 super.onStart(); 136 mRenameButton = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE); 137 refreshRenameButton(); 138 } 139 140 @Override onDestroy()141 public void onDestroy() { 142 super.onDestroy(); 143 mAlertDialog = null; 144 mDeviceNameView = null; 145 mRenameButton = null; 146 } 147 148 /** Refreshes the displayed device name with the latest value from {@link #getDeviceName()}. */ updateDeviceName()149 protected void updateDeviceName() { 150 String name = getDeviceName(); 151 if (name != null) { 152 mDeviceNameView.setText(name); 153 } 154 } 155 156 @Override beforeTextChanged(CharSequence s, int start, int count, int after)157 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 158 } 159 160 @Override onTextChanged(CharSequence s, int start, int before, int count)161 public void onTextChanged(CharSequence s, int start, int before, int count) { 162 } 163 164 @Override afterTextChanged(Editable s)165 public void afterTextChanged(Editable s) { 166 refreshRenameButton(); 167 } 168 169 @Override onEditorAction(TextView v, int actionId, KeyEvent event)170 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 171 if (actionId == EditorInfo.IME_ACTION_DONE) { 172 String editedName = getEditedName(); 173 if (TextUtils.isEmpty(editedName)) { 174 return false; 175 } 176 setDeviceName(editedName); 177 if (mAlertDialog != null && mAlertDialog.isShowing()) { 178 mAlertDialog.dismiss(); 179 } 180 return true; 181 } 182 return false; 183 } 184 refreshRenameButton()185 private void refreshRenameButton() { 186 String editedName = getEditedName(); 187 mRenameButton.setEnabled( 188 !TextUtils.isEmpty(editedName) && !Objects.equals(editedName, getDeviceName())); 189 } 190 getEditedName()191 private String getEditedName() { 192 return mDeviceNameView.getText().toString().trim(); 193 } 194 } 195