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