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