Visuals for Inline Reply
Change-Id: I374a7ec82795f95fe2f3ce8c9e6b02c1479433af
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0b77be3..af990de 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3311,12 +3311,17 @@
tombstone ? getActionTombstoneLayoutResource()
: getActionLayoutResource());
final Icon ai = action.getIcon();
- button.setTextViewCompoundDrawablesRelative(R.id.action0, ai, null, null, null);
button.setTextViewText(R.id.action0, processLegacyText(action.title));
if (!tombstone) {
button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
}
button.setContentDescription(R.id.action0, action.title);
+ if (action.mRemoteInputs != null) {
+ button.setRemoteInputs(R.id.action0, action.mRemoteInputs);
+ }
+ if (mN.color != COLOR_DEFAULT) {
+ button.setTextColor(R.id.action0, mN.color);
+ }
processLegacyAction(action, button);
return button;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index ca1b211..ce1c108 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -21,6 +21,7 @@
import android.app.ActivityThread;
import android.app.Application;
import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.ContextWrapper;
@@ -153,6 +154,13 @@
};
/**
+ * @hide
+ */
+ public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) {
+ mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
+ }
+
+ /**
* Handle with care!
*/
static class MutablePair<F, S> {
@@ -1699,6 +1707,43 @@
}
/**
+ * Helper action to add a view tag with RemoteInputs.
+ */
+ private class SetRemoteInputsAction extends Action {
+
+ public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) {
+ this.viewId = viewId;
+ this.remoteInputs = remoteInputs;
+ }
+
+ public SetRemoteInputsAction(Parcel parcel) {
+ viewId = parcel.readInt();
+ remoteInputs = parcel.readParcelableArray(RemoteInput.class.getClassLoader());
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(TAG);
+ dest.writeInt(viewId);
+ dest.writeParcelableArray(remoteInputs, flags);
+ }
+
+ @Override
+ public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
+ final TextView target = (TextView) root.findViewById(viewId);
+ if (target == null) return;
+
+ target.setTagInternal(R.id.remote_input_tag, remoteInputs);
+ }
+
+ public String getActionName() {
+ return "SetRemoteInputsAction";
+ }
+
+ final Parcelable[] remoteInputs;
+ public final static int TAG = 18;
+ }
+
+ /**
* Simple class used to keep track of memory usage in a RemoteViews.
*
*/
@@ -1894,6 +1939,9 @@
case TextViewDrawableColorFilterAction.TAG:
mActions.add(new TextViewDrawableColorFilterAction(parcel));
break;
+ case SetRemoteInputsAction.TAG:
+ mActions.add(new SetRemoteInputsAction(parcel));
+ break;
default:
throw new ActionException("Tag " + tag + " not found");
}
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index da8b2e7..f4bc918 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -18,15 +18,11 @@
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@android:style/Widget.Material.Light.Button.Borderless.Small"
android:id="@+id/action0"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="48dp"
- android:layout_weight="1"
- android:layout_margin="0dp"
- android:gravity="start|center_vertical"
- android:drawablePadding="8dp"
- android:paddingStart="8dp"
+ android:layout_gravity="center"
+ android:layout_marginStart="8dp"
android:textColor="@color/secondary_text_material_light"
- android:textSize="13sp"
android:singleLine="true"
android:ellipsize="end"
android:background="@drawable/notification_material_action_background"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index 2a36949..edaf020 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -14,14 +14,20 @@
limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/actions"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:visibility="gone"
- android:layout_marginBottom="8dp"
- >
- <!-- actions will be added here -->
-</LinearLayout>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/actions_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:paddingEnd="8dp"
+ android:orientation="horizontal"
+ android:visibility="gone"
+ android:background="#ffeeeeee"
+ >
+ <!-- actions will be added here -->
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index 9d5e5ac..c03fbeb 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -122,4 +122,6 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_CONTEXT_CLICK}. -->
<item type="id" name="accessibilityActionContextClick" />
+
+ <item type="id" name="remote_input_tag" />
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e8f6b46..5516360 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2302,6 +2302,9 @@
<java-symbol type="string" name="notification_inbox_ellipsis" />
<java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" />
+ <java-symbol type="id" name="actions_container" />
+ <java-symbol type="id" name="remote_input_tag" />
+
<java-symbol type="attr" name="seekBarDialogPreferenceStyle" />
<java-symbol type="string" name="ext_media_status_removed" />
<java-symbol type="string" name="ext_media_status_unmounted" />
diff --git a/packages/SystemUI/res/drawable/ic_send.xml b/packages/SystemUI/res/drawable/ic_send.xml
new file mode 100644
index 0000000..b1c7914
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_send.xml
@@ -0,0 +1,27 @@
+<!--
+Copyright (C) 2014 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M4.02,42.0L46.0,24.0 4.02,6.0 4.0,20.0l30.0,4.0 -30.0,4.0z"/>
+ <path
+ android:pathData="M0 0h48v48H0z"
+ android:fillColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index 8ca5634..74092c1 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -16,31 +16,61 @@
~ limitations under the License
-->
-<!-- FrameLayout -->
+<!-- LinearLayout -->
<com.android.systemui.statusbar.policy.RemoteInputView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:theme="@style/systemui_theme_light"
+ android:theme="@style/systemui_theme_remote_input"
+ android:id="@+id/remote_input"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:paddingStart="4dp"
- android:paddingEnd="2dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="12dp"
android:paddingBottom="4dp"
android:paddingTop="2dp">
<view class="com.android.systemui.statusbar.policy.RemoteInputView$RemoteEditText"
android:id="@+id/remote_input_text"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:paddingEnd="12dp"
+ android:gravity="start|center_vertical"
+ android:textAppearance="?android:attr/textAppearance"
+ android:textColor="#deffffff"
+ android:textSize="16sp"
+ android:background="@null"
android:singleLine="true"
+ android:ellipsize="start"
android:imeOptions="actionSend" />
- <ProgressBar
- android:id="@+id/remote_input_progress"
- android:layout_width="match_parent"
+ <FrameLayout
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:visibility="invisible"
- android:indeterminate="true"
- style="?android:attr/progressBarStyleHorizontal" />
+ android:layout_gravity="center_vertical">
+
+ <ImageButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:id="@+id/remote_input_send"
+ android:src="@drawable/ic_send"
+ android:tint="@android:color/white"
+ android:tintMode="src_atop"
+ android:background="@drawable/ripple_drawable" />
+
+ <ProgressBar
+ android:id="@+id/remote_input_progress"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:visibility="invisible"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyleSmall" />
+
+ </FrameLayout>
</com.android.systemui.statusbar.policy.RemoteInputView>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4462a03..2fd0fe5 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -220,9 +220,8 @@
<item name="android:colorControlActivated">@color/system_accent_color</item>
</style>
- <style name="systemui_theme_light" parent="@android:style/Theme.DeviceDefault.Light">
- <item name="android:colorPrimary">@color/system_primary_color</item>
- <item name="android:colorControlActivated">@color/system_accent_color</item>
+ <style name="systemui_theme_remote_input" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:colorControlActivated">@android:color/white</item>
</style>
<style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index d3d9bef..50c30b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -80,6 +80,7 @@
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
import android.widget.DateTimeView;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -280,6 +281,10 @@
@Override
public boolean onClickHandler(
final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
+ if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+ return true;
+ }
+
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
@@ -368,6 +373,65 @@
Intent fillInIntent) {
return super.onClickHandler(view, pendingIntent, fillInIntent);
}
+
+ private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+ Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
+ RemoteInput[] inputs = null;
+ if (tag instanceof RemoteInput[]) {
+ inputs = (RemoteInput[]) tag;
+ }
+
+ if (inputs == null) {
+ return false;
+ }
+
+ RemoteInput input = null;
+
+ for (RemoteInput i : inputs) {
+ if (i.getAllowFreeFormInput()) {
+ input = i;
+ }
+ }
+
+ if (input == null) {
+ return false;
+ }
+
+ ViewParent p = view.getParent();
+ RemoteInputView riv = null;
+ while (p != null) {
+ if (p instanceof View) {
+ View pv = (View) p;
+ if (pv.isRootNamespace()) {
+ riv = (RemoteInputView) pv.findViewWithTag(RemoteInputView.VIEW_TAG);
+ break;
+ }
+ }
+ p = p.getParent();
+ }
+
+ if (riv == null) {
+ return false;
+ }
+
+ riv.setVisibility(View.VISIBLE);
+ int cx = view.getLeft() + view.getWidth() / 2;
+ int cy = view.getTop() + view.getHeight() / 2;
+ int w = riv.getWidth();
+ int h = riv.getHeight();
+ int r = Math.max(
+ Math.max(cx + cy, cx + (h - cy)),
+ Math.max((w - cx) + cy, (w - cx) + (h - cy)));
+ ViewAnimationUtils.createCircularReveal(riv, cx, cy, 0, r)
+ .start();
+
+ riv.setPendingIntent(pendingIntent);
+ riv.setRemoteInput(inputs, input);
+ riv.focus();
+
+ return true;
+ }
+
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1551,15 +1615,15 @@
RemoteInput remoteInput = null;
- // See if the notification has exactly one action and this action allows free-form input
- // TODO: relax restrictions once we support more than one remote input action.
Notification.Action[] actions = entry.notification.getNotification().actions;
- if (actions != null && actions.length == 1) {
- if (actions[0].getRemoteInputs() != null) {
- for (RemoteInput ri : actions[0].getRemoteInputs()) {
- if (ri.getAllowFreeFormInput()) {
- remoteInput = ri;
- break;
+ if (actions != null) {
+ for (Notification.Action a : actions) {
+ if (a.getRemoteInputs() != null) {
+ for (RemoteInput ri : a.getRemoteInputs()) {
+ if (ri.getAllowFreeFormInput()) {
+ remoteInput = ri;
+ break;
+ }
}
}
}
@@ -1569,32 +1633,36 @@
if (remoteInput != null) {
View bigContentView = entry.getExpandedContentView();
if (bigContentView != null) {
- inflateRemoteInput(bigContentView, entry, remoteInput, actions);
+ inflateRemoteInput(bigContentView, entry);
}
View headsUpContentView = entry.getHeadsUpContentView();
if (headsUpContentView != null) {
- inflateRemoteInput(headsUpContentView, entry, remoteInput, actions);
+ inflateRemoteInput(headsUpContentView, entry);
}
}
}
- private void inflateRemoteInput(View view, Entry entry, RemoteInput remoteInput,
- Notification.Action[] actions) {
- View actionContainerCandidate = view.findViewById(com.android.internal.R.id.actions);
- if (actionContainerCandidate instanceof ViewGroup) {
- ViewGroup actionContainer = (ViewGroup) actionContainerCandidate;
- RemoteInputView riv = inflateRemoteInputView(actionContainer, entry,
- actions[0], remoteInput);
+ private RemoteInputView inflateRemoteInput(View view, Entry entry) {
+ View actionContainerCandidate = view.findViewById(
+ com.android.internal.R.id.actions_container);
+ if (actionContainerCandidate instanceof FrameLayout) {
+ ViewGroup actionContainer = (FrameLayout) actionContainerCandidate;
+ RemoteInputView riv = inflateRemoteInputView(actionContainer, entry);
if (riv != null) {
- actionContainer.removeAllViews();
- actionContainer.addView(riv);
+ riv.setVisibility(View.INVISIBLE);
+ actionContainer.addView(riv, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT)
+ );
+ riv.setBackgroundColor(entry.notification.getNotification().color);
+ return riv;
}
}
+ return null;
}
- protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
- Notification.Action action, RemoteInput remoteInput) {
+ protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry) {
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 0cddf1d..18445df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1101,10 +1101,8 @@
}
@Override
- protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry,
- Notification.Action action, RemoteInput remoteInput) {
- return RemoteInputView.inflate(mContext, root, entry, action, remoteInput,
- mRemoteInputController);
+ protected RemoteInputView inflateRemoteInputView(ViewGroup root, Entry entry) {
+ return RemoteInputView.inflate(mContext, root, entry, mRemoteInputController);
}
public int getStatusBarHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 2ad9287..acfe54d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -34,11 +34,13 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+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.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@@ -47,16 +49,21 @@
/**
* Host for the remote input.
*/
-public class RemoteInputView extends FrameLayout implements View.OnClickListener {
+public class RemoteInputView extends LinearLayout implements View.OnClickListener {
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();
+
private RemoteEditText mEditText;
+ private ImageButton mSendButton;
private ProgressBar mProgressBar;
private PendingIntent mPendingIntent;
+ private RemoteInput[] mRemoteInputs;
private RemoteInput mRemoteInput;
- private Notification.Action mAction;
private RemoteInputController mController;
+
private NotificationData.Entry mEntry;
public RemoteInputView(Context context, AttributeSet attrs) {
@@ -69,6 +76,9 @@
mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
+ mSendButton = (ImageButton) findViewById(R.id.remote_input_send);
+ mSendButton.setOnClickListener(this);
+
mEditText = (RemoteEditText) getChildAt(0);
mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
@@ -99,10 +109,11 @@
Bundle results = new Bundle();
results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
+ RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
results);
mEditText.setEnabled(false);
+ mSendButton.setVisibility(INVISIBLE);
mProgressBar.setVisibility(VISIBLE);
try {
@@ -113,17 +124,13 @@
}
public static RemoteInputView inflate(Context context, ViewGroup root,
- NotificationData.Entry entry, Notification.Action action, RemoteInput remoteInput,
+ NotificationData.Entry entry,
RemoteInputController controller) {
RemoteInputView v = (RemoteInputView)
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
-
- v.mEditText.setHint(action.title);
- v.mPendingIntent = action.actionIntent;
- v.mRemoteInput = remoteInput;
- v.mAction = action;
v.mController = controller;
v.mEntry = entry;
+ v.setTag(VIEW_TAG);
return v;
}
@@ -132,15 +139,16 @@
public void onClick(View v) {
if (v == mEditText) {
if (!mEditText.isFocusable()) {
- mEditText.setInnerFocusable(true);
- mController.addRemoteInput(mEntry);
- mEditText.mShowImeOnInputConnection = true;
+ focus();
}
+ } else if (v == mSendButton) {
+ sendRemoteInput();
}
}
public void onDefocus() {
mController.removeRemoteInput(mEntry);
+ setVisibility(INVISIBLE);
}
@Override
@@ -149,6 +157,23 @@
mController.removeRemoteInput(mEntry);
}
+ public void setPendingIntent(PendingIntent pendingIntent) {
+ mPendingIntent = pendingIntent;
+ }
+
+ public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+ mRemoteInputs = remoteInputs;
+ mRemoteInput = remoteInput;
+ mEditText.setHint(mRemoteInput.getLabel());
+ }
+
+ public void focus() {
+ mEditText.setInnerFocusable(true);
+ mController.addRemoteInput(mEntry);
+ mEditText.mShowImeOnInputConnection = true;
+ mEditText.requestFocus();
+ }
+
/**
* 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.
@@ -220,6 +245,13 @@
return inputConnection;
}
+ @Override
+ public void onCommitCompletion(CompletionInfo text) {
+ clearComposingText();
+ setText(text.getText());
+ setSelection(getText().length());
+ }
+
void setInnerFocusable(boolean focusable) {
setFocusableInTouchMode(focusable);
setFocusable(focusable);