Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6f59787d authored by Adrian Roos's avatar Adrian Roos Committed by Android (Google) Code Review
Browse files

Merge "Add prototype of Inline Reply behind debug flag"

parents a72d6401 497ab023
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
<?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>
+5 −0
Original line number Diff line number Diff line
@@ -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>
+107 −0
Original line number Diff line number Diff line
@@ -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;
@@ -98,6 +100,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;
@@ -117,6 +120,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;
@@ -410,6 +416,7 @@ public abstract class BaseStatusBar extends SystemUI implements
                @Override
                public void run() {
                    for (StatusBarNotification sbn : notifications) {
                        processForRemoteInput(sbn.getNotification());
                        addNotification(sbn, currentRanking);
                    }
                }
@@ -424,6 +431,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());
@@ -1357,6 +1365,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) {
@@ -1518,9 +1529,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);
    }
@@ -2037,6 +2142,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) {
+2 −1
Original line number Diff line number Diff line
@@ -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) {
+191 −0
Original line number Diff line number Diff line
/*
 * 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);
            }

        }
    }
}