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

Commit b0cc50de authored by Daniel Sandler's avatar Daniel Sandler
Browse files

Notification flyovers.

Hold your finger on an individual notification icon to
quickly show that notification's payload.

Also: Quickly swipe up on any icon to open the tray.

Bug: 2994009

Change-Id: I2ae2b546fcfa62994b63b9376f487289b2d06796
parent e5bc8f61
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
/* apps/common/assets/default/default/skins/StatusBar.xml
**
** Copyright 2006, 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.
*/
-->

<!--    android:background="@drawable/status_bar_closed_default_background" -->
<com.android.systemui.statusbar.tablet.NotificationPanel
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:background="@*android:drawable/dialog_full_holo_dark"
    android:orientation="vertical"
    >

    <FrameLayout 
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal|bottom"
        android:animationCache="false"
        android:orientation="vertical"
        android:background="@drawable/status_bar_background"
        android:clickable="true"
        android:focusable="true"
        android:descendantFocusability="afterDescendants"
        >
    </FrameLayout>
</com.android.systemui.statusbar.tablet.NotificationPanel>
+4 −1
Original line number Diff line number Diff line
@@ -23,20 +23,23 @@
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:background="#FF000000"
    android:background="@*android:drawable/dialog_full_holo_dark"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    >

    <ScrollView
        android:id="@+id/notificationScroller"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:animateLayoutChanges="true"
        >
        <LinearLayout 
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal|bottom"
            android:animateLayoutChanges="true"
            android:animationCache="false"
            android:orientation="vertical"
            android:background="@drawable/status_bar_background"
+5 −4
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.util.Slog;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ImageView;
import android.view.MotionEvent;

import com.android.systemui.R;

@@ -43,12 +44,12 @@ public class NotificationIconArea extends LinearLayout {
        public IconLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    }

    static class DraggerView extends View {
        public DraggerView(Context context, AttributeSet attrs) {
            super(context, attrs);
        public boolean onInterceptTouchEvent(MotionEvent e) {
            return true;
        }
    }
}


+227 −12
Original line number Diff line number Diff line
@@ -18,7 +18,10 @@ package com.android.systemui.statusbar.tablet;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;

import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.app.ActivityManagerNative;
import android.app.PendingIntent;
import android.app.Notification;
@@ -41,6 +44,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
@@ -65,6 +69,8 @@ public class TabletStatusBarService extends StatusBarService {

    public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
    public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
    public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002;
    public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003;
    public static final int MSG_OPEN_SYSTEM_PANEL = 1010;
    public static final int MSG_CLOSE_SYSTEM_PANEL = 1011;
    public static final int MSG_OPEN_RECENTS_PANEL = 1020;
@@ -91,6 +97,13 @@ public class TabletStatusBarService extends StatusBarService {

    NotificationPanel mNotificationPanel;
    SystemPanel mSystemPanel;
    NotificationPanel mNotificationPeekWindow;
    ViewGroup mNotificationPeekRow;
    int mNotificationPeekIndex;
    LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight;

    int mNotificationPeekTapDuration;
    int mNotificationFlingVelocity;

    ViewGroup mPile;
    TextView mClearButton;
@@ -132,7 +145,7 @@ public class TabletStatusBarService extends StatusBarService {
        mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);

        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                400, // ViewGroup.LayoutParams.WRAP_CONTENT,
                512, // ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
@@ -144,6 +157,41 @@ public class TabletStatusBarService extends StatusBarService {

        WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);

        // Notification preview window
        mNotificationPeekWindow = (NotificationPanel) View.inflate(context,
                R.layout.sysbar_panel_notification_peek, null);
        mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(R.id.content);
        mNotificationPeekWindow.setVisibility(View.GONE);
        mNotificationPeekWindow.setOnTouchListener(
                new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPeekWindow));
        mNotificationPeekScrubRight = new LayoutTransition();
        mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, 
                ObjectAnimator.ofInt(null, "left", -512, 0));
        mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, 
                ObjectAnimator.ofInt(null, "left", -512, 0));
        mNotificationPeekScrubRight.setDuration(500);

        mNotificationPeekScrubLeft = new LayoutTransition();
        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, 
                ObjectAnimator.ofInt(null, "left", 512, 0));
        mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, 
                ObjectAnimator.ofInt(null, "left", 512, 0));
        mNotificationPeekScrubLeft.setDuration(500);

        // XXX: setIgnoreChildren?
        lp = new WindowManager.LayoutParams(
                512, // ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                    | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                PixelFormat.TRANSLUCENT);
        lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
        lp.setTitle("NotificationPeekWindow");
        lp.windowAnimations = com.android.internal.R.style.Animation_Toast;

        WindowManagerImpl.getDefault().addView(mNotificationPeekWindow, lp);

        // System Panel
        mSystemPanel = (SystemPanel) View.inflate(context, R.layout.sysbar_panel_system, null);
        mSystemPanel.setVisibility(View.GONE);
@@ -238,9 +286,13 @@ public class TabletStatusBarService extends StatusBarService {
        mDoNotDisturbButton = (TextView)mNotificationButtons.findViewById(R.id.do_not_disturb);
        mDoNotDisturbButton.setOnClickListener(mOnClickListener);


        // where the icons go
        mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
        mIconLayout.setOnTouchListener(new NotificationIconTouchListener());

        ViewConfiguration vc = ViewConfiguration.get(context);
        mNotificationPeekTapDuration = vc.getTapTimeout();
        mNotificationFlingVelocity = 300; // px/s

        mTicker = new TabletTicker(context, (FrameLayout)sb.findViewById(R.id.ticker));

@@ -276,9 +328,57 @@ public class TabletStatusBarService extends StatusBarService {
    private class H extends Handler {
        public void handleMessage(Message m) {
            switch (m.what) {
                case MSG_OPEN_NOTIFICATION_PEEK:
                    if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1);
                    if (m.arg1 >= 0) {
                        final int N = mNotns.size();
                        if (mNotificationPeekIndex < N) {
                            NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
                            entry.icon.setBackgroundColor(0);
                        }

                        final int peekIndex = m.arg1;
                        if (peekIndex < N) {
                            Slog.d(TAG, "loading peek: " + peekIndex);
                            NotificationData.Entry entry = mNotns.get(N-1-peekIndex);
                            NotificationData.Entry copy = new NotificationData.Entry(
                                    entry.key, 
                                    entry.notification, 
                                    entry.icon);
                            inflateViews(copy, mNotificationPeekRow);

                            entry.icon.setBackgroundColor(0x20FFFFFF);

//                          mNotificationPeekRow.setLayoutTransition(
//                              peekIndex < mNotificationPeekIndex 
//                                  ? mNotificationPeekScrubLeft
//                                  : mNotificationPeekScrubRight);

                            mNotificationPeekRow.removeAllViews();
                            mNotificationPeekRow.addView(copy.row);

                            mNotificationPeekWindow.setVisibility(View.VISIBLE);
                            mNotificationPanel.setVisibility(View.GONE);

                            mNotificationPeekIndex = peekIndex;
                        }
                    }
                    break;
                case MSG_CLOSE_NOTIFICATION_PEEK:
                    if (DEBUG) Slog.d(TAG, "closing notification peek window");
                    mNotificationPeekWindow.setVisibility(View.GONE);
                    mNotificationPeekRow.removeAllViews();
                    final int N = mNotns.size();
                    if (mNotificationPeekIndex < N) {
                        NotificationData.Entry entry = mNotns.get(N-1-mNotificationPeekIndex);
                        entry.icon.setBackgroundColor(0);
                    }
                    break;
                case MSG_OPEN_NOTIFICATION_PANEL:
                    if (DEBUG) Slog.d(TAG, "opening notifications panel");
                    if (mNotificationPanel.getVisibility() == View.GONE) {
                        mNotificationPeekWindow.setVisibility(View.GONE);

                        mDoNotDisturbButton.setText(mNotificationsOn
                                ? R.string.status_bar_do_not_disturb_button
                                : R.string.status_bar_please_disturb_button);
@@ -812,6 +912,72 @@ public class TabletStatusBarService extends StatusBarService {
        return entry.notification;
    }

    private class NotificationIconTouchListener implements View.OnTouchListener {
        VelocityTracker mVT;

        public NotificationIconTouchListener() {
        }

        public boolean onTouch(View v, MotionEvent event) {
            boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE;
            boolean panelShowing = mNotificationPanel.getVisibility() != View.GONE;
            if (panelShowing) return false;

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mVT = VelocityTracker.obtain();

                    // fall through
                case MotionEvent.ACTION_OUTSIDE:
                case MotionEvent.ACTION_MOVE:
                    // peek and switch icons if necessary
                    int numIcons = mIconLayout.getChildCount();
                    int peekIndex = 
                            (int)((float)event.getX() * numIcons / mIconLayout.getWidth());
                    if (peekIndex > numIcons - 1) peekIndex = numIcons - 1;
                    else if (peekIndex < 0) peekIndex = 0;

                    if (!peeking || mNotificationPeekIndex != peekIndex) {
                        if (DEBUG) Slog.d(TAG, "will peek at notification #" + peekIndex);
                        Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK);
                        peekMsg.arg1 = peekIndex;

                        mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);

                        // no delay if we're scrubbing left-right
                        mHandler.sendMessageDelayed(peekMsg,
                                peeking ? 0 : mNotificationPeekTapDuration);
                    }

                    // check for fling
                    if (mVT != null) {
                        mVT.addMovement(event);
                        mVT.computeCurrentVelocity(1000);
                        // require a little more oomph once we're already in peekaboo mode
                        if (!panelShowing && (
                               (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3)
                            || (mVT.getYVelocity() < -mNotificationFlingVelocity))) {
                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
                            mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
                            mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK);
                            mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
                        }
                    }
                    return true;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK);
                    if (peeking) {
                        mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, 250);
                    }
                    mVT.recycle();
                    mVT = null;
                    return true;
            }
            return false;
        }
    }

    StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
        if (DEBUG) {
            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
@@ -836,6 +1002,7 @@ public class TabletStatusBarService extends StatusBarService {
                    + notification);
            return null;
        }

        // Add the icon.
        mNotns.add(entry);
        refreshIcons();
@@ -847,28 +1014,76 @@ public class TabletStatusBarService extends StatusBarService {
        // XXX: need to implement a new limited linear layout class
        // to avoid removing & readding everything

        final int ICON_LIMIT = 4;
        final LinearLayout.LayoutParams params
            = new LinearLayout.LayoutParams(mIconSize, mIconSize);

        int N = mNotns.size();
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize, mIconSize);

        if (DEBUG) {
            Slog.d(TAG, "refreshing icons (" + N + " notifications, mIconLayout="
                    + mIconLayout + ", mPile=" + mPile);
            Slog.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout);
        }

        mIconLayout.removeAllViews();
        for (int i=0; i<4; i++) {
        ArrayList<View> toShow = new ArrayList<View>();

        for (int i=0; i<ICON_LIMIT; i++) {
            if (i>=N) break;
            mIconLayout.addView(mNotns.get(N-i-1).icon, i, params);
            toShow.add(mNotns.get(N-i-1).icon);
        }

        mPile.removeAllViews();
        for (int i=0; i<N; i++) {
            mPile.addView(mNotns.get(N-i-1).row);
        ArrayList<View> toRemove = new ArrayList<View>();
        for (int i=0; i<mIconLayout.getChildCount(); i++) {
            View child = mIconLayout.getChildAt(i);
            if (!toShow.contains(child)) {
                toRemove.add(child);
            }
        }

        for (View remove : toRemove) {
            mIconLayout.removeView(remove);
        }

        for (int i=0; i<toShow.size(); i++) {
            View v = toShow.get(i);
            if (v.getParent() == null) {
                mIconLayout.addView(toShow.get(i), i, params);
            }
        }

        loadNotificationPanel();
        refreshNotificationTrigger();
    }

    private void loadNotificationPanel() {
        int N = mNotns.size();

        ArrayList<View> toShow = new ArrayList<View>();

        for (int i=0; i<N; i++) {
            View row = mNotns.get(N-i-1).row;
            toShow.add(row);
        }

        ArrayList<View> toRemove = new ArrayList<View>();
        for (int i=0; i<mPile.getChildCount(); i++) {
            View child = mPile.getChildAt(i);
            if (!toShow.contains(child)) {
                toRemove.add(child);
            }
        }

        for (View remove : toRemove) {
            mPile.removeView(remove);
        }

        for (int i=0; i<toShow.size(); i++) {
            View v = toShow.get(i);
            if (v.getParent() == null) {
                mPile.addView(toShow.get(i));
            }
        }
    }

    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
        StatusBarNotification sbn = entry.notification;
        RemoteViews remoteViews = sbn.notification.contentView;
+1.3 KiB (1.91 KiB)
Loading image diff...
Loading