RemoteInputView.java revision 245aa87f3ffb96aba9d6dca308e6ae832ba60ff5
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 17package com.android.systemui.statusbar.policy; 18 19import com.android.systemui.R; 20import com.android.systemui.statusbar.NotificationData; 21import com.android.systemui.statusbar.RemoteInputController; 22 23import android.annotation.NonNull; 24import android.app.Notification; 25import android.app.PendingIntent; 26import android.app.RemoteInput; 27import android.content.Context; 28import android.content.Intent; 29import android.graphics.drawable.Drawable; 30import android.os.Bundle; 31import android.text.Editable; 32import android.text.TextWatcher; 33import android.util.AttributeSet; 34import android.util.Log; 35import android.view.KeyEvent; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.inputmethod.CompletionInfo; 40import android.view.inputmethod.EditorInfo; 41import android.view.inputmethod.InputConnection; 42import android.view.inputmethod.InputMethodManager; 43import android.widget.EditText; 44import android.widget.ImageButton; 45import android.widget.LinearLayout; 46import android.widget.ProgressBar; 47import android.widget.TextView; 48 49import java.util.ArrayList; 50 51/** 52 * Host for the remote input. 53 */ 54public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher { 55 56 private static final String TAG = "RemoteInput"; 57 58 // A marker object that let's us easily find views of this class. 59 public static final Object VIEW_TAG = new Object(); 60 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; 68 69 private NotificationData.Entry mEntry; 70 71 public RemoteInputView(Context context, AttributeSet attrs) { 72 super(context, attrs); 73 } 74 75 @Override 76 protected void onFinishInflate() { 77 super.onFinishInflate(); 78 79 mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress); 80 81 mSendButton = (ImageButton) findViewById(R.id.remote_input_send); 82 mSendButton.setOnClickListener(this); 83 84 mEditText = (RemoteEditText) getChildAt(0); 85 mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { 86 @Override 87 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 88 89 // Check if this was the result of hitting the enter key 90 final boolean isSoftImeEvent = event == null 91 && (actionId == EditorInfo.IME_ACTION_DONE 92 || actionId == EditorInfo.IME_ACTION_NEXT 93 || actionId == EditorInfo.IME_ACTION_SEND); 94 final boolean isKeyboardEnterKey = event != null 95 && KeyEvent.isConfirmKey(event.getKeyCode()) 96 && event.getAction() == KeyEvent.ACTION_DOWN; 97 98 if (isSoftImeEvent || isKeyboardEnterKey) { 99 sendRemoteInput(); 100 return true; 101 } 102 return false; 103 } 104 }); 105 mEditText.setOnClickListener(this); 106 mEditText.addTextChangedListener(this); 107 mEditText.setInnerFocusable(false); 108 mEditText.mDefocusListener = this; 109 } 110 111 private void sendRemoteInput() { 112 Bundle results = new Bundle(); 113 results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString()); 114 Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 115 RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent, 116 results); 117 118 mEditText.setEnabled(false); 119 mSendButton.setVisibility(INVISIBLE); 120 mProgressBar.setVisibility(VISIBLE); 121 mController.removeRemoteInput(mEntry); 122 mEditText.mShowImeOnInputConnection = false; 123 124 try { 125 mPendingIntent.send(mContext, 0, fillInIntent); 126 } catch (PendingIntent.CanceledException e) { 127 Log.i(TAG, "Unable to send remote input result", e); 128 } 129 } 130 131 public static RemoteInputView inflate(Context context, ViewGroup root, 132 NotificationData.Entry entry, 133 RemoteInputController controller) { 134 RemoteInputView v = (RemoteInputView) 135 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false); 136 v.mController = controller; 137 v.mEntry = entry; 138 v.setTag(VIEW_TAG); 139 140 return v; 141 } 142 143 @Override 144 public void onClick(View v) { 145 if (v == mEditText) { 146 if (!mEditText.isFocusable()) { 147 focus(); 148 } 149 } else if (v == mSendButton) { 150 sendRemoteInput(); 151 } 152 } 153 154 public void onDefocus() { 155 mController.removeRemoteInput(mEntry); 156 mEntry.remoteInputText = mEditText.getText(); 157 setVisibility(INVISIBLE); 158 } 159 160 @Override 161 protected void onDetachedFromWindow() { 162 super.onDetachedFromWindow(); 163 mController.removeRemoteInput(mEntry); 164 } 165 166 public void setPendingIntent(PendingIntent pendingIntent) { 167 mPendingIntent = pendingIntent; 168 } 169 170 public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) { 171 mRemoteInputs = remoteInputs; 172 mRemoteInput = remoteInput; 173 mEditText.setHint(mRemoteInput.getLabel()); 174 } 175 176 public void focus() { 177 mEditText.setInnerFocusable(true); 178 mController.addRemoteInput(mEntry); 179 mEditText.mShowImeOnInputConnection = true; 180 mEditText.setText(mEntry.remoteInputText); 181 mEditText.setSelection(mEditText.getText().length()); 182 mEditText.requestFocus(); 183 updateSendButton(); 184 } 185 186 public void onNotificationUpdate() { 187 boolean sending = mProgressBar.getVisibility() == VISIBLE; 188 189 if (sending) { 190 // Update came in after we sent the reply, time to reset. 191 reset(); 192 } 193 } 194 195 private void reset() { 196 mEditText.getText().clear(); 197 mEditText.setEnabled(true); 198 mSendButton.setVisibility(VISIBLE); 199 mProgressBar.setVisibility(INVISIBLE); 200 updateSendButton(); 201 onDefocus(); 202 } 203 204 private void updateSendButton() { 205 mSendButton.setEnabled(mEditText.getText().length() != 0); 206 } 207 208 @Override 209 public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 210 211 @Override 212 public void onTextChanged(CharSequence s, int start, int before, int count) {} 213 214 @Override 215 public void afterTextChanged(Editable s) { 216 updateSendButton(); 217 } 218 219 /** 220 * An EditText that changes appearance based on whether it's focusable and becomes 221 * un-focusable whenever the user navigates away from it or it becomes invisible. 222 */ 223 public static class RemoteEditText extends EditText { 224 225 private final Drawable mBackground; 226 private RemoteInputView mDefocusListener; 227 boolean mShowImeOnInputConnection; 228 229 public RemoteEditText(Context context, AttributeSet attrs) { 230 super(context, attrs); 231 mBackground = getBackground(); 232 } 233 234 private void defocusIfNeeded() { 235 if (isFocusable() && isEnabled()) { 236 setInnerFocusable(false); 237 if (mDefocusListener != null) { 238 mDefocusListener.onDefocus(); 239 } 240 mShowImeOnInputConnection = false; 241 } 242 } 243 244 @Override 245 protected void onVisibilityChanged(View changedView, int visibility) { 246 super.onVisibilityChanged(changedView, visibility); 247 248 if (!isShown()) { 249 defocusIfNeeded(); 250 } 251 } 252 253 @Override 254 protected void onFocusLost() { 255 super.onFocusLost(); 256 defocusIfNeeded(); 257 } 258 259 @Override 260 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 261 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { 262 defocusIfNeeded(); 263 final InputMethodManager imm = InputMethodManager.getInstance(); 264 imm.hideSoftInputFromWindow(getWindowToken(), 0); 265 return true; 266 } 267 return super.onKeyPreIme(keyCode, event); 268 } 269 270 @Override 271 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 272 final InputConnection inputConnection = super.onCreateInputConnection(outAttrs); 273 274 if (mShowImeOnInputConnection && inputConnection != null) { 275 final InputMethodManager imm = InputMethodManager.getInstance(); 276 if (imm != null) { 277 // onCreateInputConnection is called by InputMethodManager in the middle of 278 // setting up the connection to the IME; wait with requesting the IME until that 279 // work has completed. 280 post(new Runnable() { 281 @Override 282 public void run() { 283 imm.viewClicked(RemoteEditText.this); 284 imm.showSoftInput(RemoteEditText.this, 0); 285 } 286 }); 287 } 288 } 289 290 return inputConnection; 291 } 292 293 @Override 294 public void onCommitCompletion(CompletionInfo text) { 295 clearComposingText(); 296 setText(text.getText()); 297 setSelection(getText().length()); 298 } 299 300 void setInnerFocusable(boolean focusable) { 301 setFocusableInTouchMode(focusable); 302 setFocusable(focusable); 303 setCursorVisible(focusable); 304 305 if (focusable) { 306 requestFocus(); 307 setBackground(mBackground); 308 } else { 309 setBackground(null); 310 } 311 312 } 313 } 314} 315