Zoom notifications on lockscreen after tapping.

For the double tap interaction, this change introduces a new cue
that the notifications must be double-tapped: With the first tap,
the tapped notifications gets larger and the others fade out a bit.

Change-Id: Ib48ff0291aee1a5ec083b9e7ed1021bc420514cf
This commit is contained in:
Jorim Jaggi
2014-04-15 15:42:55 +02:00
parent 18769204be
commit c5dc0d0cce
8 changed files with 297 additions and 30 deletions

View File

@@ -14,7 +14,8 @@
~ limitations under the License
-->
<FrameLayout
<!-- Extends FrameLayout -->
<com.android.systemui.statusbar.NotificationOverflowContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -48,4 +49,4 @@
android:layout_height="wrap_content"
/>
</com.android.systemui.statusbar.LatestItemView>
</FrameLayout>
</com.android.systemui.statusbar.NotificationOverflowContainer>

View File

@@ -83,7 +83,7 @@
<dimen name="notification_mid_height">128dp</dimen>
<!-- Height of a small notification in the status bar plus glow, padding, etc -->
<dimen name="notification_row_min_height">70dp</dimen>
<dimen name="notification_row_min_height">68dp</dimen>
<!-- Height of a large notification in the status bar plus glow, padding, etc -->
<dimen name="notification_row_max_height">260dp</dimen>
@@ -98,7 +98,7 @@
<dimen name="status_bar_icon_padding">0dp</dimen>
<!-- half the distance between notifications in the panel -->
<dimen name="notification_divider_height">3dp</dimen>
<dimen name="notification_divider_height">2dp</dimen>
<!-- Notification drawer tuning parameters (phone UI) -->
<!-- Initial velocity of the shade when expanding on its own -->

View File

@@ -83,7 +83,7 @@ import java.util.ArrayList;
import java.util.Locale;
public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks {
CommandQueue.Callbacks, LatestItemView.OnActivatedListener {
public static final String TAG = "StatusBar";
public static final boolean DEBUG = false;
public static final boolean MULTIUSER_DEBUG = false;
@@ -169,8 +169,7 @@ public abstract class BaseStatusBar extends SystemUI implements
protected int mZenMode;
protected boolean mOnKeyguard;
protected View mKeyguardIconOverflowContainer;
protected NotificationOverflowIconsView mOverflowIconsView;
protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
public boolean isDeviceProvisioned() {
return mDeviceProvisioned;
@@ -882,6 +881,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
entry.row = row;
entry.row.setHeightRange(mRowMinHeight, mRowMaxHeight);
entry.row.setOnActivatedListener(this);
entry.content = content;
entry.expanded = contentViewLocal;
entry.expandedPublic = publicViewLocal;
@@ -1067,7 +1067,7 @@ public abstract class BaseStatusBar extends SystemUI implements
*/
protected void updateRowStates() {
int maxKeyguardNotifications = getMaxKeyguardNotifications();
mOverflowIconsView.removeAllViews();
mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
int n = mNotificationData.size();
int visibleNotifications = 0;
for (int i = n-1; i >= 0; i--) {
@@ -1087,7 +1087,7 @@ public abstract class BaseStatusBar extends SystemUI implements
|| !showOnKeyguard)) {
entry.row.setVisibility(View.GONE);
if (showOnKeyguard) {
mOverflowIconsView.addNotification(entry);
mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
}
} else {
entry.row.setVisibility(View.VISIBLE);
@@ -1095,13 +1095,49 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
if (mOnKeyguard && mOverflowIconsView.getChildCount() > 0) {
if (mOnKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
} else {
mKeyguardIconOverflowContainer.setVisibility(View.GONE);
}
}
@Override
public void onActivated(View view) {
int n = mNotificationData.size();
for (int i = 0; i < n; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
if (entry.row.getVisibility() != View.GONE) {
if (view == entry.row) {
entry.row.getActivator().activate();
} else {
entry.row.getActivator().activateInverse();
}
}
}
if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
if (view == mKeyguardIconOverflowContainer) {
mKeyguardIconOverflowContainer.getActivator().activate();
} else {
mKeyguardIconOverflowContainer.getActivator().activateInverse();
}
}
}
@Override
public void onReset(View view) {
int n = mNotificationData.size();
for (int i = 0; i < n; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
if (entry.row.getVisibility() != View.GONE) {
entry.row.getActivator().reset();
}
}
if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
mKeyguardIconOverflowContainer.getActivator().reset();
}
}
private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
return sbn.getNotification().priority >= Notification.PRIORITY_LOW;
}

View File

@@ -25,7 +25,8 @@ import android.widget.FrameLayout;
import com.android.internal.widget.SizeAdaptiveLayout;
import com.android.systemui.R;
public class ExpandableNotificationRow extends FrameLayout {
public class ExpandableNotificationRow extends FrameLayout
implements LatestItemView.OnActivatedListener {
private int mRowMinHeight;
private int mRowMaxHeight;
@@ -51,6 +52,8 @@ public class ExpandableNotificationRow extends FrameLayout {
private SizeAdaptiveLayout mPrivateLayout;
private int mMaxExpandHeight;
private boolean mMaxHeightNeedsUpdate;
private NotificationActivator mActivator;
private LatestItemView.OnActivatedListener mOnActivatedListener;
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -62,8 +65,10 @@ public class ExpandableNotificationRow extends FrameLayout {
mPublicLayout = (SizeAdaptiveLayout) findViewById(R.id.expandedPublic);
mPrivateLayout = (SizeAdaptiveLayout) findViewById(R.id.expanded);
mLatestItemView = (LatestItemView) findViewById(R.id.container);
}
mActivator = new NotificationActivator(this);
mLatestItemView.setOnActivatedListener(this);
}
public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
mRowMinHeight = rowMinHeight;
@@ -202,6 +207,7 @@ public class ExpandableNotificationRow extends FrameLayout {
*/
public void setDimmed(boolean dimmed) {
mLatestItemView.setDimmed(dimmed);
mActivator.setDimmed(dimmed);
}
public int getMaxExpandHeight() {
@@ -220,6 +226,28 @@ public class ExpandableNotificationRow extends FrameLayout {
mLatestItemView.setLocked(locked);
}
public void setOnActivatedListener(LatestItemView.OnActivatedListener listener) {
mOnActivatedListener = listener;
}
public NotificationActivator getActivator() {
return mActivator;
}
@Override
public void onActivated(View view) {
if (mOnActivatedListener != null) {
mOnActivatedListener.onActivated(this);
}
}
@Override
public void onReset(View view) {
if (mOnActivatedListener != null) {
mOnActivatedListener.onReset(this);
}
}
/**
* Sets the resource id for the background of this notification.
*

View File

@@ -17,7 +17,6 @@
package com.android.systemui.statusbar;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
@@ -46,7 +45,8 @@ public class LatestItemView extends FrameLayout {
private float mDownX;
private float mDownY;
private final float mTouchSlop;
private boolean mHotspotActive;
private OnActivatedListener mOnActivatedListener;
public LatestItemView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -90,14 +90,17 @@ public class LatestItemView extends FrameLayout {
private boolean handleTouchEventLocked(MotionEvent event) {
int action = event.getActionMasked();
Drawable background = getBackground();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
if (!mActivated) {
background.setHotspot(0, event.getX(), event.getY());
mHotspotActive = true;
// Call the listener tentatively directly, even if we don't know whether the user
// will stay within the touch slop, as the listener is implemented as a scale
// animation, which is cancellable without jarring effects when swiping away
// notifications.
if (mOnActivatedListener != null) {
mOnActivatedListener.onActivated(this);
}
break;
case MotionEvent.ACTION_MOVE:
@@ -109,7 +112,7 @@ public class LatestItemView extends FrameLayout {
case MotionEvent.ACTION_UP:
if (isWithinTouchSlop(event)) {
if (!mActivated) {
mActivated = true;
makeActive(event.getX(), event.getY());
postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
} else {
performClick();
@@ -128,17 +131,24 @@ public class LatestItemView extends FrameLayout {
return true;
}
private void makeActive(float x, float y) {
getBackground().setHotspot(0, x, y);
mActivated = true;
}
/**
* Cancels the hotspot and makes the notification inactive.
*/
private void makeInactive() {
if (mHotspotActive) {
if (mActivated) {
// Make sure that we clear the hotspot from the center.
getBackground().setHotspot(0, getWidth()/2, getHeight()/2);
getBackground().setHotspot(0, getWidth() / 2, getHeight() / 2);
getBackground().removeHotspot(0);
mHotspotActive = false;
mActivated = false;
}
if (mOnActivatedListener != null) {
mOnActivatedListener.onReset(this);
}
mActivated = false;
removeCallbacks(mTapTimeoutRunnable);
}
@@ -180,4 +190,13 @@ public class LatestItemView extends FrameLayout {
private void updateBackgroundResource() {
setBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId);
}
public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
mOnActivatedListener = onActivatedListener;
}
public interface OnActivatedListener {
void onActivated(View view);
void onReset(View view);
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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
*/
package com.android.systemui.statusbar;
import android.content.Context;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
/**
* A helper class used by both {@link com.android.systemui.statusbar.ExpandableNotificationRow} and
* {@link com.android.systemui.statusbar.NotificationOverflowIconsView} to make a notification look
* active after tapping it once on the Keyguard.
*/
public class NotificationActivator {
private static final int ANIMATION_LENGTH_MS = 220;
private static final float INVERSE_ALPHA = 0.9f;
private static final float DIMMED_SCALE = 0.95f;
private final View mTargetView;
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mLinearOutSlowInInterpolator;
private final int mTranslationZ;
public NotificationActivator(View targetView) {
mTargetView = targetView;
Context ctx = targetView.getContext();
mFastOutSlowInInterpolator =
AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in);
mLinearOutSlowInInterpolator =
AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in);
mTranslationZ =
ctx.getResources().getDimensionPixelSize(R.dimen.z_distance_between_notifications);
mTargetView.animate().setDuration(ANIMATION_LENGTH_MS);
}
public void activateInverse() {
mTargetView.animate().withLayer().alpha(INVERSE_ALPHA);
}
public void activate() {
mTargetView.animate()
.setInterpolator(mLinearOutSlowInInterpolator)
.scaleX(1)
.scaleY(1)
.translationZBy(mTranslationZ);
}
public void reset() {
mTargetView.animate()
.setInterpolator(mFastOutSlowInInterpolator)
.scaleX(DIMMED_SCALE)
.scaleY(DIMMED_SCALE)
.translationZBy(-mTranslationZ);
if (mTargetView.getAlpha() != 1.0f) {
mTargetView.animate().withLayer().alpha(1);
}
}
public void setDimmed(boolean dimmed) {
if (dimmed) {
mTargetView.setScaleX(DIMMED_SCALE);
mTargetView.setScaleY(DIMMED_SCALE);
} else {
mTargetView.setScaleX(1);
mTargetView.setScaleY(1);
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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
*/
package com.android.systemui.statusbar;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.systemui.R;
/**
* Container view for overflowing notification icons on Keyguard.
*/
public class NotificationOverflowContainer extends FrameLayout
implements LatestItemView.OnActivatedListener {
private NotificationOverflowIconsView mIconsView;
private LatestItemView.OnActivatedListener mOnActivatedListener;
private NotificationActivator mActivator;
public NotificationOverflowContainer(Context context) {
super(context);
}
public NotificationOverflowContainer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NotificationOverflowContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NotificationOverflowContainer(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
LatestItemView latestItemView = (LatestItemView) findViewById(R.id.container);
mActivator = new NotificationActivator(this);
mActivator.setDimmed(true);
latestItemView.setOnActivatedListener(this);
latestItemView.setLocked(true);
}
public NotificationOverflowIconsView getIconsView() {
return mIconsView;
}
public void setOnActivatedListener(LatestItemView.OnActivatedListener onActivatedListener) {
mOnActivatedListener = onActivatedListener;
}
@Override
public void onActivated(View view) {
if (mOnActivatedListener != null) {
mOnActivatedListener.onActivated(this);
}
}
@Override
public void onReset(View view) {
if (mOnActivatedListener != null) {
mOnActivatedListener.onReset(this);
}
}
public NotificationActivator getActivator() {
return mActivator;
}
}

View File

@@ -99,6 +99,7 @@ import com.android.systemui.statusbar.InterceptedNotifications;
import com.android.systemui.statusbar.LatestItemView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.NotificationOverflowContainer;
import com.android.systemui.statusbar.NotificationOverflowIconsView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarIconView;
@@ -519,13 +520,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setChildLocationsChangedListener(mOnChildLocationsChangedListener);
mKeyguardIconOverflowContainer = LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
((LatestItemView) mKeyguardIconOverflowContainer.findViewById(R.id.container)).setLocked(true);
mOverflowIconsView = (NotificationOverflowIconsView) mKeyguardIconOverflowContainer.findViewById(
R.id.overflow_icons_view);
mOverflowIconsView.setMoreText(
(TextView) mKeyguardIconOverflowContainer.findViewById(R.id.more_text));
mKeyguardIconOverflowContainer =
(NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
mKeyguardIconOverflowContainer.setOnActivatedListener(this);
mStackScroller.addView(mKeyguardIconOverflowContainer);
mExpandedContents = mStackScroller;
@@ -2878,6 +2876,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode {
mNotificationPanel.setExpandedFraction(1);
}
@Override
public void onActivated(View view) {
userActivity();
super.onActivated(view);
}
@Override
protected int getMaxKeyguardNotifications() {
return mKeyguardMaxNotificationCount;