1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package androidx.leanback.widget;
15 
16 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
17 
18 import android.content.Context;
19 import android.util.Log;
20 import android.util.Pair;
21 import android.view.View;
22 import android.view.inputmethod.InputMethodManager;
23 import android.widget.TextView;
24 
25 import androidx.annotation.RestrictTo;
26 import androidx.leanback.widget.GuidedActionAdapter.EditListener;
27 
28 import java.util.ArrayList;
29 
30 /**
31  * Internal implementation manages a group of GuidedActionAdapters, control the next action after
32  * editing finished, maintain the Ime open/close status.
33  * @hide
34  */
35 @RestrictTo(LIBRARY_GROUP)
36 public class GuidedActionAdapterGroup {
37 
38     private static final String TAG_EDIT = "EditableAction";
39     private static final boolean DEBUG_EDIT = false;
40 
41     ArrayList<Pair<GuidedActionAdapter, GuidedActionAdapter>> mAdapters =
42             new ArrayList<Pair<GuidedActionAdapter, GuidedActionAdapter>>();
43     private boolean mImeOpened;
44     private EditListener mEditListener;
45 
addAdpter(GuidedActionAdapter adapter1, GuidedActionAdapter adapter2)46     public void addAdpter(GuidedActionAdapter adapter1, GuidedActionAdapter adapter2) {
47         mAdapters.add(new Pair<GuidedActionAdapter, GuidedActionAdapter>(adapter1, adapter2));
48         if (adapter1 != null) {
49             adapter1.mGroup = this;
50         }
51         if (adapter2 != null) {
52             adapter2.mGroup = this;
53         }
54     }
55 
getNextAdapter(GuidedActionAdapter adapter)56     public GuidedActionAdapter getNextAdapter(GuidedActionAdapter adapter) {
57         for (int i = 0; i < mAdapters.size(); i++) {
58             Pair<GuidedActionAdapter, GuidedActionAdapter> pair = mAdapters.get(i);
59             if (pair.first == adapter) {
60                 return pair.second;
61             }
62         }
63         return null;
64     }
65 
setEditListener(EditListener listener)66     public void setEditListener(EditListener listener) {
67         mEditListener = listener;
68     }
69 
focusToNextAction(GuidedActionAdapter adapter, GuidedAction action, long nextActionId)70     boolean focusToNextAction(GuidedActionAdapter adapter, GuidedAction action, long nextActionId) {
71         // for ACTION_ID_NEXT, we first find out the matching index in Actions list.
72         int index = 0;
73         if (nextActionId == GuidedAction.ACTION_ID_NEXT) {
74             index = adapter.indexOf(action);
75             if (index < 0) {
76                 return false;
77             }
78             // start from next, if reach end, will go next Adapter below
79             index++;
80         }
81 
82         do {
83             int size = adapter.getCount();
84             if (nextActionId == GuidedAction.ACTION_ID_NEXT) {
85                 while (index < size && !adapter.getItem(index).isFocusable()) {
86                     index++;
87                 }
88             } else {
89                 while (index < size && adapter.getItem(index).getId() != nextActionId) {
90                     index++;
91                 }
92             }
93             if (index < size) {
94                 GuidedActionsStylist.ViewHolder vh =
95                         (GuidedActionsStylist.ViewHolder) adapter.getGuidedActionsStylist()
96                                 .getActionsGridView().findViewHolderForPosition(index);
97                 if (vh != null) {
98                     if (vh.getAction().hasTextEditable()) {
99                         if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme of next Action");
100                         // open Ime on next action.
101                         openIme(adapter, vh);
102                     } else {
103                         if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme and focus to next Action");
104                         // close IME and focus to next (not editable) action
105                         closeIme(vh.itemView);
106                         vh.itemView.requestFocus();
107                     }
108                     return true;
109                 }
110                 return false;
111             }
112             // search from index 0 of next Adapter
113             adapter = getNextAdapter(adapter);
114             if (adapter == null) {
115                 break;
116             }
117             index = 0;
118         } while (true);
119         return false;
120     }
121 
openIme(GuidedActionAdapter adapter, GuidedActionsStylist.ViewHolder avh)122     public void openIme(GuidedActionAdapter adapter, GuidedActionsStylist.ViewHolder avh) {
123         adapter.getGuidedActionsStylist().setEditingMode(avh, true);
124         View v = avh.getEditingView();
125         if (v == null || !avh.isInEditingText()) {
126             return;
127         }
128         InputMethodManager mgr = (InputMethodManager)
129                 v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
130         // Make the TextView focusable during editing, avoid the TextView gets accessibility focus
131         // before editing started. see also GuidedActionEditText where setFocusable(false).
132         v.setFocusable(true);
133         v.requestFocus();
134         mgr.showSoftInput(v, 0);
135         if (!mImeOpened) {
136             mImeOpened = true;
137             mEditListener.onImeOpen();
138         }
139     }
140 
closeIme(View v)141     public void closeIme(View v) {
142         if (mImeOpened) {
143             mImeOpened = false;
144             InputMethodManager mgr = (InputMethodManager)
145                     v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
146             mgr.hideSoftInputFromWindow(v.getWindowToken(), 0);
147             mEditListener.onImeClose();
148         }
149     }
150 
fillAndStay(GuidedActionAdapter adapter, TextView v)151     public void fillAndStay(GuidedActionAdapter adapter, TextView v) {
152         GuidedActionsStylist.ViewHolder avh = adapter.findSubChildViewHolder(v);
153         updateTextIntoAction(avh, v);
154         mEditListener.onGuidedActionEditCanceled(avh.getAction());
155         adapter.getGuidedActionsStylist().setEditingMode(avh, false);
156         closeIme(v);
157         avh.itemView.requestFocus();
158     }
159 
fillAndGoNext(GuidedActionAdapter adapter, TextView v)160     public void fillAndGoNext(GuidedActionAdapter adapter, TextView v) {
161         boolean handled = false;
162         GuidedActionsStylist.ViewHolder avh = adapter.findSubChildViewHolder(v);
163         updateTextIntoAction(avh, v);
164         adapter.performOnActionClick(avh);
165         long nextActionId = mEditListener.onGuidedActionEditedAndProceed(avh.getAction());
166         adapter.getGuidedActionsStylist().setEditingMode(avh, false);
167         if (nextActionId != GuidedAction.ACTION_ID_CURRENT
168                 && nextActionId != avh.getAction().getId()) {
169             handled = focusToNextAction(adapter, avh.getAction(), nextActionId);
170         }
171         if (!handled) {
172             if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme no next action");
173             handled = true;
174             closeIme(v);
175             avh.itemView.requestFocus();
176         }
177     }
178 
updateTextIntoAction(GuidedActionsStylist.ViewHolder avh, TextView v)179     private void updateTextIntoAction(GuidedActionsStylist.ViewHolder avh, TextView v) {
180         GuidedAction action = avh.getAction();
181         if (v == avh.getDescriptionView()) {
182             if (action.getEditDescription() != null) {
183                 action.setEditDescription(v.getText());
184             } else {
185                 action.setDescription(v.getText());
186             }
187         } else if (v == avh.getTitleView()) {
188             if (action.getEditTitle() != null) {
189                 action.setEditTitle(v.getText());
190             } else {
191                 action.setTitle(v.getText());
192             }
193         }
194     }
195 
196 }
197