blob: 44ec2831dd9b79ca34d6a6154b3b5f1f1fa65f31 [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 Roosaf06bf22016-07-15 12:26:49 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Adrian Roos4c1fcc82016-03-31 14:39:39 -070021import android.app.Notification;
Adrian Roos497ab022015-02-10 20:49:33 +010022import android.app.PendingIntent;
23import android.app.RemoteInput;
24import android.content.Context;
25import android.content.Intent;
Makoto Onukid6e1f3b2016-06-14 11:17:59 -070026import android.content.pm.ShortcutManager;
Adrian Roosdc5b4532016-01-06 20:49:41 +010027import android.graphics.Rect;
Adrian Roos497ab022015-02-10 20:49:33 +010028import android.graphics.drawable.Drawable;
29import android.os.Bundle;
Adrian Roos245aa872015-12-07 14:53:53 -080030import android.text.Editable;
31import android.text.TextWatcher;
Adrian Roos497ab022015-02-10 20:49:33 +010032import android.util.AttributeSet;
33import android.util.Log;
34import android.view.KeyEvent;
35import android.view.LayoutInflater;
Adrian Roos0bd8a4b2016-03-14 16:21:44 -070036import android.view.MotionEvent;
Adrian Roos497ab022015-02-10 20:49:33 +010037import android.view.View;
Adrian Roosaf06bf22016-07-15 12:26:49 -070038import android.view.ViewAnimationUtils;
Adrian Roos497ab022015-02-10 20:49:33 +010039import android.view.ViewGroup;
Adrian Roos0bd8a4b2016-03-14 16:21:44 -070040import android.view.ViewParent;
Adrian Roosf17c86b2016-09-07 14:08:40 -070041import android.view.accessibility.AccessibilityEvent;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080042import android.view.inputmethod.CompletionInfo;
Adrian Roos497ab022015-02-10 20:49:33 +010043import android.view.inputmethod.EditorInfo;
Adrian Roos1c0ca502015-10-07 12:20:42 -070044import android.view.inputmethod.InputConnection;
Adrian Roos497ab022015-02-10 20:49:33 +010045import android.view.inputmethod.InputMethodManager;
46import android.widget.EditText;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080047import android.widget.ImageButton;
48import android.widget.LinearLayout;
Adrian Roos497ab022015-02-10 20:49:33 +010049import android.widget.ProgressBar;
50import android.widget.TextView;
51
Adrian Roosceeb04c2016-04-25 14:00:54 -070052import com.android.internal.logging.MetricsLogger;
Tamas Berghammercbd3f0c2016-06-22 15:21:38 +010053import com.android.internal.logging.nano.MetricsProto;
Adrian Roosaf06bf22016-07-15 12:26:49 -070054import com.android.systemui.Interpolators;
Winsonc0d70582016-01-29 10:24:39 -080055import com.android.systemui.R;
Adrian Roos4a579672016-05-24 16:54:37 -070056import com.android.systemui.statusbar.ExpandableView;
Winsonc0d70582016-01-29 10:24:39 -080057import com.android.systemui.statusbar.NotificationData;
58import com.android.systemui.statusbar.RemoteInputController;
Adrian Roos5153d4a2016-03-22 10:01:56 -070059import com.android.systemui.statusbar.stack.ScrollContainer;
Adrian Roosaf06bf22016-07-15 12:26:49 -070060import com.android.systemui.statusbar.stack.StackStateAnimator;
Adrian Roos1c0ca502015-10-07 12:20:42 -070061
Adrian Roos497ab022015-02-10 20:49:33 +010062/**
63 * Host for the remote input.
64 */
Adrian Roos245aa872015-12-07 14:53:53 -080065public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
Adrian Roos497ab022015-02-10 20:49:33 +010066
67 private static final String TAG = "RemoteInput";
68
Adrian Roosfe84e1f2015-11-04 15:55:39 -080069 // A marker object that let's us easily find views of this class.
70 public static final Object VIEW_TAG = new Object();
71
Adrian Roos7813dd72016-09-23 17:12:17 -070072 public final Object mToken = new Object();
73
Adrian Roos497ab022015-02-10 20:49:33 +010074 private RemoteEditText mEditText;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080075 private ImageButton mSendButton;
Adrian Roos497ab022015-02-10 20:49:33 +010076 private ProgressBar mProgressBar;
77 private PendingIntent mPendingIntent;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080078 private RemoteInput[] mRemoteInputs;
Adrian Roos497ab022015-02-10 20:49:33 +010079 private RemoteInput mRemoteInput;
Adrian Roos1c0ca502015-10-07 12:20:42 -070080 private RemoteInputController mController;
Adrian Roosfe84e1f2015-11-04 15:55:39 -080081
Adrian Roos1c0ca502015-10-07 12:20:42 -070082 private NotificationData.Entry mEntry;
Adrian Roos5153d4a2016-03-22 10:01:56 -070083
84 private ScrollContainer mScrollContainer;
85 private View mScrollContainerChild;
Adrian Roosd009ab12016-05-20 17:58:53 -070086 private boolean mRemoved;
Adrian Roos497ab022015-02-10 20:49:33 +010087
Adrian Roosaf06bf22016-07-15 12:26:49 -070088 private int mRevealCx;
89 private int mRevealCy;
90 private int mRevealR;
91
Adrian Roosf17c86b2016-09-07 14:08:40 -070092 private boolean mResetting;
93
Adrian Roos497ab022015-02-10 20:49:33 +010094 public RemoteInputView(Context context, AttributeSet attrs) {
95 super(context, attrs);
96 }
97
98 @Override
99 protected void onFinishInflate() {
100 super.onFinishInflate();
101
102 mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
103
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800104 mSendButton = (ImageButton) findViewById(R.id.remote_input_send);
105 mSendButton.setOnClickListener(this);
106
Adrian Roos497ab022015-02-10 20:49:33 +0100107 mEditText = (RemoteEditText) getChildAt(0);
108 mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
109 @Override
110 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Adrian Roos497ab022015-02-10 20:49:33 +0100111 final boolean isSoftImeEvent = event == null
112 && (actionId == EditorInfo.IME_ACTION_DONE
113 || actionId == EditorInfo.IME_ACTION_NEXT
114 || actionId == EditorInfo.IME_ACTION_SEND);
115 final boolean isKeyboardEnterKey = event != null
116 && KeyEvent.isConfirmKey(event.getKeyCode())
117 && event.getAction() == KeyEvent.ACTION_DOWN;
118
119 if (isSoftImeEvent || isKeyboardEnterKey) {
Adrian Roos6db43cb2016-04-29 10:45:38 -0700120 if (mEditText.length() > 0) {
121 sendRemoteInput();
122 }
123 // Consume action to prevent IME from closing.
Adrian Roos497ab022015-02-10 20:49:33 +0100124 return true;
125 }
126 return false;
127 }
128 });
Adrian Roos245aa872015-12-07 14:53:53 -0800129 mEditText.addTextChangedListener(this);
Adrian Roos497ab022015-02-10 20:49:33 +0100130 mEditText.setInnerFocusable(false);
Adrian Roos5153d4a2016-03-22 10:01:56 -0700131 mEditText.mRemoteInputView = this;
Adrian Roos497ab022015-02-10 20:49:33 +0100132 }
133
134 private void sendRemoteInput() {
135 Bundle results = new Bundle();
136 results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
Adrian Roos1c0ca502015-10-07 12:20:42 -0700137 Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800138 RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
Adrian Roos497ab022015-02-10 20:49:33 +0100139 results);
140
141 mEditText.setEnabled(false);
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800142 mSendButton.setVisibility(INVISIBLE);
Adrian Roos497ab022015-02-10 20:49:33 +0100143 mProgressBar.setVisibility(VISIBLE);
Adrian Roosc0a579e2016-03-30 16:43:58 -0700144 mEntry.remoteInputText = mEditText.getText();
Adrian Roos7813dd72016-09-23 17:12:17 -0700145 mController.addSpinning(mEntry.key, mToken);
146 mController.removeRemoteInput(mEntry, mToken);
Adrian Roos245aa872015-12-07 14:53:53 -0800147 mEditText.mShowImeOnInputConnection = false;
Adrian Roosc0a579e2016-03-30 16:43:58 -0700148 mController.remoteInputSent(mEntry);
Adrian Roos497ab022015-02-10 20:49:33 +0100149
Makoto Onukid6e1f3b2016-06-14 11:17:59 -0700150 // Tell ShortcutManager that this package has been "activated". ShortcutManager
151 // will reset the throttling for this package.
152 // Strictly speaking, the intent receiver may be different from the notification publisher,
153 // but that's an edge case, and also because we can't always know which package will receive
154 // an intent, so we just reset for the publisher.
155 getContext().getSystemService(ShortcutManager.class).onApplicationActive(
156 mEntry.notification.getPackageName(),
157 mEntry.notification.getUser().getIdentifier());
158
Adrian Roosceeb04c2016-04-25 14:00:54 -0700159 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_SEND,
160 mEntry.notification.getPackageName());
Adrian Roos497ab022015-02-10 20:49:33 +0100161 try {
162 mPendingIntent.send(mContext, 0, fillInIntent);
163 } catch (PendingIntent.CanceledException e) {
164 Log.i(TAG, "Unable to send remote input result", e);
Adrian Roosceeb04c2016-04-25 14:00:54 -0700165 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_FAIL,
166 mEntry.notification.getPackageName());
Adrian Roos497ab022015-02-10 20:49:33 +0100167 }
168 }
169
170 public static RemoteInputView inflate(Context context, ViewGroup root,
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800171 NotificationData.Entry entry,
Adrian Roos1c0ca502015-10-07 12:20:42 -0700172 RemoteInputController controller) {
Adrian Roos497ab022015-02-10 20:49:33 +0100173 RemoteInputView v = (RemoteInputView)
174 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
Adrian Roos1c0ca502015-10-07 12:20:42 -0700175 v.mController = controller;
176 v.mEntry = entry;
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800177 v.setTag(VIEW_TAG);
Adrian Roos497ab022015-02-10 20:49:33 +0100178
179 return v;
180 }
181
182 @Override
183 public void onClick(View v) {
Adrian Roos5389a172016-04-20 13:58:03 -0700184 if (v == mSendButton) {
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800185 sendRemoteInput();
Adrian Roos497ab022015-02-10 20:49:33 +0100186 }
187 }
188
Adrian Roos5389a172016-04-20 13:58:03 -0700189 @Override
190 public boolean onTouchEvent(MotionEvent event) {
191 super.onTouchEvent(event);
192
193 // We never want for a touch to escape to an outer view or one we covered.
194 return true;
195 }
196
Adrian Roosaf06bf22016-07-15 12:26:49 -0700197 private void onDefocus(boolean animate) {
Adrian Roos7813dd72016-09-23 17:12:17 -0700198 mController.removeRemoteInput(mEntry, mToken);
Adrian Roos777ef562015-12-01 17:37:14 -0800199 mEntry.remoteInputText = mEditText.getText();
Adrian Roosd009ab12016-05-20 17:58:53 -0700200
201 // During removal, we get reattached and lose focus. Not hiding in that
202 // case to prevent flicker.
203 if (!mRemoved) {
Adrian Roosaf06bf22016-07-15 12:26:49 -0700204 if (animate && mRevealR > 0) {
205 Animator reveal = ViewAnimationUtils.createCircularReveal(
206 this, mRevealCx, mRevealCy, mRevealR, 0);
207 reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
208 reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
209 reveal.addListener(new AnimatorListenerAdapter() {
210 @Override
211 public void onAnimationEnd(Animator animation) {
212 setVisibility(INVISIBLE);
213 }
214 });
215 reveal.start();
216 } else {
217 setVisibility(INVISIBLE);
218 }
Adrian Roosd009ab12016-05-20 17:58:53 -0700219 }
Adrian Roosceeb04c2016-04-25 14:00:54 -0700220 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_CLOSE,
221 mEntry.notification.getPackageName());
Adrian Roos1c0ca502015-10-07 12:20:42 -0700222 }
223
224 @Override
Adrian Roos14503e22016-03-09 14:01:24 -0800225 protected void onAttachedToWindow() {
226 super.onAttachedToWindow();
227 if (mEntry.row.isChangingPosition()) {
228 if (getVisibility() == VISIBLE && mEditText.isFocusable()) {
229 mEditText.requestFocus();
230 }
231 }
232 }
233
234 @Override
Adrian Roos1c0ca502015-10-07 12:20:42 -0700235 protected void onDetachedFromWindow() {
236 super.onDetachedFromWindow();
Adrian Roos7813dd72016-09-23 17:12:17 -0700237 if (mEntry.row.isChangingPosition() || isTemporarilyDetached()) {
Adrian Roos14503e22016-03-09 14:01:24 -0800238 return;
239 }
Adrian Roos7813dd72016-09-23 17:12:17 -0700240 mController.removeRemoteInput(mEntry, mToken);
241 mController.removeSpinning(mEntry.key, mToken);
Adrian Roos1c0ca502015-10-07 12:20:42 -0700242 }
243
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800244 public void setPendingIntent(PendingIntent pendingIntent) {
245 mPendingIntent = pendingIntent;
246 }
247
248 public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
249 mRemoteInputs = remoteInputs;
250 mRemoteInput = remoteInput;
251 mEditText.setHint(mRemoteInput.getLabel());
252 }
253
Adrian Roosaf06bf22016-07-15 12:26:49 -0700254 public void focusAnimated() {
255 if (getVisibility() != VISIBLE) {
256 Animator animator = ViewAnimationUtils.createCircularReveal(
257 this, mRevealCx, mRevealCy, 0, mRevealR);
258 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
259 animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
260 animator.start();
261 }
262 focus();
263 }
264
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800265 public void focus() {
Adrian Roosceeb04c2016-04-25 14:00:54 -0700266 MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_OPEN,
267 mEntry.notification.getPackageName());
268
Adrian Roos4c1fcc82016-03-31 14:39:39 -0700269 setVisibility(VISIBLE);
Adrian Roos7813dd72016-09-23 17:12:17 -0700270 mController.addRemoteInput(mEntry, mToken);
Adrian Roosdc5b4532016-01-06 20:49:41 +0100271 mEditText.setInnerFocusable(true);
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800272 mEditText.mShowImeOnInputConnection = true;
Adrian Roos777ef562015-12-01 17:37:14 -0800273 mEditText.setText(mEntry.remoteInputText);
274 mEditText.setSelection(mEditText.getText().length());
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800275 mEditText.requestFocus();
Adrian Roos245aa872015-12-07 14:53:53 -0800276 updateSendButton();
277 }
278
Adrian Roos0789ee02016-06-01 11:34:58 -0700279 public void onNotificationUpdateOrReset() {
Adrian Roos245aa872015-12-07 14:53:53 -0800280 boolean sending = mProgressBar.getVisibility() == VISIBLE;
281
282 if (sending) {
283 // Update came in after we sent the reply, time to reset.
284 reset();
285 }
286 }
287
288 private void reset() {
Adrian Roosf17c86b2016-09-07 14:08:40 -0700289 mResetting = true;
290
Adrian Roos245aa872015-12-07 14:53:53 -0800291 mEditText.getText().clear();
292 mEditText.setEnabled(true);
293 mSendButton.setVisibility(VISIBLE);
294 mProgressBar.setVisibility(INVISIBLE);
Adrian Roos7813dd72016-09-23 17:12:17 -0700295 mController.removeSpinning(mEntry.key, mToken);
Adrian Roos245aa872015-12-07 14:53:53 -0800296 updateSendButton();
Adrian Roosaf06bf22016-07-15 12:26:49 -0700297 onDefocus(false /* animate */);
Adrian Roosf17c86b2016-09-07 14:08:40 -0700298
299 mResetting = false;
300 }
301
302 @Override
303 public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
304 if (mResetting && child == mEditText) {
305 // Suppress text events if it happens during resetting. Ideally this would be
306 // suppressed by the text view not being shown, but that doesn't work here because it
307 // needs to stay visible for the animation.
308 return false;
309 }
310 return super.onRequestSendAccessibilityEvent(child, event);
Adrian Roos245aa872015-12-07 14:53:53 -0800311 }
312
313 private void updateSendButton() {
314 mSendButton.setEnabled(mEditText.getText().length() != 0);
315 }
316
317 @Override
318 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
319
320 @Override
321 public void onTextChanged(CharSequence s, int start, int before, int count) {}
322
323 @Override
324 public void afterTextChanged(Editable s) {
325 updateSendButton();
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800326 }
327
Adrian Roos0bd8a4b2016-03-14 16:21:44 -0700328 public void close() {
Adrian Roosaf06bf22016-07-15 12:26:49 -0700329 mEditText.defocusIfNeeded(false /* animated */);
Adrian Roos0bd8a4b2016-03-14 16:21:44 -0700330 }
331
332 @Override
333 public boolean onInterceptTouchEvent(MotionEvent ev) {
334 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
Adrian Roos5153d4a2016-03-22 10:01:56 -0700335 findScrollContainer();
336 if (mScrollContainer != null) {
337 mScrollContainer.requestDisallowLongPress();
Adrian Roosfa139752016-04-27 09:59:08 -0700338 mScrollContainer.requestDisallowDismiss();
Adrian Roos0bd8a4b2016-03-14 16:21:44 -0700339 }
340 }
341 return super.onInterceptTouchEvent(ev);
342 }
343
Adrian Roos5153d4a2016-03-22 10:01:56 -0700344 public boolean requestScrollTo() {
345 findScrollContainer();
Adrian Roos181385c2016-05-05 17:45:44 -0400346 mScrollContainer.lockScrollTo(mScrollContainerChild);
Adrian Roos5153d4a2016-03-22 10:01:56 -0700347 return true;
348 }
349
350 private void findScrollContainer() {
351 if (mScrollContainer == null) {
Adrian Roos4a579672016-05-24 16:54:37 -0700352 mScrollContainerChild = null;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700353 ViewParent p = this;
354 while (p != null) {
Adrian Roos4a579672016-05-24 16:54:37 -0700355 if (mScrollContainerChild == null && p instanceof ExpandableView) {
356 mScrollContainerChild = (View) p;
357 }
Adrian Roos5153d4a2016-03-22 10:01:56 -0700358 if (p.getParent() instanceof ScrollContainer) {
359 mScrollContainer = (ScrollContainer) p.getParent();
Adrian Roos4a579672016-05-24 16:54:37 -0700360 if (mScrollContainerChild == null) {
361 mScrollContainerChild = (View) p;
362 }
Adrian Roos5153d4a2016-03-22 10:01:56 -0700363 break;
364 }
365 p = p.getParent();
366 }
367 }
368 }
369
Adrian Roos4c1fcc82016-03-31 14:39:39 -0700370 public boolean isActive() {
Adrian Roos9550f2e2016-08-23 18:23:01 +0200371 return mEditText.isFocused() && mEditText.isEnabled();
Adrian Roos4c1fcc82016-03-31 14:39:39 -0700372 }
373
374 public void stealFocusFrom(RemoteInputView other) {
375 other.close();
376 setPendingIntent(other.mPendingIntent);
377 setRemoteInput(other.mRemoteInputs, other.mRemoteInput);
Adrian Roosaf06bf22016-07-15 12:26:49 -0700378 setRevealParameters(other.mRevealCx, other.mRevealCy, other.mRevealR);
Adrian Roos4c1fcc82016-03-31 14:39:39 -0700379 focus();
380 }
381
382 /**
383 * Tries to find an action in {@param actions} that matches the current pending intent
384 * of this view and updates its state to that of the found action
385 *
386 * @return true if a matching action was found, false otherwise
387 */
388 public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
Adrian Roos4c1fcc82016-03-31 14:39:39 -0700389 if (mPendingIntent == null || actions == null) {
390 return false;
391 }
392 Intent current = mPendingIntent.getIntent();
393 if (current == null) {
394 return false;
395 }
396
397 for (Notification.Action a : actions) {
398 RemoteInput[] inputs = a.getRemoteInputs();
399 if (a.actionIntent == null || inputs == null) {
400 continue;
401 }
402 Intent candidate = a.actionIntent.getIntent();
403 if (!current.filterEquals(candidate)) {
404 continue;
405 }
406
407 RemoteInput input = null;
408 for (RemoteInput i : inputs) {
409 if (i.getAllowFreeFormInput()) {
410 input = i;
411 }
412 }
413 if (input == null) {
414 continue;
415 }
416 setPendingIntent(a.actionIntent);
417 setRemoteInput(inputs, input);
418 return true;
419 }
420 return false;
421 }
422
423 public PendingIntent getPendingIntent() {
424 return mPendingIntent;
425 }
426
Adrian Roosd009ab12016-05-20 17:58:53 -0700427 public void setRemoved() {
428 mRemoved = true;
429 }
430
Adrian Roosaf06bf22016-07-15 12:26:49 -0700431 public void setRevealParameters(int cx, int cy, int r) {
432 mRevealCx = cx;
433 mRevealCy = cy;
434 mRevealR = r;
435 }
436
Adrian Roos7813dd72016-09-23 17:12:17 -0700437 @Override
438 public void dispatchStartTemporaryDetach() {
439 super.dispatchStartTemporaryDetach();
440 // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
441 // won't lose IME focus.
442 detachViewFromParent(mEditText);
443 }
444
445 @Override
446 public void dispatchFinishTemporaryDetach() {
447 if (isAttachedToWindow()) {
448 attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
449 } else {
450 removeDetachedView(mEditText, false /* animate */);
451 }
452 super.dispatchFinishTemporaryDetach();
453 }
454
Adrian Roos497ab022015-02-10 20:49:33 +0100455 /**
456 * An EditText that changes appearance based on whether it's focusable and becomes
457 * un-focusable whenever the user navigates away from it or it becomes invisible.
458 */
459 public static class RemoteEditText extends EditText {
460
461 private final Drawable mBackground;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700462 private RemoteInputView mRemoteInputView;
Adrian Roos1c0ca502015-10-07 12:20:42 -0700463 boolean mShowImeOnInputConnection;
Adrian Roos497ab022015-02-10 20:49:33 +0100464
465 public RemoteEditText(Context context, AttributeSet attrs) {
466 super(context, attrs);
467 mBackground = getBackground();
468 }
469
Adrian Roosaf06bf22016-07-15 12:26:49 -0700470 private void defocusIfNeeded(boolean animate) {
Adrian Roos7813dd72016-09-23 17:12:17 -0700471 if (mRemoteInputView != null && mRemoteInputView.mEntry.row.isChangingPosition()
472 || isTemporarilyDetached()) {
473 if (isTemporarilyDetached()) {
474 // We might get reattached but then the other one of HUN / expanded might steal
475 // our focus, so we'll need to save our text here.
476 if (mRemoteInputView != null) {
477 mRemoteInputView.mEntry.remoteInputText = getText();
478 }
479 }
Adrian Roos14503e22016-03-09 14:01:24 -0800480 return;
481 }
Adrian Roos497ab022015-02-10 20:49:33 +0100482 if (isFocusable() && isEnabled()) {
483 setInnerFocusable(false);
Adrian Roos5153d4a2016-03-22 10:01:56 -0700484 if (mRemoteInputView != null) {
Adrian Roosaf06bf22016-07-15 12:26:49 -0700485 mRemoteInputView.onDefocus(animate);
Adrian Roos1c0ca502015-10-07 12:20:42 -0700486 }
487 mShowImeOnInputConnection = false;
Adrian Roos497ab022015-02-10 20:49:33 +0100488 }
489 }
490
491 @Override
492 protected void onVisibilityChanged(View changedView, int visibility) {
493 super.onVisibilityChanged(changedView, visibility);
494
495 if (!isShown()) {
Adrian Roosaf06bf22016-07-15 12:26:49 -0700496 defocusIfNeeded(false /* animate */);
Adrian Roos497ab022015-02-10 20:49:33 +0100497 }
498 }
499
500 @Override
Adrian Roos967f6192016-03-09 12:47:26 -0800501 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
502 super.onFocusChanged(focused, direction, previouslyFocusedRect);
503 if (!focused) {
Adrian Roosaf06bf22016-07-15 12:26:49 -0700504 defocusIfNeeded(true /* animate */);
Adrian Roos967f6192016-03-09 12:47:26 -0800505 }
Adrian Roos497ab022015-02-10 20:49:33 +0100506 }
507
508 @Override
Adrian Roosdc5b4532016-01-06 20:49:41 +0100509 public void getFocusedRect(Rect r) {
510 super.getFocusedRect(r);
511 r.top = mScrollY;
512 r.bottom = mScrollY + (mBottom - mTop);
513 }
514
515 @Override
Adrian Roos5153d4a2016-03-22 10:01:56 -0700516 public boolean requestRectangleOnScreen(Rect rectangle) {
517 return mRemoteInputView.requestScrollTo();
518 }
519
520 @Override
Adrian Roos56cf73a2016-07-18 14:23:32 -0700521 public boolean onKeyDown(int keyCode, KeyEvent event) {
522 if (keyCode == KeyEvent.KEYCODE_BACK) {
523 // Eat the DOWN event here to prevent any default behavior.
Adrian Roos777ef562015-12-01 17:37:14 -0800524 return true;
Adrian Roos497ab022015-02-10 20:49:33 +0100525 }
Adrian Roos56cf73a2016-07-18 14:23:32 -0700526 return super.onKeyDown(keyCode, event);
527 }
528
529 @Override
530 public boolean onKeyUp(int keyCode, KeyEvent event) {
531 if (keyCode == KeyEvent.KEYCODE_BACK) {
532 defocusIfNeeded(true /* animate */);
533 return true;
534 }
535 return super.onKeyUp(keyCode, event);
Adrian Roos497ab022015-02-10 20:49:33 +0100536 }
537
Adrian Roos1c0ca502015-10-07 12:20:42 -0700538 @Override
Adrian Roosd009ab12016-05-20 17:58:53 -0700539 public boolean onCheckIsTextEditor() {
540 // Stop being editable while we're being removed. During removal, we get reattached,
541 // and editable views get their spellchecking state re-evaluated which is too costly
542 // during the removal animation.
543 boolean flyingOut = mRemoteInputView != null && mRemoteInputView.mRemoved;
544 return !flyingOut && super.onCheckIsTextEditor();
545 }
546
547 @Override
Adrian Roos1c0ca502015-10-07 12:20:42 -0700548 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
549 final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
550
551 if (mShowImeOnInputConnection && inputConnection != null) {
552 final InputMethodManager imm = InputMethodManager.getInstance();
553 if (imm != null) {
554 // onCreateInputConnection is called by InputMethodManager in the middle of
555 // setting up the connection to the IME; wait with requesting the IME until that
556 // work has completed.
557 post(new Runnable() {
558 @Override
559 public void run() {
560 imm.viewClicked(RemoteEditText.this);
561 imm.showSoftInput(RemoteEditText.this, 0);
562 }
563 });
564 }
565 }
566
567 return inputConnection;
568 }
Adrian Roos497ab022015-02-10 20:49:33 +0100569
Adrian Roosfe84e1f2015-11-04 15:55:39 -0800570 @Override
571 public void onCommitCompletion(CompletionInfo text) {
572 clearComposingText();
573 setText(text.getText());
574 setSelection(getText().length());
575 }
576
Adrian Roos497ab022015-02-10 20:49:33 +0100577 void setInnerFocusable(boolean focusable) {
578 setFocusableInTouchMode(focusable);
579 setFocusable(focusable);
580 setCursorVisible(focusable);
581
582 if (focusable) {
583 requestFocus();
584 setBackground(mBackground);
585 } else {
586 setBackground(null);
587 }
588
589 }
590 }
591}