OSDN Git Service

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