RemoteInputView.java revision dc5b4535165d82e5e9c83576a47bd95cd422ca0a
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.Rect; 30import android.graphics.drawable.Drawable; 31import android.os.Bundle; 32import android.text.Editable; 33import android.text.TextWatcher; 34import android.util.AttributeSet; 35import android.util.Log; 36import android.view.KeyEvent; 37import android.view.LayoutInflater; 38import android.view.View; 39import android.view.ViewGroup; 40import android.view.inputmethod.CompletionInfo; 41import android.view.inputmethod.EditorInfo; 42import android.view.inputmethod.InputConnection; 43import android.view.inputmethod.InputMethodManager; 44import android.widget.EditText; 45import android.widget.ImageButton; 46import android.widget.LinearLayout; 47import android.widget.ProgressBar; 48import android.widget.TextView; 49 50import java.util.ArrayList; 51 52/** 53 * Host for the remote input. 54 */ 55public class RemoteInputView extends LinearLayout implements View.OnClickListener, TextWatcher { 56 57 private static final String TAG = "RemoteInput"; 58 59 // A marker object that let's us easily find views of this class. 60 public static final Object VIEW_TAG = new Object(); 61 62 private RemoteEditText mEditText; 63 private ImageButton mSendButton; 64 private ProgressBar mProgressBar; 65 private PendingIntent mPendingIntent; 66 private RemoteInput[] mRemoteInputs; 67 private RemoteInput mRemoteInput; 68 private RemoteInputController mController; 69 70 private NotificationData.Entry mEntry; 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 onDetachedFromWindow() { 163 super.onDetachedFromWindow(); 164 mController.removeRemoteInput(mEntry); 165 } 166 167 public void setPendingIntent(PendingIntent pendingIntent) { 168 mPendingIntent = pendingIntent; 169 } 170 171 public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) { 172 mRemoteInputs = remoteInputs; 173 mRemoteInput = remoteInput; 174 mEditText.setHint(mRemoteInput.getLabel()); 175 } 176 177 public void focus() { 178 mController.addRemoteInput(mEntry); 179 mEditText.setInnerFocusable(true); 180 mEditText.mShowImeOnInputConnection = true; 181 mEditText.setText(mEntry.remoteInputText); 182 mEditText.setSelection(mEditText.getText().length()); 183 mEditText.requestFocus(); 184 updateSendButton(); 185 } 186 187 public void onNotificationUpdate() { 188 boolean sending = mProgressBar.getVisibility() == VISIBLE; 189 190 if (sending) { 191 // Update came in after we sent the reply, time to reset. 192 reset(); 193 } 194 } 195 196 private void reset() { 197 mEditText.getText().clear(); 198 mEditText.setEnabled(true); 199 mSendButton.setVisibility(VISIBLE); 200 mProgressBar.setVisibility(INVISIBLE); 201 updateSendButton(); 202 onDefocus(); 203 } 204 205 private void updateSendButton() { 206 mSendButton.setEnabled(mEditText.getText().length() != 0); 207 } 208 209 @Override 210 public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 211 212 @Override 213 public void onTextChanged(CharSequence s, int start, int before, int count) {} 214 215 @Override 216 public void afterTextChanged(Editable s) { 217 updateSendButton(); 218 } 219 220 /** 221 * An EditText that changes appearance based on whether it's focusable and becomes 222 * un-focusable whenever the user navigates away from it or it becomes invisible. 223 */ 224 public static class RemoteEditText extends EditText { 225 226 private final Drawable mBackground; 227 private RemoteInputView mDefocusListener; 228 boolean mShowImeOnInputConnection; 229 230 public RemoteEditText(Context context, AttributeSet attrs) { 231 super(context, attrs); 232 mBackground = getBackground(); 233 } 234 235 private void defocusIfNeeded() { 236 if (isFocusable() && isEnabled()) { 237 setInnerFocusable(false); 238 if (mDefocusListener != null) { 239 mDefocusListener.onDefocus(); 240 } 241 mShowImeOnInputConnection = false; 242 } 243 } 244 245 @Override 246 protected void onVisibilityChanged(View changedView, int visibility) { 247 super.onVisibilityChanged(changedView, visibility); 248 249 if (!isShown()) { 250 defocusIfNeeded(); 251 } 252 } 253 254 @Override 255 protected void onFocusLost() { 256 super.onFocusLost(); 257 defocusIfNeeded(); 258 } 259 260 @Override 261 public boolean requestRectangleOnScreen(Rect r) { 262 r.top = mScrollY; 263 r.bottom = mScrollY + (mBottom - mTop); 264 return super.requestRectangleOnScreen(r); 265 } 266 267 @Override 268 public void getFocusedRect(Rect r) { 269 super.getFocusedRect(r); 270 r.top = mScrollY; 271 r.bottom = mScrollY + (mBottom - mTop); 272 } 273 274 @Override 275 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 276 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { 277 defocusIfNeeded(); 278 final InputMethodManager imm = InputMethodManager.getInstance(); 279 imm.hideSoftInputFromWindow(getWindowToken(), 0); 280 return true; 281 } 282 return super.onKeyPreIme(keyCode, event); 283 } 284 285 @Override 286 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 287 final InputConnection inputConnection = super.onCreateInputConnection(outAttrs); 288 289 if (mShowImeOnInputConnection && inputConnection != null) { 290 final InputMethodManager imm = InputMethodManager.getInstance(); 291 if (imm != null) { 292 // onCreateInputConnection is called by InputMethodManager in the middle of 293 // setting up the connection to the IME; wait with requesting the IME until that 294 // work has completed. 295 post(new Runnable() { 296 @Override 297 public void run() { 298 imm.viewClicked(RemoteEditText.this); 299 imm.showSoftInput(RemoteEditText.this, 0); 300 } 301 }); 302 } 303 } 304 305 return inputConnection; 306 } 307 308 @Override 309 public void onCommitCompletion(CompletionInfo text) { 310 clearComposingText(); 311 setText(text.getText()); 312 setSelection(getText().length()); 313 } 314 315 void setInnerFocusable(boolean focusable) { 316 setFocusableInTouchMode(focusable); 317 setFocusable(focusable); 318 setCursorVisible(focusable); 319 320 if (focusable) { 321 requestFocus(); 322 setBackground(mBackground); 323 } else { 324 setBackground(null); 325 } 326 327 } 328 } 329} 330