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