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