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
This commit is contained in:
Daniel Sandler
2010-10-26 16:55:56 -04:00
parent e5bc8f617b
commit b0cc50de14
18 changed files with 312 additions and 21 deletions

View File

@@ -0,0 +1,43 @@
<?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>

View File

@@ -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"

View File

@@ -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;
}
}
}

View File

@@ -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);
@@ -336,7 +436,7 @@ public class TabletStatusBarService extends StatusBarService {
}
mNotificationTrigger.setImageResource(resId);
}
public void setBatteryMeter(int level, boolean plugged) {
if (DEBUG) Slog.d(TAG, "battery=" + level + (plugged ? " - plugged" : " - unplugged"));
mBatteryMeter.setImageResource(R.drawable.sysbar_batterymini);
@@ -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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 616 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

View File

@@ -44,6 +44,20 @@ public class NotificationTestList extends TestActivity
long mActivityCreateTime = System.currentTimeMillis();
long mChronometerBase = 0;
final int[] kNumberedIconResIDs = {
R.drawable.notification0,
R.drawable.notification1,
R.drawable.notification2,
R.drawable.notification3,
R.drawable.notification4,
R.drawable.notification5,
R.drawable.notification6,
R.drawable.notification7,
R.drawable.notification8,
R.drawable.notification9
};
final int kUnnumberedIconResID = R.drawable.notificationx;
@Override
protected String tag() {
return TAG;
@@ -708,7 +722,8 @@ public class NotificationTestList extends TestActivity
new Test("Ten Notifications") {
public void run() {
for (int i = 0; i < 2; i++) {
Notification n = new Notification(NotificationTestList.this, R.drawable.icon2,
Notification n = new Notification(NotificationTestList.this,
kNumberedIconResIDs[i],
null, System.currentTimeMillis(), "Persistent #" + i,
"Notify me!!!" + i, null);
n.flags |= Notification.FLAG_ONGOING_EVENT;
@@ -716,7 +731,8 @@ public class NotificationTestList extends TestActivity
mNM.notify((i+1)*10, n);
}
for (int i = 2; i < 10; i++) {
Notification n = new Notification(NotificationTestList.this, R.drawable.icon2,
Notification n = new Notification(NotificationTestList.this,
kNumberedIconResIDs[i],
null, System.currentTimeMillis(), "Persistent #" + i,
"Notify me!!!" + i, null);
n.number = i;
@@ -733,6 +749,13 @@ public class NotificationTestList extends TestActivity
}
},
new Test("Cancel the other two notifications") {
public void run() {
mNM.cancel(10);
mNM.cancel(100);
}
},
new Test("Persistent with numbers 1") {
public void run() {
mNM.notify(1, notificationWithNumbers(1));
@@ -776,8 +799,14 @@ public class NotificationTestList extends TestActivity
};
private Notification notificationWithNumbers(int num) {
Notification n = new Notification(this, R.drawable.icon2, null, System.currentTimeMillis(),
"Persistent #2", "Notify me!!!", null);
Notification n = new Notification(this,
(num >= 0 && num < kNumberedIconResIDs.length)
? kNumberedIconResIDs[num]
: kUnnumberedIconResID,
null,
System.currentTimeMillis(),
"Notification", "Number=" + num,
null);
n.number = num;
return n;
}