summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorAdrian Roos <roosa@google.com>2015-02-10 20:49:33 +0100
committerAdrian Roos <roosa@google.com>2015-03-09 18:31:58 +0100
commit497ab023f9ed121664a210c380b43746b13e9038 (patch)
treed88a47dde8266c32c530e3f2943abe2fd1d2e3d6 /packages
parentf855b0d2d0c0c157d657668fcb98097e2aca097e (diff)
downloadframeworks_base-497ab023f9ed121664a210c380b43746b13e9038.zip
frameworks_base-497ab023f9ed121664a210c380b43746b13e9038.tar.gz
frameworks_base-497ab023f9ed121664a210c380b43746b13e9038.tar.bz2
Add prototype of Inline Reply behind debug flag
The flag also extracts wear-only RemoteInput actions to the normal set of actions under certain circumstances to make this prototype actually useful. Change-Id: Ide8bbef4e2ab82e2f152d32b143876ed309a8f21
Diffstat (limited to 'packages')
-rw-r--r--packages/SystemUI/res/layout/remote_input.xml46
-rw-r--r--packages/SystemUI/res/values/styles.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java191
5 files changed, 351 insertions, 1 deletions
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
new file mode 100644
index 0000000..8ca5634
--- /dev/null
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2015 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
+ -->
+
+<!-- FrameLayout -->
+<com.android.systemui.statusbar.policy.RemoteInputView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:theme="@style/systemui_theme_light"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:paddingStart="4dp"
+ android:paddingEnd="2dp"
+ 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:singleLine="true"
+ android:imeOptions="actionSend" />
+
+ <ProgressBar
+ android:id="@+id/remote_input_progress"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:visibility="invisible"
+ android:indeterminate="true"
+ style="?android:attr/progressBarStyleHorizontal" />
+
+</com.android.systemui.statusbar.policy.RemoteInputView>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 94f77c6..07fcb82 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -211,6 +211,11 @@
<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>
+
<style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog">
<item name="android:colorPrimary">@color/system_primary_color</item>
<item name="android:colorControlActivated">@color/system_accent_color</item>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 3a812cc..2fb4bde 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -24,6 +24,7 @@ import android.app.ActivityManagerNative;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.app.TaskStackBuilder;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
@@ -49,6 +50,7 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -97,6 +99,7 @@ import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
import com.android.systemui.statusbar.policy.PreviewInflater;
+import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
@@ -116,6 +119,9 @@ public abstract class BaseStatusBar extends SystemUI implements
// STOPSHIP disable once we resolve b/18102199
private static final boolean NOTIFICATION_CLICK_DEBUG = true;
+ public static final boolean ENABLE_REMOTE_INPUT =
+ Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.enable_remote_input", false);
+
protected static final int MSG_SHOW_RECENT_APPS = 1019;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
@@ -409,6 +415,7 @@ public abstract class BaseStatusBar extends SystemUI implements
@Override
public void run() {
for (StatusBarNotification sbn : notifications) {
+ processForRemoteInput(sbn.getNotification());
addNotification(sbn, currentRanking);
}
}
@@ -423,6 +430,7 @@ public abstract class BaseStatusBar extends SystemUI implements
mHandler.post(new Runnable() {
@Override
public void run() {
+ processForRemoteInput(sbn.getNotification());
Notification n = sbn.getNotification();
boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
|| isHeadsUp(sbn.getKey());
@@ -1356,6 +1364,9 @@ public abstract class BaseStatusBar extends SystemUI implements
(NotificationContentView) row.findViewById(R.id.expandedPublic);
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
+ if (ENABLE_REMOTE_INPUT) {
+ row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
+ }
PendingIntent contentIntent = sbn.getNotification().contentIntent;
if (contentIntent != null) {
@@ -1517,9 +1528,103 @@ public abstract class BaseStatusBar extends SystemUI implements
}
row.setUserLocked(userLocked);
row.setStatusBarNotification(entry.notification);
+ applyRemoteInput(entry);
return true;
}
+ /**
+ * Adds RemoteInput actions from the WearableExtender; to be removed once more apps support this
+ * via first-class API.
+ *
+ * TODO: Remove once enough apps specify remote inputs on their own.
+ */
+ private void processForRemoteInput(Notification n) {
+ if (!ENABLE_REMOTE_INPUT) return;
+
+ if (n.extras != null && n.extras.containsKey("android.wearable.EXTENSIONS") &&
+ (n.actions == null || n.actions.length == 0)) {
+ Notification.Action viableAction = null;
+ Notification.WearableExtender we = new Notification.WearableExtender(n);
+
+ List<Notification.Action> actions = we.getActions();
+ final int numActions = actions.size();
+
+ for (int i = 0; i < numActions; i++) {
+ Notification.Action action = actions.get(i);
+ RemoteInput[] remoteInputs = action.getRemoteInputs();
+ for (RemoteInput ri : action.getRemoteInputs()) {
+ if (ri.getAllowFreeFormInput()) {
+ viableAction = action;
+ break;
+ }
+ }
+ if (viableAction != null) {
+ break;
+ }
+ }
+
+ if (viableAction != null) {
+ Notification stripped = n.clone();
+ Notification.Builder.stripForDelivery(stripped);
+ stripped.actions = new Notification.Action[] { viableAction };
+ stripped.extras.putBoolean("android.rebuild.contentView", true);
+ stripped.contentView = null;
+ stripped.extras.putBoolean("android.rebuild.bigView", true);
+ stripped.bigContentView = null;
+
+ // Don't create the HUN input view for now because input doesn't work there yet.
+ // TODO: Enable once HUNs can take remote input correctly.
+ if (false) {
+ stripped.extras.putBoolean("android.rebuild.hudView", true);
+ stripped.headsUpContentView = null;
+ }
+
+ Notification rebuilt = Notification.Builder.rebuild(mContext, stripped);
+
+ n.actions = rebuilt.actions;
+ n.bigContentView = rebuilt.bigContentView;
+ n.headsUpContentView = rebuilt.headsUpContentView;
+ n.publicVersion = rebuilt.publicVersion;
+ }
+ }
+ }
+
+ private void applyRemoteInput(final Entry entry) {
+ if (!ENABLE_REMOTE_INPUT) return;
+
+ 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;
+ }
+ }
+ }
+ }
+
+ // See if we have somewhere to put that remote input
+ ViewGroup actionContainer = null;
+ if (remoteInput != null && entry.expandedBig != null) {
+ View actionContainerCandidate = entry.expandedBig
+ .findViewById(com.android.internal.R.id.actions);
+ if (actionContainerCandidate instanceof ViewGroup) {
+ actionContainer = (ViewGroup) actionContainerCandidate;
+ }
+ }
+
+ if (actionContainer != null) {
+ actionContainer.removeAllViews();
+ actionContainer.addView(
+ RemoteInputView.inflate(mContext, actionContainer, actions[0], remoteInput));
+ }
+ }
+
public NotificationClicker makeClicker(PendingIntent intent, String notificationKey) {
return new NotificationClicker(intent, notificationKey);
}
@@ -2036,6 +2141,8 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.setStatusBarNotification(notification);
entry.row.notifyContentUpdated();
entry.row.resetHeight();
+
+ applyRemoteInput(entry);
}
protected void notifyHeadsUpScreenOn(boolean screenOn) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index eba7d9f..63bbf97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -115,7 +115,8 @@ public class StatusBarWindowManager {
private void applyFocusableFlag(State state) {
if (state.isKeyguardShowingAndNotOccluded() && state.keyguardNeedsInput
- && state.bouncerShowing) {
+ && state.bouncerShowing
+ || BaseStatusBar.ENABLE_REMOTE_INPUT && state.statusBarExpanded) {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
} else if (state.isKeyguardShowingAndNotOccluded() || state.statusBarFocusable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
new file mode 100644
index 0000000..7d721c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 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
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.R;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * Host for the remote input.
+ */
+public class RemoteInputView extends FrameLayout implements View.OnClickListener {
+
+ private static final String TAG = "RemoteInput";
+
+ private RemoteEditText mEditText;
+ private ProgressBar mProgressBar;
+ private PendingIntent mPendingIntent;
+ private RemoteInput mRemoteInput;
+ private Notification.Action mAction;
+
+ public RemoteInputView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mProgressBar = (ProgressBar) findViewById(R.id.remote_input_progress);
+
+ mEditText = (RemoteEditText) getChildAt(0);
+ mEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+
+ // Check if this was the result of hitting the enter key
+ final boolean isSoftImeEvent = event == null
+ && (actionId == EditorInfo.IME_ACTION_DONE
+ || actionId == EditorInfo.IME_ACTION_NEXT
+ || actionId == EditorInfo.IME_ACTION_SEND);
+ final boolean isKeyboardEnterKey = event != null
+ && KeyEvent.isConfirmKey(event.getKeyCode())
+ && event.getAction() == KeyEvent.ACTION_DOWN;
+
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ sendRemoteInput();
+ return true;
+ }
+ return false;
+ }
+ });
+ mEditText.setOnClickListener(this);
+ mEditText.setInnerFocusable(false);
+ }
+
+ private void sendRemoteInput() {
+ Bundle results = new Bundle();
+ results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
+ Intent fillInIntent = new Intent();
+ RemoteInput.addResultsToIntent(mAction.getRemoteInputs(), fillInIntent,
+ results);
+
+ mEditText.setEnabled(false);
+ mProgressBar.setVisibility(VISIBLE);
+
+ try {
+ mPendingIntent.send(mContext, 0, fillInIntent);
+ } catch (PendingIntent.CanceledException e) {
+ Log.i(TAG, "Unable to send remote input result", e);
+ }
+ }
+
+ public static RemoteInputView inflate(Context context, ViewGroup root,
+ Notification.Action action, RemoteInput remoteInput) {
+ 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;
+
+ return v;
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == mEditText) {
+ if (!mEditText.isFocusable()) {
+ mEditText.setInnerFocusable(true);
+ InputMethodManager imm = InputMethodManager.getInstance();
+ if (imm != null) {
+ imm.viewClicked(mEditText);
+ imm.showSoftInput(mEditText, 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public static class RemoteEditText extends EditText {
+
+ private final Drawable mBackground;
+
+ public RemoteEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mBackground = getBackground();
+ }
+
+ private void defocusIfNeeded() {
+ if (isFocusable() && isEnabled()) {
+ setInnerFocusable(false);
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ if (!isShown()) {
+ defocusIfNeeded();
+ }
+ }
+
+ @Override
+ protected void onFocusLost() {
+ super.onFocusLost();
+ defocusIfNeeded();
+ }
+
+ @Override
+ public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ defocusIfNeeded();
+ }
+ return super.onKeyPreIme(keyCode, event);
+ }
+
+
+ void setInnerFocusable(boolean focusable) {
+ setFocusableInTouchMode(focusable);
+ setFocusable(focusable);
+ setCursorVisible(focusable);
+
+ if (focusable) {
+ requestFocus();
+ setBackground(mBackground);
+ } else {
+ setBackground(null);
+ }
+
+ }
+ }
+}