2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com.android.systemui.statusbar.policy;
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;
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;
52 * Host for the remote input.
54 public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
56 private static final String TAG = "RemoteInput";
58 // A marker object that let's us easily find views of this class.
59 public static final Object VIEW_TAG = new Object();
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;
69 private NotificationData.Entry mEntry;
71 private ScrollContainer mScrollContainer;
72 private View mScrollContainerChild;
74 public RemoteInputView(Context context, AttributeSet attrs) {
75 super(context, attrs);
79 protected void onFinishInflate() {
80 super.onFinishInflate();
82 mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
84 mSendButton = (ImageButton) findViewById(R.id.remote_input_send);
85 mSendButton.setOnClickListener(this);
87 mEditText = (RemoteEditText) getChildAt(0);
88 mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
90 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
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;
101 if (isSoftImeEvent || isKeyboardEnterKey) {
108 mEditText.setOnClickListener(this);
109 mEditText.addTextChangedListener(this);
110 mEditText.setInnerFocusable(false);
111 mEditText.mRemoteInputView = this;
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,
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);
131 mPendingIntent.send(mContext, 0, fillInIntent);
132 } catch (PendingIntent.CanceledException e) {
133 Log.i(TAG, "Unable to send remote input result", e);
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;
150 public void onClick(View v) {
151 if (v == mEditText) {
152 if (!mEditText.isFocusable()) {
155 } else if (v == mSendButton) {
160 public void onDefocus() {
161 mController.removeRemoteInput(mEntry);
162 mEntry.remoteInputText = mEditText.getText();
163 setVisibility(INVISIBLE);
167 protected void onAttachedToWindow() {
168 super.onAttachedToWindow();
169 if (mEntry.row.isChangingPosition()) {
170 if (getVisibility() == VISIBLE && mEditText.isFocusable()) {
171 mEditText.requestFocus();
177 protected void onDetachedFromWindow() {
178 super.onDetachedFromWindow();
179 if (mEntry.row.isChangingPosition()) {
182 mController.removeRemoteInput(mEntry);
183 mController.removeSpinning(mEntry.key);
186 public void setPendingIntent(PendingIntent pendingIntent) {
187 mPendingIntent = pendingIntent;
190 public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
191 mRemoteInputs = remoteInputs;
192 mRemoteInput = remoteInput;
193 mEditText.setHint(mRemoteInput.getLabel());
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();
206 public void onNotificationUpdate() {
207 boolean sending = mProgressBar.getVisibility() == VISIBLE;
210 // Update came in after we sent the reply, time to reset.
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);
225 private void updateSendButton() {
226 mSendButton.setEnabled(mEditText.getText().length() != 0);
230 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
233 public void onTextChanged(CharSequence s, int start, int before, int count) {}
236 public void afterTextChanged(Editable s) {
240 public void close() {
241 mEditText.defocusIfNeeded();
245 public boolean onInterceptTouchEvent(MotionEvent ev) {
246 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
247 findScrollContainer();
248 if (mScrollContainer != null) {
249 mScrollContainer.requestDisallowLongPress();
252 return super.onInterceptTouchEvent(ev);
255 public boolean requestScrollTo() {
256 findScrollContainer();
257 mScrollContainer.scrollTo(mScrollContainerChild);
261 private void findScrollContainer() {
262 if (mScrollContainer == null) {
265 if (p.getParent() instanceof ScrollContainer) {
266 mScrollContainer = (ScrollContainer) p.getParent();
267 mScrollContainerChild = (View) p;
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.
279 public static class RemoteEditText extends EditText {
281 private final Drawable mBackground;
282 private RemoteInputView mRemoteInputView;
283 boolean mShowImeOnInputConnection;
285 public RemoteEditText(Context context, AttributeSet attrs) {
286 super(context, attrs);
287 mBackground = getBackground();
290 private void defocusIfNeeded() {
291 if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition()) {
294 if (isFocusable() && isEnabled()) {
295 setInnerFocusable(false);
296 if (mRemoteInputView != null) {
297 mRemoteInputView.onDefocus();
299 mShowImeOnInputConnection = false;
304 protected void onVisibilityChanged(View changedView, int visibility) {
305 super.onVisibilityChanged(changedView, visibility);
313 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
314 super.onFocusChanged(focused, direction, previouslyFocusedRect);
321 public void getFocusedRect(Rect r) {
322 super.getFocusedRect(r);
324 r.bottom = mScrollY + (mBottom - mTop);
328 public boolean requestRectangleOnScreen(Rect rectangle) {
329 return mRemoteInputView.requestScrollTo();
333 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
334 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
336 final InputMethodManager imm = InputMethodManager.getInstance();
337 imm.hideSoftInputFromWindow(getWindowToken(), 0);
340 return super.onKeyPreIme(keyCode, event);
344 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
345 final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
347 if (mShowImeOnInputConnection && inputConnection != null) {
348 final InputMethodManager imm = InputMethodManager.getInstance();
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() {
356 imm.viewClicked(RemoteEditText.this);
357 imm.showSoftInput(RemoteEditText.this, 0);
363 return inputConnection;
367 public void onCommitCompletion(CompletionInfo text) {
368 clearComposingText();
369 setText(text.getText());
370 setSelection(getText().length());
373 void setInnerFocusable(boolean focusable) {
374 setFocusableInTouchMode(focusable);
375 setFocusable(focusable);
376 setCursorVisible(focusable);
380 setBackground(mBackground);