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