OSDN Git Service

Visuals for Inline Reply
authorAdrian Roos <roosa@google.com>
Wed, 4 Nov 2015 23:55:39 +0000 (15:55 -0800)
committerAdrian Roos <roosa@google.com>
Mon, 23 Nov 2015 20:07:49 +0000 (20:07 +0000)
Change-Id: I374a7ec82795f95fe2f3ce8c9e6b02c1479433af

12 files changed:
core/java/android/app/Notification.java
core/java/android/widget/RemoteViews.java
core/res/res/layout/notification_material_action.xml
core/res/res/layout/notification_material_action_list.xml
core/res/res/values/ids.xml
core/res/res/values/symbols.xml
packages/SystemUI/res/drawable/ic_send.xml [new file with mode: 0644]
packages/SystemUI/res/layout/remote_input.xml
packages/SystemUI/res/values/styles.xml
packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java

index 0b77be3..af990de 100644 (file)
@@ -3311,12 +3311,17 @@ public class Notification implements Parcelable
                     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;
         }
index ca1b211..ce1c108 100644 (file)
@@ -21,6 +21,7 @@ import android.app.ActivityOptions;
 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 @@ public class RemoteViews implements Parcelable, Filter {
     };
 
     /**
+     * @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 @@ public class RemoteViews implements Parcelable, Filter {
     }
 
     /**
+     * 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 @@ public class RemoteViews implements Parcelable, Filter {
                         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");
                     }
index da8b2e7..f4bc918 100644 (file)
 <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"
index 2a36949..edaf020 100644 (file)
      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>
index 9d5e5ac..c03fbeb 100644 (file)
   
   <!-- 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>
index e8f6b46..5516360 100644 (file)
   <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 (file)
index 0000000..b1c7914
--- /dev/null
@@ -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>
index 8ca5634..74092c1 100644 (file)
   ~ 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>
index 4462a03..2fd0fe5 100644 (file)
         <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">
index d3d9bef..50c30b1 100644 (file)
@@ -80,6 +80,7 @@ import android.view.WindowManagerGlobal;
 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 @@ public abstract class BaseStatusBar extends SystemUI implements
         @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 @@ public abstract class BaseStatusBar extends SystemUI implements
                 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 @@ public abstract class BaseStatusBar extends SystemUI implements
 
         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 @@ public abstract class BaseStatusBar extends SystemUI implements
         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;
     }
 
index 0cddf1d..18445df 100644 (file)
@@ -1101,10 +1101,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
     }
 
     @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() {
index 2ad9287..acfe54d 100644 (file)
@@ -34,11 +34,13 @@ import android.view.KeyEvent;
 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 @@ import java.util.ArrayList;
 /**
  * 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 @@ public class RemoteInputView extends FrameLayout implements View.OnClickListener
 
         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 @@ public class RemoteInputView extends FrameLayout implements View.OnClickListener
         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 class RemoteInputView extends FrameLayout implements View.OnClickListener
     }
 
     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 class RemoteInputView extends FrameLayout implements View.OnClickListener
     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 @@ public class RemoteInputView extends FrameLayout implements View.OnClickListener
         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 @@ public class RemoteInputView extends FrameLayout implements View.OnClickListener
             return inputConnection;
         }
 
+        @Override
+        public void onCommitCompletion(CompletionInfo text) {
+            clearComposingText();
+            setText(text.getText());
+            setSelection(getText().length());
+        }
+
         void setInnerFocusable(boolean focusable) {
             setFocusableInTouchMode(focusable);
             setFocusable(focusable);