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