RemoteInputView.java revision 14503e270c7efbee4c766d1e805865f1d415ec05
1/*
2 * Copyright (C) 2015 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
17package com.android.systemui.statusbar.policy;
18
19import android.app.PendingIntent;
20import android.app.RemoteInput;
21import android.content.Context;
22import android.content.Intent;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.os.Bundle;
26import android.text.Editable;
27import android.text.TextWatcher;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.view.KeyEvent;
31import android.view.LayoutInflater;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.inputmethod.CompletionInfo;
35import android.view.inputmethod.EditorInfo;
36import android.view.inputmethod.InputConnection;
37import android.view.inputmethod.InputMethodManager;
38import android.widget.EditText;
39import android.widget.ImageButton;
40import android.widget.LinearLayout;
41import android.widget.ProgressBar;
42import android.widget.TextView;
43
44import com.android.systemui.R;
45import com.android.systemui.statusbar.NotificationData;
46import com.android.systemui.statusbar.RemoteInputController;
47
48/**
49 * Host for the remote input.
50 */
51public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
52
53    private static final String TAG = "RemoteInput";
54
55    // A marker object that let's us easily find views of this class.
56    public static final Object VIEW_TAG = new Object();
57
58    private RemoteEditText mEditText;
59    private ImageButton mSendButton;
60    private ProgressBar mProgressBar;
61    private PendingIntent mPendingIntent;
62    private RemoteInput[] mRemoteInputs;
63    private RemoteInput mRemoteInput;
64    private RemoteInputController mController;
65
66    private NotificationData.Entry mEntry;
67
68    public RemoteInputView(Context context, AttributeSet attrs) {
69        super(context, attrs);
70    }
71
72    @Override
73    protected void onFinishInflate() {
74        super.onFinishInflate();
75
76        mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
77
78        mSendButton = (ImageButton) findViewById(R.id.remote_input_send);
79        mSendButton.setOnClickListener(this);
80
81        mEditText = (RemoteEditText) getChildAt(0);
82        mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
83            @Override
84            public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
85
86                // Check if this was the result of hitting the enter key
87                final boolean isSoftImeEvent = event == null
88                        && (actionId == EditorInfo.IME_ACTION_DONE
89                        || actionId == EditorInfo.IME_ACTION_NEXT
90                        || actionId == EditorInfo.IME_ACTION_SEND);
91                final boolean isKeyboardEnterKey = event != null
92                        && KeyEvent.isConfirmKey(event.getKeyCode())
93                        && event.getAction() == KeyEvent.ACTION_DOWN;
94
95                if (isSoftImeEvent || isKeyboardEnterKey) {
96                    sendRemoteInput();
97                    return true;
98                }
99                return false;
100            }
101        });
102        mEditText.setOnClickListener(this);
103        mEditText.addTextChangedListener(this);
104        mEditText.setInnerFocusable(false);
105        mEditText.mDefocusListener = this;
106    }
107
108    private void sendRemoteInput() {
109        Bundle results = new Bundle();
110        results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
111        Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
112        RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
113                results);
114
115        mEditText.setEnabled(false);
116        mSendButton.setVisibility(INVISIBLE);
117        mProgressBar.setVisibility(VISIBLE);
118        mController.removeRemoteInput(mEntry);
119        mEditText.mShowImeOnInputConnection = false;
120
121        try {
122            mPendingIntent.send(mContext, 0, fillInIntent);
123        } catch (PendingIntent.CanceledException e) {
124            Log.i(TAG, "Unable to send remote input result", e);
125        }
126    }
127
128    public static RemoteInputView inflate(Context context, ViewGroup root,
129            NotificationData.Entry entry,
130            RemoteInputController controller) {
131        RemoteInputView v = (RemoteInputView)
132                LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
133        v.mController = controller;
134        v.mEntry = entry;
135        v.setTag(VIEW_TAG);
136
137        return v;
138    }
139
140    @Override
141    public void onClick(View v) {
142        if (v == mEditText) {
143            if (!mEditText.isFocusable()) {
144                focus();
145            }
146        } else if (v == mSendButton) {
147            sendRemoteInput();
148        }
149    }
150
151    public void onDefocus() {
152        mController.removeRemoteInput(mEntry);
153        mEntry.remoteInputText = mEditText.getText();
154        setVisibility(INVISIBLE);
155    }
156
157    @Override
158    protected void onAttachedToWindow() {
159        super.onAttachedToWindow();
160        if (mEntry.row.isChangingPosition()) {
161            if (getVisibility() == VISIBLE && mEditText.isFocusable()) {
162                mEditText.requestFocus();
163            }
164        }
165    }
166
167    @Override
168    protected void onDetachedFromWindow() {
169        super.onDetachedFromWindow();
170        if (mEntry.row.isChangingPosition()) {
171            return;
172        }
173        mController.removeRemoteInput(mEntry);
174    }
175
176    public void setPendingIntent(PendingIntent pendingIntent) {
177        mPendingIntent = pendingIntent;
178    }
179
180    public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
181        mRemoteInputs = remoteInputs;
182        mRemoteInput = remoteInput;
183        mEditText.setHint(mRemoteInput.getLabel());
184    }
185
186    public void focus() {
187        mController.addRemoteInput(mEntry);
188        mEditText.setInnerFocusable(true);
189        mEditText.mShowImeOnInputConnection = true;
190        mEditText.setText(mEntry.remoteInputText);
191        mEditText.setSelection(mEditText.getText().length());
192        mEditText.requestFocus();
193        updateSendButton();
194    }
195
196    public void onNotificationUpdate() {
197        boolean sending = mProgressBar.getVisibility() == VISIBLE;
198
199        if (sending) {
200            // Update came in after we sent the reply, time to reset.
201            reset();
202        }
203    }
204
205    private void reset() {
206        mEditText.getText().clear();
207        mEditText.setEnabled(true);
208        mSendButton.setVisibility(VISIBLE);
209        mProgressBar.setVisibility(INVISIBLE);
210        updateSendButton();
211        onDefocus();
212    }
213
214    private void updateSendButton() {
215        mSendButton.setEnabled(mEditText.getText().length() != 0);
216    }
217
218    @Override
219    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
220
221    @Override
222    public void onTextChanged(CharSequence s, int start, int before, int count) {}
223
224    @Override
225    public void afterTextChanged(Editable s) {
226        updateSendButton();
227    }
228
229    /**
230     * An EditText that changes appearance based on whether it's focusable and becomes
231     * un-focusable whenever the user navigates away from it or it becomes invisible.
232     */
233    public static class RemoteEditText extends EditText {
234
235        private final Drawable mBackground;
236        private RemoteInputView mDefocusListener;
237        boolean mShowImeOnInputConnection;
238
239        public RemoteEditText(Context context, AttributeSet attrs) {
240            super(context, attrs);
241            mBackground = getBackground();
242        }
243
244        private void defocusIfNeeded() {
245            if (mDefocusListener.mEntry.row.isChangingPosition()) {
246                return;
247            }
248            if (isFocusable() && isEnabled()) {
249                setInnerFocusable(false);
250                if (mDefocusListener != null) {
251                    mDefocusListener.onDefocus();
252                }
253                mShowImeOnInputConnection = false;
254            }
255        }
256
257        @Override
258        protected void onVisibilityChanged(View changedView, int visibility) {
259            super.onVisibilityChanged(changedView, visibility);
260
261            if (!isShown()) {
262                defocusIfNeeded();
263            }
264        }
265
266        @Override
267        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
268            super.onFocusChanged(focused, direction, previouslyFocusedRect);
269            if (!focused) {
270                defocusIfNeeded();
271            }
272        }
273
274        @Override
275        public boolean requestRectangleOnScreen(Rect r) {
276            r.top = mScrollY;
277            r.bottom = mScrollY + (mBottom - mTop);
278            return super.requestRectangleOnScreen(r);
279        }
280
281        @Override
282        public void getFocusedRect(Rect r) {
283            super.getFocusedRect(r);
284            r.top = mScrollY;
285            r.bottom = mScrollY + (mBottom - mTop);
286        }
287
288        @Override
289        public boolean onKeyPreIme(int keyCode, KeyEvent event) {
290            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
291                defocusIfNeeded();
292                final InputMethodManager imm = InputMethodManager.getInstance();
293                imm.hideSoftInputFromWindow(getWindowToken(), 0);
294                return true;
295            }
296            return super.onKeyPreIme(keyCode, event);
297        }
298
299        @Override
300        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
301            final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
302
303            if (mShowImeOnInputConnection && inputConnection != null) {
304                final InputMethodManager imm = InputMethodManager.getInstance();
305                if (imm != null) {
306                    // onCreateInputConnection is called by InputMethodManager in the middle of
307                    // setting up the connection to the IME; wait with requesting the IME until that
308                    // work has completed.
309                    post(new Runnable() {
310                        @Override
311                        public void run() {
312                            imm.viewClicked(RemoteEditText.this);
313                            imm.showSoftInput(RemoteEditText.this, 0);
314                        }
315                    });
316                }
317            }
318
319            return inputConnection;
320        }
321
322        @Override
323        public void onCommitCompletion(CompletionInfo text) {
324            clearComposingText();
325            setText(text.getText());
326            setSelection(getText().length());
327        }
328
329        void setInnerFocusable(boolean focusable) {
330            setFocusableInTouchMode(focusable);
331            setFocusable(focusable);
332            setCursorVisible(focusable);
333
334            if (focusable) {
335                requestFocus();
336                setBackground(mBackground);
337            } else {
338                setBackground(null);
339            }
340
341        }
342    }
343}
344