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.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.annotation.Nullable;
22 import android.app.ActivityManager;
23 import android.app.Notification;
24 import android.app.PendingIntent;
25 import android.app.RemoteInput;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.ShortcutManager;
29 import android.graphics.Rect;
30 import android.graphics.drawable.Drawable;
31 import android.os.Bundle;
32 import android.os.SystemClock;
33 import android.os.UserHandle;
34 import android.text.Editable;
35 import android.text.SpannedString;
36 import android.text.TextWatcher;
37 import android.util.AttributeSet;
38 import android.util.Log;
39 import android.view.KeyEvent;
40 import android.view.LayoutInflater;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewAnimationUtils;
44 import android.view.ViewGroup;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.view.inputmethod.CompletionInfo;
47 import android.view.inputmethod.EditorInfo;
48 import android.view.inputmethod.InputConnection;
49 import android.view.inputmethod.InputMethodManager;
50 import android.widget.EditText;
51 import android.widget.ImageButton;
52 import android.widget.LinearLayout;
53 import android.widget.ProgressBar;
54 import android.widget.TextView;
55 
56 import com.android.internal.logging.MetricsLogger;
57 import com.android.internal.logging.nano.MetricsProto;
58 import com.android.systemui.Dependency;
59 import com.android.systemui.Interpolators;
60 import com.android.systemui.R;
61 import com.android.systemui.statusbar.RemoteInputController;
62 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
63 import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
64 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
65 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
66 import com.android.systemui.statusbar.phone.LightBarController;
67 
68 import java.util.function.Consumer;
69 
70 /**
71  * Host for the remote input.
72  */
73 public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher {
74 
75     private static final String TAG = "RemoteInput";
76 
77     // A marker object that let's us easily find views of this class.
78     public static final Object VIEW_TAG = new Object();
79 
80     public final Object mToken = new Object();
81 
82     private RemoteEditText mEditText;
83     private ImageButton mSendButton;
84     private ProgressBar mProgressBar;
85     private PendingIntent mPendingIntent;
86     private RemoteInput[] mRemoteInputs;
87     private RemoteInput mRemoteInput;
88     private RemoteInputController mController;
89     private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
90 
91     private NotificationEntry mEntry;
92 
93     private boolean mRemoved;
94 
95     private int mRevealCx;
96     private int mRevealCy;
97     private int mRevealR;
98 
99     private boolean mResetting;
100     private NotificationViewWrapper mWrapper;
101     private Consumer<Boolean> mOnVisibilityChangedListener;
102 
RemoteInputView(Context context, AttributeSet attrs)103     public RemoteInputView(Context context, AttributeSet attrs) {
104         super(context, attrs);
105         mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class);
106     }
107 
108     @Override
onFinishInflate()109     protected void onFinishInflate() {
110         super.onFinishInflate();
111 
112         mProgressBar = findViewById(R.id.remote_input_progress);
113 
114         mSendButton = findViewById(R.id.remote_input_send);
115         mSendButton.setOnClickListener(this);
116 
117         mEditText = (RemoteEditText) getChildAt(0);
118         mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
119             @Override
120             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
121                 final boolean isSoftImeEvent = event == null
122                         && (actionId == EditorInfo.IME_ACTION_DONE
123                         || actionId == EditorInfo.IME_ACTION_NEXT
124                         || actionId == EditorInfo.IME_ACTION_SEND);
125                 final boolean isKeyboardEnterKey = event != null
126                         && KeyEvent.isConfirmKey(event.getKeyCode())
127                         && event.getAction() == KeyEvent.ACTION_DOWN;
128 
129                 if (isSoftImeEvent || isKeyboardEnterKey) {
130                     if (mEditText.length() > 0) {
131                         sendRemoteInput();
132                     }
133                     // Consume action to prevent IME from closing.
134                     return true;
135                 }
136                 return false;
137             }
138         });
139         mEditText.addTextChangedListener(this);
140         mEditText.setInnerFocusable(false);
141         mEditText.mRemoteInputView = this;
142     }
143 
sendRemoteInput()144     private void sendRemoteInput() {
145         Bundle results = new Bundle();
146         results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
147         Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
148         RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
149                 results);
150         if (mEntry.editedSuggestionInfo == null) {
151             RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
152         } else {
153             RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
154         }
155 
156         mEditText.setEnabled(false);
157         mSendButton.setVisibility(INVISIBLE);
158         mProgressBar.setVisibility(VISIBLE);
159         mEntry.remoteInputText = mEditText.getText();
160         mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
161         mController.addSpinning(mEntry.key, mToken);
162         mController.removeRemoteInput(mEntry, mToken);
163         mEditText.mShowImeOnInputConnection = false;
164         mController.remoteInputSent(mEntry);
165         mEntry.setHasSentReply();
166 
167         // Tell ShortcutManager that this package has been "activated".  ShortcutManager
168         // will reset the throttling for this package.
169         // Strictly speaking, the intent receiver may be different from the notification publisher,
170         // but that's an edge case, and also because we can't always know which package will receive
171         // an intent, so we just reset for the publisher.
172         getContext().getSystemService(ShortcutManager.class).onApplicationActive(
173                 mEntry.notification.getPackageName(),
174                 mEntry.notification.getUser().getIdentifier());
175 
176         MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_SEND,
177                 mEntry.notification.getPackageName());
178         try {
179             mPendingIntent.send(mContext, 0, fillInIntent);
180         } catch (PendingIntent.CanceledException e) {
181             Log.i(TAG, "Unable to send remote input result", e);
182             MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_FAIL,
183                     mEntry.notification.getPackageName());
184         }
185     }
186 
getText()187     public CharSequence getText() {
188         return mEditText.getText();
189     }
190 
inflate(Context context, ViewGroup root, NotificationEntry entry, RemoteInputController controller)191     public static RemoteInputView inflate(Context context, ViewGroup root,
192             NotificationEntry entry,
193             RemoteInputController controller) {
194         RemoteInputView v = (RemoteInputView)
195                 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
196         v.mController = controller;
197         v.mEntry = entry;
198         v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
199         v.setTag(VIEW_TAG);
200 
201         return v;
202     }
203 
204     @Override
onClick(View v)205     public void onClick(View v) {
206         if (v == mSendButton) {
207             sendRemoteInput();
208         }
209     }
210 
211     @Override
onTouchEvent(MotionEvent event)212     public boolean onTouchEvent(MotionEvent event) {
213         super.onTouchEvent(event);
214 
215         // We never want for a touch to escape to an outer view or one we covered.
216         return true;
217     }
218 
onDefocus(boolean animate)219     private void onDefocus(boolean animate) {
220         mController.removeRemoteInput(mEntry, mToken);
221         mEntry.remoteInputText = mEditText.getText();
222 
223         // During removal, we get reattached and lose focus. Not hiding in that
224         // case to prevent flicker.
225         if (!mRemoved) {
226             if (animate && mRevealR > 0) {
227                 Animator reveal = ViewAnimationUtils.createCircularReveal(
228                         this, mRevealCx, mRevealCy, mRevealR, 0);
229                 reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
230                 reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
231                 reveal.addListener(new AnimatorListenerAdapter() {
232                     @Override
233                     public void onAnimationEnd(Animator animation) {
234                         setVisibility(INVISIBLE);
235                         if (mWrapper != null) {
236                             mWrapper.setRemoteInputVisible(false);
237                         }
238                     }
239                 });
240                 reveal.start();
241             } else {
242                 setVisibility(INVISIBLE);
243                 if (mWrapper != null) {
244                     mWrapper.setRemoteInputVisible(false);
245                 }
246             }
247         }
248 
249         mRemoteInputQuickSettingsDisabler.setRemoteInputActive(false);
250 
251         MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_CLOSE,
252                 mEntry.notification.getPackageName());
253     }
254 
255     @Override
onAttachedToWindow()256     protected void onAttachedToWindow() {
257         super.onAttachedToWindow();
258         if (mEntry.getRow().isChangingPosition()) {
259             if (getVisibility() == VISIBLE && mEditText.isFocusable()) {
260                 mEditText.requestFocus();
261             }
262         }
263     }
264 
265     @Override
onDetachedFromWindow()266     protected void onDetachedFromWindow() {
267         super.onDetachedFromWindow();
268         if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) {
269             return;
270         }
271         mController.removeRemoteInput(mEntry, mToken);
272         mController.removeSpinning(mEntry.key, mToken);
273     }
274 
setPendingIntent(PendingIntent pendingIntent)275     public void setPendingIntent(PendingIntent pendingIntent) {
276         mPendingIntent = pendingIntent;
277     }
278 
279     /**
280      * Sets the remote input for this view.
281      *
282      * @param remoteInputs The remote inputs that need to be sent to the app.
283      * @param remoteInput The remote input that needs to be activated.
284      * @param editedSuggestionInfo The smart reply that should be inserted in the remote input, or
285      *         {@code null} if the user is not editing a smart reply.
286      */
setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput, @Nullable EditedSuggestionInfo editedSuggestionInfo)287     public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput,
288             @Nullable EditedSuggestionInfo editedSuggestionInfo) {
289         mRemoteInputs = remoteInputs;
290         mRemoteInput = remoteInput;
291         mEditText.setHint(mRemoteInput.getLabel());
292 
293         mEntry.editedSuggestionInfo = editedSuggestionInfo;
294         if (editedSuggestionInfo != null) {
295             mEntry.remoteInputText = editedSuggestionInfo.originalText;
296         }
297     }
298 
focusAnimated()299     public void focusAnimated() {
300         if (getVisibility() != VISIBLE) {
301             Animator animator = ViewAnimationUtils.createCircularReveal(
302                     this, mRevealCx, mRevealCy, 0, mRevealR);
303             animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
304             animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
305             animator.start();
306         }
307         focus();
308     }
309 
computeTextOperationUser(UserHandle notificationUser)310     private static UserHandle computeTextOperationUser(UserHandle notificationUser) {
311         return UserHandle.ALL.equals(notificationUser)
312                 ? UserHandle.of(ActivityManager.getCurrentUser()) : notificationUser;
313     }
314 
focus()315     public void focus() {
316         MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_OPEN,
317                 mEntry.notification.getPackageName());
318 
319         setVisibility(VISIBLE);
320         if (mWrapper != null) {
321             mWrapper.setRemoteInputVisible(true);
322         }
323         mEditText.setInnerFocusable(true);
324         mEditText.mShowImeOnInputConnection = true;
325         mEditText.setText(mEntry.remoteInputText);
326         mEditText.setSelection(mEditText.getText().length());
327         mEditText.requestFocus();
328         mController.addRemoteInput(mEntry, mToken);
329 
330         mRemoteInputQuickSettingsDisabler.setRemoteInputActive(true);
331 
332         updateSendButton();
333     }
334 
onNotificationUpdateOrReset()335     public void onNotificationUpdateOrReset() {
336         boolean sending = mProgressBar.getVisibility() == VISIBLE;
337 
338         if (sending) {
339             // Update came in after we sent the reply, time to reset.
340             reset();
341         }
342 
343         if (isActive() && mWrapper != null) {
344             mWrapper.setRemoteInputVisible(true);
345         }
346     }
347 
reset()348     private void reset() {
349         mResetting = true;
350         mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
351 
352         mEditText.getText().clear();
353         mEditText.setEnabled(true);
354         mSendButton.setVisibility(VISIBLE);
355         mProgressBar.setVisibility(INVISIBLE);
356         mController.removeSpinning(mEntry.key, mToken);
357         updateSendButton();
358         onDefocus(false /* animate */);
359 
360         mResetting = false;
361     }
362 
363     @Override
onRequestSendAccessibilityEvent(View child, AccessibilityEvent event)364     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
365         if (mResetting && child == mEditText) {
366             // Suppress text events if it happens during resetting. Ideally this would be
367             // suppressed by the text view not being shown, but that doesn't work here because it
368             // needs to stay visible for the animation.
369             return false;
370         }
371         return super.onRequestSendAccessibilityEvent(child, event);
372     }
373 
updateSendButton()374     private void updateSendButton() {
375         mSendButton.setEnabled(mEditText.getText().length() != 0);
376     }
377 
378     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)379     public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
380 
381     @Override
onTextChanged(CharSequence s, int start, int before, int count)382     public void onTextChanged(CharSequence s, int start, int before, int count) {}
383 
384     @Override
afterTextChanged(Editable s)385     public void afterTextChanged(Editable s) {
386         updateSendButton();
387     }
388 
close()389     public void close() {
390         mEditText.defocusIfNeeded(false /* animated */);
391     }
392 
393     @Override
onInterceptTouchEvent(MotionEvent ev)394     public boolean onInterceptTouchEvent(MotionEvent ev) {
395         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
396             mController.requestDisallowLongPressAndDismiss();
397         }
398         return super.onInterceptTouchEvent(ev);
399     }
400 
requestScrollTo()401     public boolean requestScrollTo() {
402         mController.lockScrollTo(mEntry);
403         return true;
404     }
405 
isActive()406     public boolean isActive() {
407         return mEditText.isFocused() && mEditText.isEnabled();
408     }
409 
stealFocusFrom(RemoteInputView other)410     public void stealFocusFrom(RemoteInputView other) {
411         other.close();
412         setPendingIntent(other.mPendingIntent);
413         setRemoteInput(other.mRemoteInputs, other.mRemoteInput, mEntry.editedSuggestionInfo);
414         setRevealParameters(other.mRevealCx, other.mRevealCy, other.mRevealR);
415         focus();
416     }
417 
418     /**
419      * Tries to find an action in {@param actions} that matches the current pending intent
420      * of this view and updates its state to that of the found action
421      *
422      * @return true if a matching action was found, false otherwise
423      */
updatePendingIntentFromActions(Notification.Action[] actions)424     public boolean updatePendingIntentFromActions(Notification.Action[] actions) {
425         if (mPendingIntent == null || actions == null) {
426             return false;
427         }
428         Intent current = mPendingIntent.getIntent();
429         if (current == null) {
430             return false;
431         }
432 
433         for (Notification.Action a : actions) {
434             RemoteInput[] inputs = a.getRemoteInputs();
435             if (a.actionIntent == null || inputs == null) {
436                 continue;
437             }
438             Intent candidate = a.actionIntent.getIntent();
439             if (!current.filterEquals(candidate)) {
440                 continue;
441             }
442 
443             RemoteInput input = null;
444             for (RemoteInput i : inputs) {
445                 if (i.getAllowFreeFormInput()) {
446                     input = i;
447                 }
448             }
449             if (input == null) {
450                 continue;
451             }
452             setPendingIntent(a.actionIntent);
453             setRemoteInput(inputs, input, null /* editedSuggestionInfo*/);
454             return true;
455         }
456         return false;
457     }
458 
getPendingIntent()459     public PendingIntent getPendingIntent() {
460         return mPendingIntent;
461     }
462 
setRemoved()463     public void setRemoved() {
464         mRemoved = true;
465     }
466 
setRevealParameters(int cx, int cy, int r)467     public void setRevealParameters(int cx, int cy, int r) {
468         mRevealCx = cx;
469         mRevealCy = cy;
470         mRevealR = r;
471     }
472 
473     @Override
dispatchStartTemporaryDetach()474     public void dispatchStartTemporaryDetach() {
475         super.dispatchStartTemporaryDetach();
476         // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and
477         // won't lose IME focus.
478         detachViewFromParent(mEditText);
479     }
480 
481     @Override
dispatchFinishTemporaryDetach()482     public void dispatchFinishTemporaryDetach() {
483         if (isAttachedToWindow()) {
484             attachViewToParent(mEditText, 0, mEditText.getLayoutParams());
485         } else {
486             removeDetachedView(mEditText, false /* animate */);
487         }
488         super.dispatchFinishTemporaryDetach();
489     }
490 
setWrapper(NotificationViewWrapper wrapper)491     public void setWrapper(NotificationViewWrapper wrapper) {
492         mWrapper = wrapper;
493     }
494 
setOnVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener)495     public void setOnVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) {
496         mOnVisibilityChangedListener = visibilityChangedListener;
497     }
498 
499     @Override
onVisibilityChanged(View changedView, int visibility)500     protected void onVisibilityChanged(View changedView, int visibility) {
501         super.onVisibilityChanged(changedView, visibility);
502         if (changedView == this && mOnVisibilityChangedListener != null) {
503             mOnVisibilityChangedListener.accept(visibility == VISIBLE);
504         }
505     }
506 
isSending()507     public boolean isSending() {
508         return getVisibility() == VISIBLE && mController.isSpinning(mEntry.key, mToken);
509     }
510 
511     /**
512      * An EditText that changes appearance based on whether it's focusable and becomes
513      * un-focusable whenever the user navigates away from it or it becomes invisible.
514      */
515     public static class RemoteEditText extends EditText {
516 
517         private final Drawable mBackground;
518         private RemoteInputView mRemoteInputView;
519         boolean mShowImeOnInputConnection;
520         private LightBarController mLightBarController;
521 
RemoteEditText(Context context, AttributeSet attrs)522         public RemoteEditText(Context context, AttributeSet attrs) {
523             super(context, attrs);
524             mBackground = getBackground();
525             mLightBarController = Dependency.get(LightBarController.class);
526         }
527 
defocusIfNeeded(boolean animate)528         private void defocusIfNeeded(boolean animate) {
529             if (mRemoteInputView != null && mRemoteInputView.mEntry.getRow().isChangingPosition()
530                     || isTemporarilyDetached()) {
531                 if (isTemporarilyDetached()) {
532                     // We might get reattached but then the other one of HUN / expanded might steal
533                     // our focus, so we'll need to save our text here.
534                     if (mRemoteInputView != null) {
535                         mRemoteInputView.mEntry.remoteInputText = getText();
536                     }
537                 }
538                 return;
539             }
540             if (isFocusable() && isEnabled()) {
541                 setInnerFocusable(false);
542                 if (mRemoteInputView != null) {
543                     mRemoteInputView.onDefocus(animate);
544                 }
545                 mShowImeOnInputConnection = false;
546             }
547         }
548 
549         @Override
onVisibilityChanged(View changedView, int visibility)550         protected void onVisibilityChanged(View changedView, int visibility) {
551             super.onVisibilityChanged(changedView, visibility);
552 
553             if (!isShown()) {
554                 defocusIfNeeded(false /* animate */);
555             }
556         }
557 
558         @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)559         protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
560             super.onFocusChanged(focused, direction, previouslyFocusedRect);
561             if (!focused) {
562                 defocusIfNeeded(true /* animate */);
563             }
564             if (!mRemoteInputView.mRemoved) {
565                 mLightBarController.setDirectReplying(focused);
566             }
567         }
568 
569         @Override
getFocusedRect(Rect r)570         public void getFocusedRect(Rect r) {
571             super.getFocusedRect(r);
572             r.top = mScrollY;
573             r.bottom = mScrollY + (mBottom - mTop);
574         }
575 
576         @Override
requestRectangleOnScreen(Rect rectangle)577         public boolean requestRectangleOnScreen(Rect rectangle) {
578             return mRemoteInputView.requestScrollTo();
579         }
580 
581         @Override
onKeyDown(int keyCode, KeyEvent event)582         public boolean onKeyDown(int keyCode, KeyEvent event) {
583             if (keyCode == KeyEvent.KEYCODE_BACK) {
584                 // Eat the DOWN event here to prevent any default behavior.
585                 return true;
586             }
587             return super.onKeyDown(keyCode, event);
588         }
589 
590         @Override
onKeyUp(int keyCode, KeyEvent event)591         public boolean onKeyUp(int keyCode, KeyEvent event) {
592             if (keyCode == KeyEvent.KEYCODE_BACK) {
593                 defocusIfNeeded(true /* animate */);
594                 return true;
595             }
596             return super.onKeyUp(keyCode, event);
597         }
598 
599         @Override
onKeyPreIme(int keyCode, KeyEvent event)600         public boolean onKeyPreIme(int keyCode, KeyEvent event) {
601             // When BACK key is pressed, this method would be invoked twice.
602             if (event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
603                     event.getAction() == KeyEvent.ACTION_UP) {
604                 defocusIfNeeded(true /* animate */);
605             }
606             return super.onKeyPreIme(keyCode, event);
607         }
608 
609         @Override
onCheckIsTextEditor()610         public boolean onCheckIsTextEditor() {
611             // Stop being editable while we're being removed. During removal, we get reattached,
612             // and editable views get their spellchecking state re-evaluated which is too costly
613             // during the removal animation.
614             boolean flyingOut = mRemoteInputView != null && mRemoteInputView.mRemoved;
615             return !flyingOut && super.onCheckIsTextEditor();
616         }
617 
618         @Override
onCreateInputConnection(EditorInfo outAttrs)619         public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
620             final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
621 
622             if (mShowImeOnInputConnection && inputConnection != null) {
623                 final InputMethodManager imm =
624                         getContext().getSystemService(InputMethodManager.class);
625                 if (imm != null) {
626                     // onCreateInputConnection is called by InputMethodManager in the middle of
627                     // setting up the connection to the IME; wait with requesting the IME until that
628                     // work has completed.
629                     post(new Runnable() {
630                         @Override
631                         public void run() {
632                             imm.viewClicked(RemoteEditText.this);
633                             imm.showSoftInput(RemoteEditText.this, 0);
634                         }
635                     });
636                 }
637             }
638 
639             return inputConnection;
640         }
641 
642         @Override
onCommitCompletion(CompletionInfo text)643         public void onCommitCompletion(CompletionInfo text) {
644             clearComposingText();
645             setText(text.getText());
646             setSelection(getText().length());
647         }
648 
setInnerFocusable(boolean focusable)649         void setInnerFocusable(boolean focusable) {
650             setFocusableInTouchMode(focusable);
651             setFocusable(focusable);
652             setCursorVisible(focusable);
653 
654             if (focusable) {
655                 requestFocus();
656                 setBackground(mBackground);
657             } else {
658                 setBackground(null);
659             }
660 
661         }
662     }
663 }
664