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