| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.systemui.statusbar.policy; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.Notification; |
| import android.app.PendingIntent; |
| import android.app.RemoteInput; |
| import android.content.ClipDescription; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ShortcutManager; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.ServiceManager; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.text.Editable; |
| import android.text.SpannedString; |
| import android.text.TextWatcher; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewAnimationUtils; |
| import android.view.ViewGroup; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.view.inputmethod.CompletionInfo; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputConnection; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.EditText; |
| import android.widget.ImageButton; |
| import android.widget.LinearLayout; |
| import android.widget.ProgressBar; |
| import android.widget.TextView; |
| |
| import androidx.core.view.inputmethod.EditorInfoCompat; |
| import androidx.core.view.inputmethod.InputConnectionCompat; |
| import androidx.core.view.inputmethod.InputContentInfoCompat; |
| |
| import com.android.internal.logging.MetricsLogger; |
| import com.android.internal.logging.nano.MetricsProto; |
| import com.android.internal.statusbar.IStatusBarService; |
| import com.android.systemui.Dependency; |
| import com.android.systemui.Interpolators; |
| import com.android.systemui.R; |
| import com.android.systemui.statusbar.RemoteInputController; |
| import com.android.systemui.statusbar.notification.collection.NotificationEntry; |
| import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo; |
| import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; |
| import com.android.systemui.statusbar.notification.stack.StackStateAnimator; |
| import com.android.systemui.statusbar.phone.LightBarController; |
| |
| import java.util.HashMap; |
| import java.util.function.Consumer; |
| |
| /** |
| * Host for the remote input. |
| */ |
| public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher { |
| |
| private static final String TAG = "RemoteInput"; |
| |
| // A marker object that let's us easily find views of this class. |
| public static final Object VIEW_TAG = new Object(); |
| |
| public final Object mToken = new Object(); |
| |
| private RemoteEditText mEditText; |
| private ImageButton mSendButton; |
| private ProgressBar mProgressBar; |
| private PendingIntent mPendingIntent; |
| private RemoteInput[] mRemoteInputs; |
| private RemoteInput mRemoteInput; |
| private RemoteInputController mController; |
| private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; |
| |
| private IStatusBarService mStatusBarManagerService; |
| |
| private NotificationEntry mEntry; |
| |
| private boolean mRemoved; |
| |
| private int mRevealCx; |
| private int mRevealCy; |
| private int mRevealR; |
| |
| private boolean mResetting; |
| private NotificationViewWrapper mWrapper; |
| private Consumer<Boolean> mOnVisibilityChangedListener; |
| |
| public RemoteInputView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class); |
| mStatusBarManagerService = IStatusBarService.Stub.asInterface( |
| ServiceManager.getService(Context.STATUS_BAR_SERVICE)); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| |
| mProgressBar = findViewById(R.id.remote_input_progress); |
| |
| mSendButton = findViewById(R.id.remote_input_send); |
| mSendButton.setOnClickListener(this); |
| |
| mEditText = (RemoteEditText) getChildAt(0); |
| mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { |
| @Override |
| public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { |
| final boolean isSoftImeEvent = event == null |
| && (actionId == EditorInfo.IME_ACTION_DONE |
| || actionId == EditorInfo.IME_ACTION_NEXT |
| || actionId == EditorInfo.IME_ACTION_SEND); |
| final boolean isKeyboardEnterKey = event != null |
| && KeyEvent.isConfirmKey(event.getKeyCode()) |
| && event.getAction() == KeyEvent.ACTION_DOWN; |
| |
| if (isSoftImeEvent || isKeyboardEnterKey) { |
| if (mEditText.length() > 0) { |
| sendRemoteInput(prepareRemoteInputFromText()); |
| } |
| // Consume action to prevent IME from closing. |
| return true; |
| } |
| return false; |
| } |
| }); |
| mEditText.addTextChangedListener(this); |
| mEditText.setInnerFocusable(false); |
| mEditText.mRemoteInputView = this; |
| } |
| |
| protected Intent prepareRemoteInputFromText() { |
| Bundle results = new Bundle(); |
| results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString()); |
| Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent, |
| results); |
| if (mEntry.editedSuggestionInfo == null) { |
| RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT); |
| } else { |
| RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE); |
| } |
| |
| return fillInIntent; |
| } |
| |
| protected Intent prepareRemoteInputFromData(String contentType, Uri data) { |
| HashMap<String, Uri> results = new HashMap<>(); |
| results.put(contentType, data); |
| try { |
| mStatusBarManagerService.grantInlineReplyUriPermission( |
| mEntry.getSbn().getKey(), data); |
| } catch (Exception e) { |
| Log.e(TAG, "Failed to grant URI permissions:" + e.getMessage(), e); |
| } |
| Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results); |
| |
| return fillInIntent; |
| } |
| |
| private void sendRemoteInput(Intent intent) { |
| mEditText.setEnabled(false); |
| mSendButton.setVisibility(INVISIBLE); |
| mProgressBar.setVisibility(VISIBLE); |
| mEntry.remoteInputText = mEditText.getText(); |
| mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime(); |
| mController.addSpinning(mEntry.getKey(), mToken); |
| mController.removeRemoteInput(mEntry, mToken); |
| mEditText.mShowImeOnInputConnection = false; |
| mController.remoteInputSent(mEntry); |
| mEntry.setHasSentReply(); |
| |
| // Tell ShortcutManager that this package has been "activated". ShortcutManager |
| // will reset the throttling for this package. |
| // Strictly speaking, the intent receiver may be different from the notification publisher, |
| // but that's an edge case, and also because we can't always know which package will receive |
| // an intent, so we just reset for the publisher. |
| getContext().getSystemService(ShortcutManager.class).onApplicationActive( |
| mEntry.getSbn().getPackageName(), |
| mEntry.getSbn().getUser().getIdentifier()); |
| |
| MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_SEND, |
| mEntry.getSbn().getPackageName()); |
| try { |
| mPendingIntent.send(mContext, 0, intent); |
| } catch (PendingIntent.CanceledException e) { |
| Log.i(TAG, "Unable to send remote input result", e); |
| MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_FAIL, |
| mEntry.getSbn().getPackageName()); |
| } |
| } |
| |
| public CharSequence getText() { |
| return mEditText.getText(); |
| } |
| |
| public static RemoteInputView inflate(Context context, ViewGroup root, |
| NotificationEntry entry, |
| RemoteInputController controller) { |
| RemoteInputView v = (RemoteInputView) |
| LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); |
| v.mController = controller; |
| v.mEntry = entry; |
| UserHandle user = computeTextOperationUser(entry.getSbn().getUser()); |
| v.mEditText.mUser = user; |
| v.mEditText.setTextOperationUser(user); |
| v.setTag(VIEW_TAG); |
| |
| return v; |
| } |
| |
| @Override |
| public void onClick(View v) { |
| if (v == mSendButton) { |
| sendRemoteInput(prepareRemoteInputFromText()); |
| } |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent event) { |
| super.onTouchEvent(event); |
| |
| // We never want for a touch to escape to an outer view or one we covered. |
| return true; |
| } |
| |
| private void onDefocus(boolean animate) { |
| mController.removeRemoteInput(mEntry, mToken); |
| mEntry.remoteInputText = mEditText.getText(); |
| |
| // During removal, we get reattached and lose focus. Not hiding in that |
| // case to prevent flicker. |
| if (!mRemoved) { |
| if (animate && mRevealR > 0) { |
| Animator reveal = ViewAnimationUtils.createCircularReveal( |
| this, mRevealCx, mRevealCy, mRevealR, 0); |
| reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); |
| reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT); |
| reveal.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| setVisibility(INVISIBLE); |
| if (mWrapper != null) { |
| mWrapper.setRemoteInputVisible(false); |
| } |
| } |
| }); |
| reveal.start(); |
| } else { |
| setVisibility(INVISIBLE); |
| if (mWrapper != null) { |
| mWrapper.setRemoteInputVisible(false); |
| } |
| } |
| } |
| |
| mRemoteInputQuickSettingsDisabler.setRemoteInputActive(false); |
| |
| MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_CLOSE, |
| mEntry.getSbn().getPackageName()); |
| } |
| |
| @Override |
| protected void onAttachedToWindow() { |
| super.onAttachedToWindow(); |
| if (mEntry.getRow().isChangingPosition()) { |
| if (getVisibility() == VISIBLE && mEditText.isFocusable()) { |
| mEditText.requestFocus(); |
| } |
| } |
| } |
| |
| @Override |
| protected void onDetachedFromWindow() { |
| super.onDetachedFromWindow(); |
| if (mEntry.getRow().isChangingPosition() || isTemporarilyDetached()) { |
| return; |
| } |
| mController.removeRemoteInput(mEntry, mToken); |
| mController.removeSpinning(mEntry.getKey(), mToken); |
| } |
| |
| public void setPendingIntent(PendingIntent pendingIntent) { |
| mPendingIntent = pendingIntent; |
| } |
| |
| /** |
| * Sets the remote input for this view. |
| * |
| * @param remoteInputs The remote inputs that need to be sent to the app. |
| * @param remoteInput The remote input that needs to be activated. |
| * @param editedSuggestionInfo The smart reply that should be inserted in the remote input, or |
| * {@code null} if the user is not editing a smart reply. |
| */ |
| public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput, |
| @Nullable EditedSuggestionInfo editedSuggestionInfo) { |
| mRemoteInputs = remoteInputs; |
| mRemoteInput = remoteInput; |
| mEditText.setHint(mRemoteInput.getLabel()); |
| |
| mEntry.editedSuggestionInfo = editedSuggestionInfo; |
| if (editedSuggestionInfo != null) { |
| mEntry.remoteInputText = editedSuggestionInfo.originalText; |
| } |
| } |
| |
| public void focusAnimated() { |
| if (getVisibility() != VISIBLE) { |
| Animator animator = ViewAnimationUtils.createCircularReveal( |
| this, mRevealCx, mRevealCy, 0, mRevealR); |
| animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); |
| animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); |
| animator.start(); |
| } |
| focus(); |
| } |
| |
| private static UserHandle computeTextOperationUser(UserHandle notificationUser) { |
| return UserHandle.ALL.equals(notificationUser) |
| ? UserHandle.of(ActivityManager.getCurrentUser()) : notificationUser; |
| } |
| |
| public void focus() { |
| MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_OPEN, |
| mEntry.getSbn().getPackageName()); |
| |
| setVisibility(VISIBLE); |
| if (mWrapper != null) { |
| mWrapper.setRemoteInputVisible(true); |
| } |
| mEditText.setInnerFocusable(true); |
| mEditText.mShowImeOnInputConnection = true; |
| mEditText.setText(mEntry.remoteInputText); |
| mEditText.setSelection(mEditText.getText().length()); |
| mEditText.requestFocus(); |
| mController.addRemoteInput(mEntry, mToken); |
| |
| mRemoteInputQuickSettingsDisabler.setRemoteInputActive(true); |
| |
| updateSendButton(); |
| } |
| |
| public void onNotificationUpdateOrReset() { |
| boolean sending = mProgressBar.getVisibility() == VISIBLE; |
| |
| if (sending) { |
| // Update came in after we sent the reply, time to reset. |
| reset(); |
| } |
| |
| if (isActive() && mWrapper != null) { |
| mWrapper.setRemoteInputVisible(true); |
| } |
| } |
| |
| private void reset() { |
| mResetting = true; |
| mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText()); |
| |
| mEditText.getText().clear(); |
| mEditText.setEnabled(true); |
| mSendButton.setVisibility(VISIBLE); |
| mProgressBar.setVisibility(INVISIBLE); |
| mController.removeSpinning(mEntry.getKey(), mToken); |
| updateSendButton(); |
| onDefocus(false /* animate */); |
| |
| mResetting = false; |
| } |
| |
| @Override |
| public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { |
| if (mResetting && child == mEditText) { |
| // Suppress text events if it happens during resetting. Ideally this would be |
| // suppressed by the text view not being shown, but that doesn't work here because it |
| // needs to stay visible for the animation. |
| return false; |
| } |
| return super.onRequestSendAccessibilityEvent(child, event); |
| } |
| |
| private void updateSendButton() { |
| mSendButton.setEnabled(mEditText.getText().length() != 0); |
| } |
| |
| @Override |
| public void beforeTextChanged(CharSequence s, int start, int count, int after) {} |
| |
| @Override |
| public void onTextChanged(CharSequence s, int start, int before, int count) {} |
| |
| @Override |
| public void afterTextChanged(Editable s) { |
| updateSendButton(); |
| } |
| |
| public void close() { |
| mEditText.defocusIfNeeded(false /* animated */); |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| if (ev.getAction() == MotionEvent.ACTION_DOWN) { |
| mController.requestDisallowLongPressAndDismiss(); |
| } |
| return super.onInterceptTouchEvent(ev); |
| } |
| |
| public boolean requestScrollTo() { |
| mController.lockScrollTo(mEntry); |
| return true; |
| } |
| |
| public boolean isActive() { |
| return mEditText.isFocused() && mEditText.isEnabled(); |
| } |
| |
| public void stealFocusFrom(RemoteInputView other) { |
| other.close(); |
| setPendingIntent(other.mPendingIntent); |
| setRemoteInput(other.mRemoteInputs, other.mRemoteInput, mEntry.editedSuggestionInfo); |
| setRevealParameters(other.mRevealCx, other.mRevealCy, other.mRevealR); |
| focus(); |
| } |
| |
| /** |
| * Tries to find an action in {@param actions} that matches the current pending intent |
| * of this view and updates its state to that of the found action |
| * |
| * @return true if a matching action was found, false otherwise |
| */ |
| public boolean updatePendingIntentFromActions(Notification.Action[] actions) { |
| if (mPendingIntent == null || actions == null) { |
| return false; |
| } |
| Intent current = mPendingIntent.getIntent(); |
| if (current == null) { |
| return false; |
| } |
| |
| for (Notification.Action a : actions) { |
| RemoteInput[] inputs = a.getRemoteInputs(); |
| if (a.actionIntent == null || inputs == null) { |
| continue; |
| } |
| Intent candidate = a.actionIntent.getIntent(); |
| if (!current.filterEquals(candidate)) { |
| continue; |
| } |
| |
| RemoteInput input = null; |
| for (RemoteInput i : inputs) { |
| if (i.getAllowFreeFormInput()) { |
| input = i; |
| } |
| } |
| if (input == null) { |
| continue; |
| } |
| setPendingIntent(a.actionIntent); |
| setRemoteInput(inputs, input, null /* editedSuggestionInfo*/); |
| return true; |
| } |
| return false; |
| } |
| |
| public PendingIntent getPendingIntent() { |
| return mPendingIntent; |
| } |
| |
| public void setRemoved() { |
| mRemoved = true; |
| } |
| |
| public void setRevealParameters(int cx, int cy, int r) { |
| mRevealCx = cx; |
| mRevealCy = cy; |
| mRevealR = r; |
| } |
| |
| @Override |
| public void dispatchStartTemporaryDetach() { |
| super.dispatchStartTemporaryDetach(); |
| // Detach the EditText temporarily such that it doesn't get onDetachedFromWindow and |
| // won't lose IME focus. |
| detachViewFromParent(mEditText); |
| } |
| |
| @Override |
| public void dispatchFinishTemporaryDetach() { |
| if (isAttachedToWindow()) { |
| attachViewToParent(mEditText, 0, mEditText.getLayoutParams()); |
| } else { |
| removeDetachedView(mEditText, false /* animate */); |
| } |
| super.dispatchFinishTemporaryDetach(); |
| } |
| |
| public void setWrapper(NotificationViewWrapper wrapper) { |
| mWrapper = wrapper; |
| } |
| |
| public void setOnVisibilityChangedListener(Consumer<Boolean> visibilityChangedListener) { |
| mOnVisibilityChangedListener = visibilityChangedListener; |
| } |
| |
| @Override |
| protected void onVisibilityChanged(View changedView, int visibility) { |
| super.onVisibilityChanged(changedView, visibility); |
| if (changedView == this && mOnVisibilityChangedListener != null) { |
| mOnVisibilityChangedListener.accept(visibility == VISIBLE); |
| } |
| } |
| |
| public boolean isSending() { |
| return getVisibility() == VISIBLE && mController.isSpinning(mEntry.getKey(), mToken); |
| } |
| |
| /** |
| * An EditText that changes appearance based on whether it's focusable and becomes |
| * un-focusable whenever the user navigates away from it or it becomes invisible. |
| */ |
| public static class RemoteEditText extends EditText { |
| |
| private final Drawable mBackground; |
| private RemoteInputView mRemoteInputView; |
| boolean mShowImeOnInputConnection; |
| private LightBarController mLightBarController; |
| UserHandle mUser; |
| |
| public RemoteEditText(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| mBackground = getBackground(); |
| mLightBarController = Dependency.get(LightBarController.class); |
| } |
| |
| private void defocusIfNeeded(boolean animate) { |
| if (mRemoteInputView != null && mRemoteInputView.mEntry.getRow().isChangingPosition() |
| || isTemporarilyDetached()) { |
| if (isTemporarilyDetached()) { |
| // We might get reattached but then the other one of HUN / expanded might steal |
| // our focus, so we'll need to save our text here. |
| if (mRemoteInputView != null) { |
| mRemoteInputView.mEntry.remoteInputText = getText(); |
| } |
| } |
| return; |
| } |
| if (isFocusable() && isEnabled()) { |
| setInnerFocusable(false); |
| if (mRemoteInputView != null) { |
| mRemoteInputView.onDefocus(animate); |
| } |
| mShowImeOnInputConnection = false; |
| } |
| } |
| |
| @Override |
| protected void onVisibilityChanged(View changedView, int visibility) { |
| super.onVisibilityChanged(changedView, visibility); |
| |
| if (!isShown()) { |
| defocusIfNeeded(false /* animate */); |
| } |
| } |
| |
| @Override |
| protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { |
| super.onFocusChanged(focused, direction, previouslyFocusedRect); |
| if (!focused) { |
| defocusIfNeeded(true /* animate */); |
| } |
| if (!mRemoteInputView.mRemoved) { |
| mLightBarController.setDirectReplying(focused); |
| } |
| } |
| |
| @Override |
| public void getFocusedRect(Rect r) { |
| super.getFocusedRect(r); |
| r.top = mScrollY; |
| r.bottom = mScrollY + (mBottom - mTop); |
| } |
| |
| @Override |
| public boolean requestRectangleOnScreen(Rect rectangle) { |
| return mRemoteInputView.requestScrollTo(); |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| // Eat the DOWN event here to prevent any default behavior. |
| return true; |
| } |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| @Override |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| if (keyCode == KeyEvent.KEYCODE_BACK) { |
| defocusIfNeeded(true /* animate */); |
| return true; |
| } |
| return super.onKeyUp(keyCode, event); |
| } |
| |
| @Override |
| public boolean onKeyPreIme(int keyCode, KeyEvent event) { |
| // When BACK key is pressed, this method would be invoked twice. |
| if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && |
| event.getAction() == KeyEvent.ACTION_UP) { |
| defocusIfNeeded(true /* animate */); |
| } |
| return super.onKeyPreIme(keyCode, event); |
| } |
| |
| @Override |
| public boolean onCheckIsTextEditor() { |
| // Stop being editable while we're being removed. During removal, we get reattached, |
| // and editable views get their spellchecking state re-evaluated which is too costly |
| // during the removal animation. |
| boolean flyingOut = mRemoteInputView != null && mRemoteInputView.mRemoved; |
| return !flyingOut && super.onCheckIsTextEditor(); |
| } |
| |
| @Override |
| public InputConnection onCreateInputConnection(EditorInfo outAttrs) { |
| String[] allowedDataTypes = mRemoteInputView.mRemoteInput.getAllowedDataTypes() |
| .toArray(new String[0]); |
| EditorInfoCompat.setContentMimeTypes(outAttrs, allowedDataTypes); |
| final InputConnection inputConnection = super.onCreateInputConnection(outAttrs); |
| |
| final InputConnectionCompat.OnCommitContentListener callback = |
| new InputConnectionCompat.OnCommitContentListener() { |
| @Override |
| public boolean onCommitContent( |
| InputContentInfoCompat inputContentInfoCompat, int i, |
| Bundle bundle) { |
| Uri contentUri = inputContentInfoCompat.getContentUri(); |
| ClipDescription description = inputContentInfoCompat.getDescription(); |
| String mimeType = null; |
| if (description != null && description.getMimeTypeCount() > 0) { |
| mimeType = description.getMimeType(0); |
| } |
| if (mimeType != null) { |
| Intent dataIntent = mRemoteInputView.prepareRemoteInputFromData( |
| mimeType, contentUri); |
| mRemoteInputView.sendRemoteInput(dataIntent); |
| } |
| return true; |
| } |
| }; |
| |
| InputConnection ic = InputConnectionCompat.createWrapper( |
| inputConnection, outAttrs, callback); |
| |
| Context userContext = null; |
| try { |
| userContext = mContext.createPackageContextAsUser( |
| mContext.getPackageName(), 0, mUser); |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.e(TAG, "Unable to create user context:" + e.getMessage(), e); |
| } |
| |
| if (mShowImeOnInputConnection && ic != null) { |
| Context targetContext = userContext != null ? userContext : getContext(); |
| final InputMethodManager imm = |
| targetContext.getSystemService(InputMethodManager.class); |
| if (imm != null) { |
| // onCreateInputConnection is called by InputMethodManager in the middle of |
| // setting up the connection to the IME; wait with requesting the IME until that |
| // work has completed. |
| post(new Runnable() { |
| @Override |
| public void run() { |
| imm.viewClicked(RemoteEditText.this); |
| imm.showSoftInput(RemoteEditText.this, 0); |
| } |
| }); |
| } |
| } |
| |
| return ic; |
| } |
| |
| @Override |
| public void onCommitCompletion(CompletionInfo text) { |
| clearComposingText(); |
| setText(text.getText()); |
| setSelection(getText().length()); |
| } |
| |
| void setInnerFocusable(boolean focusable) { |
| setFocusableInTouchMode(focusable); |
| setFocusable(focusable); |
| setCursorVisible(focusable); |
| |
| if (focusable) { |
| requestFocus(); |
| setBackground(mBackground); |
| } else { |
| setBackground(null); |
| } |
| |
| } |
| } |
| } |