1 /*
2  * Copyright (C) 2020 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.tv.settings.accessories;
18 
19 import android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.os.Bundle;
22 import android.text.TextUtils;
23 import android.util.Log;
24 import android.view.View;
25 
26 import androidx.leanback.app.GuidedStepFragment;
27 import androidx.leanback.widget.GuidanceStylist;
28 import androidx.leanback.widget.GuidedAction;
29 
30 import com.android.tv.settings.R;
31 
32 import com.android.tv.settings.util.AccessibilityHelper;
33 
34 import java.util.List;
35 
36 /**
37  * The {@Fragment} to be used with {@BluetoothActionActivity} for handling confirmation UI of
38  * Bluetooth-related actions such as connection and renaming.
39  */
40 public class BluetoothActionFragment extends GuidedStepFragment {
41 
42     private static final String TAG = "BluetoothActionFragment";
43 
44     interface Listener {
onChoice(String key, int choice)45         void onChoice(String key, int choice);
46 
onText(String key, String text)47         void onText(String key, String text);
48     }
49 
50     // The preference key associated with this fragment
51     private static final String ARG_KEY = "arg_key";
52     private static final String ARG_TITLE = "arg_title";
53     private static final String ARG_SUMMARY = "arg_summary";
54     private static final String ARG_ICON = "arg_icon";
55     private static final String ARG_CHOICES = "arg_choices";
56     private static final String ARG_NAME = "arg_name";
57     private static final String ARG_DEFAULT_CHOICE = "arg_default_choice";
58 
59     public static final int DEFAULT_CHOICE_UNDEFINED = -1;
60 
61     /** Encapsulates resources into a Bundle to be extracted. */
prepareArgs(Bundle args, String key, int iconResId, int titleResId, int summaryResId, int[] choices, String name, int defaultChoice)62     public static void prepareArgs(Bundle args, String key, int iconResId, int titleResId,
63             int summaryResId, int[] choices, String name, int defaultChoice) {
64         args.putString(ARG_KEY, key);
65         args.putInt(ARG_ICON, iconResId);
66         args.putInt(ARG_TITLE, titleResId);
67         args.putInt(ARG_SUMMARY, summaryResId);
68         args.putIntArray(ARG_CHOICES, choices);
69         args.putString(ARG_NAME, name);
70         args.putInt(ARG_DEFAULT_CHOICE, defaultChoice);
71     }
72 
73     @Override
onCreateGuidance(Bundle savedInstanceState)74     public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
75         return new GuidanceStylist.Guidance(
76                 getTitleImpl(),
77                 getSummaryImpl(),
78                 null,
79                 getDrawableImpl());
80     }
81 
82     @Override
onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState)83     public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
84         int[] choices = getChoices();
85         Context context = getActivity();
86         for (int choice : choices) {
87             actions.add(new GuidedAction.Builder(context)
88                     .title(getString(choice))
89                     .id(choice)
90                     .build());
91         }
92 
93         // If no choices were given, we know this is a text input.
94         if (choices.length == 0) {
95             Bundle args = getArguments();
96             final String existingName = args.getString(ARG_NAME);
97             actions.add(new GuidedAction.Builder(context)
98                     .title(existingName)
99                     .editable(true)
100                     .build());
101         }
102     }
103 
104     @Override
onGuidedActionClicked(GuidedAction action)105     public void onGuidedActionClicked(GuidedAction action) {
106         Bundle args = getArguments();
107         final String key = args.getString(ARG_KEY);
108         final int[] choices = getChoices();
109         final long id = action.getId();
110         Listener listener = getListener();
111         if (listener == null) {
112             return;
113         }
114 
115         for (int choice : choices) {
116             if (choice == id) {
117                 listener.onChoice(key, choice);
118                 break;
119             }
120         }
121     }
122 
123     @Override
onViewCreated(View view, Bundle savedInstanceState)124     public void onViewCreated(View view, Bundle savedInstanceState) {
125         super.onViewCreated(view, savedInstanceState);
126 
127         Bundle args = getArguments();
128         final int defaultChoice = args.getInt(ARG_DEFAULT_CHOICE, DEFAULT_CHOICE_UNDEFINED);
129         if (defaultChoice != DEFAULT_CHOICE_UNDEFINED) {
130             if (defaultChoice < getChoices().length) {
131                 setSelectedActionPosition(defaultChoice);
132             } else {
133                 Log.w(TAG, "Default choice out of bounds: " + defaultChoice);
134             }
135         }
136     }
137 
138     @Override
onGuidedActionEditedAndProceed(GuidedAction action)139     public long onGuidedActionEditedAndProceed(GuidedAction action) {
140         Listener listener = getListener();
141         if (listener == null) {
142             Log.e(TAG, "onGuidedActionEditedAndProceed: no listener");
143             return GuidedAction.ACTION_ID_CANCEL;
144         }
145 
146         Bundle args = getArguments();
147         final String existingName = args.getString(ARG_NAME);
148         final String key = args.getString(ARG_KEY);
149         final String newName = action.getTitle() != null ? action.getTitle().toString() : "";
150 
151         // We need to dismiss the keyboard ourselves since the behavior of dismissing the response
152         // after an input completes is not one of the typical flows handled by GuidedStepFragment.
153         AccessibilityHelper.dismissKeyboard(getActivity(), getView());
154         if (!TextUtils.equals(existingName, newName) && !TextUtils.isEmpty(newName)) {
155             listener.onText(key, action.getTitle().toString());
156         }
157         return action.getId();
158     }
159 
160     @Override
onProvideTheme()161     public int onProvideTheme() {
162         return R.style.BluetoothActionGuidedStepTheme;
163     }
164 
getTitleImpl()165     private String getTitleImpl() {
166         Bundle args = getArguments();
167         String name = args.getString(ARG_NAME);
168         if (!TextUtils.isEmpty(name)) {
169             return getString(args.getInt(ARG_TITLE), name);
170         }
171         return getString(args.getInt(ARG_TITLE));
172     }
173 
getSummaryImpl()174     private String getSummaryImpl() {
175         Bundle args = getArguments();
176         return args.getInt(ARG_SUMMARY) != 0 ? getString(args.getInt(ARG_SUMMARY)) : null;
177     }
178 
getDrawableImpl()179     private Drawable getDrawableImpl() {
180         Bundle args = getArguments();
181         return args.getInt(ARG_ICON) != 0
182                 ? getResources().getDrawable(args.getInt(ARG_ICON))
183                 : null;
184     }
185 
getChoices()186     private int[] getChoices() {
187         Bundle args = getArguments();
188         int[] choices = new int[0];
189         try {
190             int[] tmp = args.getIntArray(ARG_CHOICES);
191             if (tmp != null) {
192                 choices = tmp;
193             }
194         } catch (Exception e) {
195             Log.w(TAG, "Exception in reading choices: " + e);
196         }
197         return choices;
198     }
199 
getListener()200     private Listener getListener() {
201         Listener listener = getTargetFragment() instanceof Listener
202                 ? (Listener) getTargetFragment()
203                 : null;
204         if (listener == null) {
205             listener = getActivity() instanceof Listener ? (Listener) getActivity() : null;
206         }
207         return listener;
208     }
209 }
210