blob: 557f166dc1a6dba3c7d26cd0e5ef6426dc572931 [file] [log] [blame]
Adrian Roos497ab022015-02-10 20:49:33 +01001/*
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
Adrian Roos497ab022015-02-10 20:49:33 +010019import android.app.PendingIntent;
20import android.app.RemoteInput;
21import android.content.Context;
22import android.content.Intent;
Adrian Roosdc5b4532016-01-06 20:49:41 +010023import android.graphics.Rect;
Adrian Roos497ab022015-02-10 20:49:33 +010024import android.graphics.drawable.Drawable;
25import android.os.Bundle;
Adrian Roos245aa872015-12-07 14:53:53 -080026import android.text.Editable;
27import android.text.TextWatcher;
Adrian Roos497ab022015-02-10 20:49:33 +010028import android.util.AttributeSet;
29import android.util.Log;
30import android.view.KeyEvent;
31import android.view.LayoutInflater;
Adrian Roos0bd8a4b2016-03-14 16:21:44 -070032import android.view.MotionEvent;
Adrian Roos497ab022015-02-10 20:49:33 +010033import android.view.View;
34import android.view.ViewGroup;
Adrian Roos0bd8a4b2016-03-14 16:21:44 -070035import android.view.ViewParent;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080036import android.view.inputmethod.CompletionInfo;
Adrian Roos497ab022015-02-10 20:49:33 +010037import android.view.inputmethod.EditorInfo;
Adrian Roos1c0ca502015-10-07 12:20:42 -070038import android.view.inputmethod.InputConnection;
Adrian Roos497ab022015-02-10 20:49:33 +010039import android.view.inputmethod.InputMethodManager;
40import android.widget.EditText;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080041import android.widget.ImageButton;
42import android.widget.LinearLayout;
Adrian Roos497ab022015-02-10 20:49:33 +010043import android.widget.ProgressBar;
44import android.widget.TextView;
45
Winsonc0d70582016-01-29 10:24:39 -080046import com.android.systemui.R;
47import com.android.systemui.statusbar.NotificationData;
48import com.android.systemui.statusbar.RemoteInputController;
Adrian Roos5153d4a2016-03-22 10:01:56 -070049import com.android.systemui.statusbar.stack.ScrollContainer;
Adrian Roos1c0ca502015-10-07 12:20:42 -070050
Adrian Roos497ab022015-02-10 20:49:33 +010051/**
52 * Host for the remote input.
53 */
Adrian Roos245aa872015-12-07 14:53:53 -080054public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
Adrian Roos497ab022015-02-10 20:49:33 +010055
56 private static final String TAG = "RemoteInput";
57
Adrian Roosfe84e1f2015-11-04 15:55:39 -080058 // A marker object that let's us easily find views of this class.
59 public static final Object VIEW_TAG = new Object();
60
Adrian Roos497ab022015-02-10 20:49:33 +010061 private RemoteEditText mEditText;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080062 private ImageButton mSendButton;
Adrian Roos497ab022015-02-10 20:49:33 +010063 private ProgressBar mProgressBar;
64 private PendingIntent mPendingIntent;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080065 private RemoteInput[] mRemoteInputs;
Adrian Roos497ab022015-02-10 20:49:33 +010066 private RemoteInput mRemoteInput;
Adrian Roos1c0ca502015-10-07 12:20:42 -070067 private RemoteInputController mController;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080068
Adrian Roos1c0ca502015-10-07 12:20:42 -070069 private NotificationData.Entry mEntry;
Adrian Roos5153d4a2016-03-22 10:01:56 -070070
71 private ScrollContainer mScrollContainer;
72 private View mScrollContainerChild;
Adrian Roos497ab022015-02-10 20:49:33 +010073
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
Adrian Roosfe84e1f2015-11-04 15:55:39 -080084 mSendButton = (ImageButton) findViewById(R.id.remote_input_send);
85 mSendButton.setOnClickListener(this);
86
Adrian Roos497ab022015-02-10 20:49:33 +010087 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);
Adrian Roos245aa872015-12-07 14:53:53 -0800109 mEditText.addTextChangedListener(this);
Adrian Roos497ab022015-02-10 20:49:33 +0100110 mEditText.setInnerFocusable(false);
Adrian Roos5153d4a2016-03-22 10:01:56 -0700111 mEditText.mRemoteInputView = this;
Adrian Roos497ab022015-02-10 20:49:33 +0100112 }
113
114 private void sendRemoteInput() {
115 Bundle results = new Bundle();
116 results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
Adrian Roos1c0ca502015-10-07 12:20:42 -0700117 Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800118 RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
Adrian Roos497ab022015-02-10 20:49:33 +0100119 results);
120
121 mEditText.setEnabled(false);
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800122 mSendButton.setVisibility(INVISIBLE);
Adrian Roos497ab022015-02-10 20:49:33 +0100123 mProgressBar.setVisibility(VISIBLE);
Adrian Roosc0a579e2016-03-30 16:43:58 -0700124 mEntry.remoteInputText = mEditText.getText();
125 mController.addSpinning(mEntry.key);
Adrian Roos245aa872015-12-07 14:53:53 -0800126 mController.removeRemoteInput(mEntry);
127 mEditText.mShowImeOnInputConnection = false;
Adrian Roosc0a579e2016-03-30 16:43:58 -0700128 mController.remoteInputSent(mEntry);
Adrian Roos497ab022015-02-10 20:49:33 +0100129
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,
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800138 NotificationData.Entry entry,
Adrian Roos1c0ca502015-10-07 12:20:42 -0700139 RemoteInputController controller) {
Adrian Roos497ab022015-02-10 20:49:33 +0100140 RemoteInputView v = (RemoteInputView)
141 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
Adrian Roos1c0ca502015-10-07 12:20:42 -0700142 v.mController = controller;
143 v.mEntry = entry;
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800144 v.setTag(VIEW_TAG);
Adrian Roos497ab022015-02-10 20:49:33 +0100145
146 return v;
147 }
148
149 @Override
150 public void onClick(View v) {
151 if (v == mEditText) {
152 if (!mEditText.isFocusable()) {
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800153 focus();
Adrian Roos497ab022015-02-10 20:49:33 +0100154 }
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800155 } else if (v == mSendButton) {
156 sendRemoteInput();
Adrian Roos497ab022015-02-10 20:49:33 +0100157 }
158 }
159
Adrian Roos1c0ca502015-10-07 12:20:42 -0700160 public void onDefocus() {
161 mController.removeRemoteInput(mEntry);
Adrian Roos777ef562015-12-01 17:37:14 -0800162 mEntry.remoteInputText = mEditText.getText();
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800163 setVisibility(INVISIBLE);
Adrian Roos1c0ca502015-10-07 12:20:42 -0700164 }
165
166 @Override
Adrian Roos14503e22016-03-09 14:01:24 -0800167 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
Adrian Roos1c0ca502015-10-07 12:20:42 -0700177 protected void onDetachedFromWindow() {
178 super.onDetachedFromWindow();
Adrian Roos14503e22016-03-09 14:01:24 -0800179 if (mEntry.row.isChangingPosition()) {
180 return;
181 }
Adrian Roos1c0ca502015-10-07 12:20:42 -0700182 mController.removeRemoteInput(mEntry);
Adrian Roosc0a579e2016-03-30 16:43:58 -0700183 mController.removeSpinning(mEntry.key);
Adrian Roos1c0ca502015-10-07 12:20:42 -0700184 }
185
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800186 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() {
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800197 mController.addRemoteInput(mEntry);
Adrian Roosdc5b4532016-01-06 20:49:41 +0100198 mEditText.setInnerFocusable(true);
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800199 mEditText.mShowImeOnInputConnection = true;
Adrian Roos777ef562015-12-01 17:37:14 -0800200 mEditText.setText(mEntry.remoteInputText);
201 mEditText.setSelection(mEditText.getText().length());
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800202 mEditText.requestFocus();
Adrian Roos245aa872015-12-07 14:53:53 -0800203 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);
Adrian Roosc0a579e2016-03-30 16:43:58 -0700220 mController.removeSpinning(mEntry.key);
Adrian Roos245aa872015-12-07 14:53:53 -0800221 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();
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800238 }
239
Adrian Roos0bd8a4b2016-03-14 16:21:44 -0700240 public void close() {
241 mEditText.defocusIfNeeded();
242 }
243
244 @Override
245 public boolean onInterceptTouchEvent(MotionEvent ev) {
246 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Adrian Roos5153d4a2016-03-22 10:01:56 -0700247 findScrollContainer();
248 if (mScrollContainer != null) {
249 mScrollContainer.requestDisallowLongPress();
Adrian Roos0bd8a4b2016-03-14 16:21:44 -0700250 }
251 }
252 return super.onInterceptTouchEvent(ev);
253 }
254
Adrian Roos5153d4a2016-03-22 10:01:56 -0700255 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
Adrian Roos497ab022015-02-10 20:49:33 +0100275 /**
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;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700282 private RemoteInputView mRemoteInputView;
Adrian Roos1c0ca502015-10-07 12:20:42 -0700283 boolean mShowImeOnInputConnection;
Adrian Roos497ab022015-02-10 20:49:33 +0100284
285 public RemoteEditText(Context context, AttributeSet attrs) {
286 super(context, attrs);
287 mBackground = getBackground();
288 }
289
290 private void defocusIfNeeded() {
Adrian Roos5153d4a2016-03-22 10:01:56 -0700291 if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition()) {
Adrian Roos14503e22016-03-09 14:01:24 -0800292 return;
293 }
Adrian Roos497ab022015-02-10 20:49:33 +0100294 if (isFocusable() && isEnabled()) {
295 setInnerFocusable(false);
Adrian Roos5153d4a2016-03-22 10:01:56 -0700296 if (mRemoteInputView != null) {
297 mRemoteInputView.onDefocus();
Adrian Roos1c0ca502015-10-07 12:20:42 -0700298 }
299 mShowImeOnInputConnection = false;
Adrian Roos497ab022015-02-10 20:49:33 +0100300 }
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
Adrian Roos967f6192016-03-09 12:47:26 -0800313 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
314 super.onFocusChanged(focused, direction, previouslyFocusedRect);
315 if (!focused) {
316 defocusIfNeeded();
317 }
Adrian Roos497ab022015-02-10 20:49:33 +0100318 }
319
320 @Override
Adrian Roosdc5b4532016-01-06 20:49:41 +0100321 public void getFocusedRect(Rect r) {
322 super.getFocusedRect(r);
323 r.top = mScrollY;
324 r.bottom = mScrollY + (mBottom - mTop);
325 }
326
327 @Override
Adrian Roos5153d4a2016-03-22 10:01:56 -0700328 public boolean requestRectangleOnScreen(Rect rectangle) {
329 return mRemoteInputView.requestScrollTo();
330 }
331
332 @Override
Adrian Roos497ab022015-02-10 20:49:33 +0100333 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
Adrian Roos777ef562015-12-01 17:37:14 -0800334 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
Adrian Roos497ab022015-02-10 20:49:33 +0100335 defocusIfNeeded();
Adrian Roos777ef562015-12-01 17:37:14 -0800336 final InputMethodManager imm = InputMethodManager.getInstance();
337 imm.hideSoftInputFromWindow(getWindowToken(), 0);
338 return true;
Adrian Roos497ab022015-02-10 20:49:33 +0100339 }
340 return super.onKeyPreIme(keyCode, event);
341 }
342
Adrian Roos1c0ca502015-10-07 12:20:42 -0700343 @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 }
Adrian Roos497ab022015-02-10 20:49:33 +0100365
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800366 @Override
367 public void onCommitCompletion(CompletionInfo text) {
368 clearComposingText();
369 setText(text.getText());
370 setSelection(getText().length());
371 }
372
Adrian Roos497ab022015-02-10 20:49:33 +0100373 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}