diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9f933ca66c21..f18c18a651b6 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -48,4 +48,7 @@ interface INotificationManager
void cancelAllNotificationsFromListener(in INotificationListener token);
StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token);
-}
\ No newline at end of file
+
+ void setHoverBlacklistStatus(String pkg, boolean status);
+ boolean isPackageAllowedForHover(String pkg);
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 16f0216904ad..6bc958e4a606 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2014, ParanoidAndroid Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -2792,6 +2793,32 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean
*/
public static final String POINTER_SPEED = "pointer_speed";
+ /**
+ * Whether to show the battery bar
+ * @hide
+ */
+ public static final String STATUSBAR_BATTERY_BAR = "statusbar_battery_bar";
+
+ /**
+ * @hide
+ */
+ public static final String STATUSBAR_BATTERY_BAR_COLOR = "statusbar_battery_bar_color";
+
+ /**
+ * @hide
+ */
+ public static final String STATUSBAR_BATTERY_BAR_THICKNESS = "statusbar_battery_bar_thickness";
+
+ /**
+ * @hide
+ */
+ public static final String STATUSBAR_BATTERY_BAR_STYLE = "statusbar_battery_bar_style";
+
+ /**
+ * @hide
+ */
+ public static final String STATUSBAR_BATTERY_BAR_ANIMATE = "statusbar_battery_bar_animate";
+
/**
* I am the lolrus.
*
@@ -3927,6 +3954,14 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean
*/
public static final String RECENT_PANEL_SHOW_TOPMOST = "recent_panel_show_topmost";
+ /**
+ * Hover, default is 0 (off).
+ * 0 = disabled
+ * 1 = enabled
+ * @hide
+ */
+ public static final String HOVER_STATE = "hover_state";
+
/**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
@@ -6120,6 +6155,9 @@ public static boolean putFloatForUser(ContentResolver cr, String name, float val
/** @hide */
public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
+ /** @hide */
+ public static final String HOVER_FIRST_TIME = "hover_first_time";
+
/**
* This is the query URI for finding a print service to install.
*
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notify_hover_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_notify_hover_normal.png
new file mode 100644
index 000000000000..209d5d7ce5e7
Binary files /dev/null and b/packages/SystemUI/res/drawable-hdpi/ic_notify_hover_normal.png differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notify_hover_pressed.png b/packages/SystemUI/res/drawable-hdpi/ic_notify_hover_pressed.png
new file mode 100644
index 000000000000..668e6bcf14f3
Binary files /dev/null and b/packages/SystemUI/res/drawable-hdpi/ic_notify_hover_pressed.png differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_notify_hover_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_notify_hover_normal.png
new file mode 100644
index 000000000000..f67409d101d2
Binary files /dev/null and b/packages/SystemUI/res/drawable-xhdpi/ic_notify_hover_normal.png differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_notify_hover_pressed.png b/packages/SystemUI/res/drawable-xhdpi/ic_notify_hover_pressed.png
new file mode 100644
index 000000000000..122bd588ba11
Binary files /dev/null and b/packages/SystemUI/res/drawable-xhdpi/ic_notify_hover_pressed.png differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_hover_normal.png b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_hover_normal.png
new file mode 100644
index 000000000000..8800220cef14
Binary files /dev/null and b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_hover_normal.png differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_hover_pressed.png b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_hover_pressed.png
new file mode 100644
index 000000000000..06b013889359
Binary files /dev/null and b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_hover_pressed.png differ
diff --git a/packages/SystemUI/res/layout/hover_cling.xml b/packages/SystemUI/res/layout/hover_cling.xml
new file mode 100644
index 000000000000..23d62e589177
--- /dev/null
+++ b/packages/SystemUI/res/layout/hover_cling.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/hover_container.xml b/packages/SystemUI/res/layout/hover_container.xml
new file mode 100644
index 000000000000..e1dc107cc5ba
--- /dev/null
+++ b/packages/SystemUI/res/layout/hover_container.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml
index 63e35e695fe6..cdac7457c2dc 100644
--- a/packages/SystemUI/res/layout/navigation_bar.xml
+++ b/packages/SystemUI/res/layout/navigation_bar.xml
@@ -31,6 +31,26 @@
android:layout_width="match_parent"
>
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 62989a834b7d..45cf7c9fac3f 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -84,16 +84,26 @@
+ />
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values/slim_arrays.xml b/packages/SystemUI/res/values/slim_arrays.xml
new file mode 100644
index 000000000000..6302d75600d1
--- /dev/null
+++ b/packages/SystemUI/res/values/slim_arrays.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ - com.google.android.googlequicksearchbox
+ - android
+
+
+
diff --git a/packages/SystemUI/res/values/slim_dimens.xml b/packages/SystemUI/res/values/slim_dimens.xml
index 9a3a9f715dcc..a1e1a233d300 100644
--- a/packages/SystemUI/res/values/slim_dimens.xml
+++ b/packages/SystemUI/res/values/slim_dimens.xml
@@ -25,4 +25,9 @@
9dp
+
+ 64dp
+ -1px
+
+
diff --git a/packages/SystemUI/res/values/slim_strings.xml b/packages/SystemUI/res/values/slim_strings.xml
index 1d5aa409483d..d981efd98e28 100644
--- a/packages/SystemUI/res/values/slim_strings.xml
+++ b/packages/SystemUI/res/values/slim_strings.xml
@@ -158,4 +158,7 @@
Cursor left
Cursor right
+
+
+ Hover is now enabled.\n\nWhen a new notification arrives, an overlay will show allowing you to respond to it without leaving current app.
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index e1a4bb261547..0a8ccdc42908 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -273,7 +273,7 @@ private boolean isInside(View v, float x, float y) {
if (v == null) {
if (DEBUG) Log.d(TAG, "isinside null subject");
- return false;
+ return true;
}
if (mEventSource != null) {
int[] location = new int[2];
@@ -519,7 +519,9 @@ private void startExpanding(View v, int expandType) {
}
if (DEBUG) Log.d(TAG, "got mOldHeight: " + mOldHeight +
" mNaturalHeight: " + mNaturalHeight);
- v.getParent().requestDisallowInterceptTouchEvent(true);
+ if (v != null && v.getParent() != null) {
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ }
}
private void finishExpanding(boolean force) {
diff --git a/packages/SystemUI/src/com/android/systemui/quicksettings/QuickRecordingsDialog.java b/packages/SystemUI/src/com/android/systemui/quicksettings/QuickRecordingsDialog.java
index 30c7b7c679fa..3f0abdbd779d 100644
--- a/packages/SystemUI/src/com/android/systemui/quicksettings/QuickRecordingsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/quicksettings/QuickRecordingsDialog.java
@@ -26,7 +26,6 @@
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
-import android.widget.TextView;
import java.io.File;
import java.io.FilenameFilter;
@@ -42,8 +41,6 @@ public class QuickRecordingsDialog extends Activity {
private static final String RECORDING_NAME = "QuickRecord ";
private static final String RECORDING_TYPE = ".3gp";
- private int mItems;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -63,37 +60,23 @@ public void onResume() {
}
}
- mItems = recordings.length;
-
ListView list = new ListView(this);
list.setAdapter(new ArrayAdapter (this,
android.R.layout.select_dialog_item, dialogEntries));
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView> arg0, View view, int item, long id) {
- if (recordings[item] != null) {
- Intent intent = new Intent(PLAY_RECORDING);
- intent.putExtra("file", recordings[item].getAbsolutePath());
- QuickRecordingsDialog.this.sendBroadcast(intent);
- }
+ Intent intent = new Intent(PLAY_RECORDING);
+ intent.putExtra("file", recordings[item].getAbsolutePath());
+ QuickRecordingsDialog.this.sendBroadcast(intent);
}
});
list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView> arg0, View view, int item, long id) {
- if (recordings[item] != null) {
- recordings[item].delete();
- TextView textView = (TextView) view;
- textView.setText(R.string.quick_settings_quick_record_deleted);
- Intent intent = new Intent(PLAY_RECORDING);
- QuickRecordingsDialog.this.sendBroadcast(intent);
- recordings[item] = null;
- mItems--;
- if (mItems <= 0) {
- QuickRecordingsDialog.this.finish();
- }
- return true;
- } else {
- return false;
- }
+ recordings[item].delete();
+ Intent intent = new Intent(PLAY_RECORDING);
+ QuickRecordingsDialog.this.sendBroadcast(intent);
+ QuickRecordingsDialog.this.finish();
+ return true;
}
});
AlertDialog.Builder action = new AlertDialog.Builder(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index a5387134a334..a2c963e53aa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -20,6 +20,7 @@
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerNative;
+import android.app.INotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
@@ -34,6 +35,7 @@
import android.content.res.Configuration;
import android.content.ServiceConnection;
import android.database.ContentObserver;
+import android.graphics.drawable.Drawable;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
@@ -82,6 +84,10 @@
import com.android.systemui.SearchPanelView;
import com.android.systemui.SystemUI;
import com.android.systemui.slimrecent.RecentController;
+import com.android.systemui.statusbar.notification.Hover;
+import com.android.systemui.statusbar.notification.HoverCling;
+import com.android.systemui.statusbar.notification.NotificationHelper;
+import com.android.systemui.statusbar.phone.Ticker;
import com.android.systemui.statusbar.phone.KeyguardTouchDelegate;
import com.android.systemui.statusbar.phone.NavigationBarOverlay;
import com.android.systemui.statusbar.policy.NotificationRowLayout;
@@ -122,7 +128,11 @@ public abstract class BaseStatusBar extends SystemUI implements
public static final int EXPANDED_LEAVE_ALONE = -10000;
public static final int EXPANDED_FULL_OPEN = -10001;
+ public static final int HOVER_DISABLED = 0;
+ public static final int HOVER_ENABLED = 1;
+
protected CommandQueue mCommandQueue;
+ protected INotificationManager mNotificationManager;
protected IStatusBarService mBarService;
protected H mHandler = createHandler();
@@ -185,6 +195,15 @@ public interface NavigationBarCallback {
private EdgeGestureManager mEdgeGestureManager;
+ // Notification helper
+ protected NotificationHelper mNotificationHelper;
+
+ // Hover
+ protected Hover mHover;
+ protected int mHoverState;
+ protected ImageView mHoverButton;
+ protected HoverCling mHoverCling;
+
// UI-specific methods
/**
@@ -203,6 +222,16 @@ public interface NavigationBarCallback {
private RecentController mRecents;
+ public INotificationManager getNotificationManager() {
+ return mNotificationManager;
+ }
+
+ public PowerManager getPowerManagerInstance() {
+ if(mPowerManager == null) mPowerManager
+ = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ return mPowerManager;
+ }
+
public IStatusBarService getStatusBarService() {
return mBarService;
}
@@ -211,6 +240,18 @@ public boolean isDeviceProvisioned() {
return mDeviceProvisioned;
}
+ public int getNotificationCount() {
+ return mNotificationData.size();
+ }
+
+ public NotificationData getNotifications() {
+ return mNotificationData;
+ }
+
+ public RemoteViews.OnClickHandler getNotificationClickHandler() {
+ return mOnClickHandler;
+ }
+
private ContentObserver mProvisioningObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
@@ -229,6 +270,12 @@ public boolean onClickHandler(View view, PendingIntent pendingIntent, Intent fil
if (DEBUG) {
Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
}
+
+ // User is clicking a button inside the notification, stop countdowns and
+ // restart override one depending on notification expansion.
+ // We just ignore incoming call case cause is handled differently, as soon as dialer shows, is gone.
+ mHover.processOverridingQueue(mHover.isExpanded());
+
final boolean isActivity = pendingIntent.isActivity();
if (isActivity) {
try {
@@ -278,6 +325,8 @@ public void start() {
mDreamManager = IDreamManager.Stub.asInterface(
ServiceManager.checkService(DreamService.DREAM_SERVICE));
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mProvisioningObserver.onChange(false); // set up
mContext.getContentResolver().registerContentObserver(
@@ -291,6 +340,11 @@ public void start() {
mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
mRecents = new RecentController(mContext, mLayoutDirection);
+ mHover = new Hover(this, mContext);
+ mHoverCling = new HoverCling(mContext);
+ mNotificationHelper = new NotificationHelper(this, mContext);
+
+ mHover.setNotificationHelper(mNotificationHelper);
mStatusBarContainer = new FrameLayout(mContext);
@@ -358,6 +412,22 @@ public void start() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiver(mBroadcastReceiver, filter);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.HOVER_STATE),
+ false, new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateHoverState();
+ }
+ });
+
+ updateHoverState();
+ }
+
+ public Hover getHoverInstance() {
+ if(mHover == null) mHover = new Hover(this, mContext);
+ return mHover;
}
private void initPieController() {
@@ -389,6 +459,17 @@ public void setOverwriteImeIsActive(boolean enabled) {
}
}
+ protected void updateHoverState() {
+ mHoverState = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.HOVER_STATE, HOVER_DISABLED);
+
+ mHoverButton.setImageResource(mHoverState != HOVER_DISABLED
+ ? R.drawable.ic_notify_hover_pressed
+ : R.drawable.ic_notify_hover_normal);
+
+ mHover.setHoverActive(mHoverState == HOVER_ENABLED);
+ }
+
public void userSwitched(int newUserId) {
// should be overridden
}
@@ -860,7 +941,7 @@ public NotificationClicker makeClicker(PendingIntent intent, String pkg, String
return new NotificationClicker(intent, pkg, tag, id);
}
- protected class NotificationClicker implements View.OnClickListener {
+ public class NotificationClicker implements View.OnClickListener {
private PendingIntent mIntent;
private String mPkg;
private String mTag;
@@ -957,6 +1038,11 @@ protected StatusBarNotification removeNotificationViews(IBinder key) {
updateExpansionStates();
updateNotificationIcons();
+
+ // If a notif is on hover list or currently showed in hover,
+ // remove (hide) it if system does.
+ mHover.removeNotification(entry);
+
return entry.notification;
}
@@ -981,8 +1067,8 @@ protected NotificationData.Entry createNotificationViews(IBinder key,
handleNotificationError(key, notification, "Couldn't create icon: " + ic);
return null;
}
- // Construct the expanded view.
NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
+ // Construct the expanded view.
if (!inflateViews(entry, mPile)) {
handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
+ notification);
@@ -1002,13 +1088,24 @@ protected void addNotificationViews(NotificationData.Entry entry) {
}
updateExpansionStates();
updateNotificationIcons();
+
+ if (!mPowerManager.isScreenOn()) {
+ mHover.addStatusBarNotification(entry.notification);
+ } else {
+ // screen on - check if hover is enabled
+ if (mNotificationHelper.isHoverEnabled()) {
+ mHover.setNotification(entry, false);
+ } else {
+ mHover.addStatusBarNotification(entry.notification);
+ }
+ }
}
private void addNotificationViews(IBinder key, StatusBarNotification notification) {
addNotificationViews(createNotificationViews(key, notification));
}
- protected void updateExpansionStates() {
+ public void updateExpansionStates() {
int N = mNotificationData.size();
for (int i = 0; i < N; i++) {
NotificationData.Entry entry = mNotificationData.get(i);
@@ -1030,6 +1127,14 @@ protected void updateExpansionStates() {
}
}
+ public void animateStatusBarOut() {
+ // should be overridden
+ }
+
+ public void animateStatusBarIn() {
+ // should be overridden
+ }
+
protected abstract void haltTicker();
protected abstract void setAreThereNotifications();
protected abstract void updateNotificationIcons();
@@ -1037,6 +1142,7 @@ protected void updateExpansionStates() {
protected abstract void updateExpandedViewPos(int expandedPosition);
protected abstract int getExpandedViewMaxHeight();
protected abstract boolean shouldDisableNavbarGestures();
+ public abstract boolean isExpandedVisible();
protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) {
return parent != null && parent.indexOfChild(entry.row) == 0;
@@ -1094,9 +1200,10 @@ public void updateNotification(IBinder key, StatusBarNotification notification)
&& notification.getScore() == oldNotification.getScore();
// score now encompasses/supersedes isOngoing()
- boolean updateTicker = notification.getNotification().tickerText != null
+ boolean updateTicker = (notification.getNotification().tickerText != null
&& !TextUtils.equals(notification.getNotification().tickerText,
- oldEntry.notification.getNotification().tickerText);
+ oldEntry.notification.getNotification().tickerText)) &&
+ (mHoverState == HOVER_DISABLED);
boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
@@ -1186,6 +1293,18 @@ private void updateNotificationViews(NotificationData.Entry entry,
} else {
entry.content.setOnClickListener(null);
}
+
+ if (!mPowerManager.isScreenOn()) {
+ mHover.addStatusBarNotification(entry.notification);
+ } else {
+ // screen on - check if hover is enabled
+ if (mNotificationHelper.isHoverEnabled()) {
+ mHover.setNotification(entry, true);
+ } else {
+ // We pass this to hover here only if it doesn't show
+ mHover.addStatusBarNotification(entry.notification);
+ }
+ }
}
protected void notifyHeadsUpScreenOn(boolean screenOn) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Hover.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Hover.java
new file mode 100644
index 000000000000..a592828aef74
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Hover.java
@@ -0,0 +1,845 @@
+/*
+ * Copyright (C) 2014 ParanoidAndroid 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.notification;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.widget.SizeAdaptiveLayout;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.NotificationData.Entry;
+
+import java.util.ArrayList;
+
+/**
+ * Hover constructor
+ * Handles creating, processing and displaying hover notifications
+ * Must be initilized and needs a NotificationHelper instance
+ */
+public class Hover {
+
+ public static final boolean DEBUG = false;
+ public static final boolean DEBUG_REPARENT = false;
+
+ private static final String TAG = "Hover";
+ private static final String IN_CALL_UI = "com.android.incallui";
+ private static final String DIALER = "com.android.dialer";
+ private static final String DELIMITER = "|";
+
+ private static final int ANIMATION_DURATION = 350; // 350 ms
+ private static final int INDEX_CURRENT = 0; // first array object
+ private static final int INDEX_NEXT = 1; // second array object
+ private static final int INSTANT_FADE_OUT_DELAY = 0; // 0 seconds
+ private static final int MICRO_FADE_OUT_DELAY = 1250; // 1.25 seconds, enough
+ private static final int LONG_FADE_OUT_DELAY = 5000; // 5 seconds, default show time
+ private static final int SHORT_FADE_OUT_DELAY = 2500; // 2.5 seconds to show next one
+
+ private static final int OVERLAY_NOTIFICATION_OFFSET = 125; // special purpose
+
+ public boolean mHoverActive;
+ public HoverNotification mLastNotification = null; // special purpose
+
+ private boolean mAnimatingVisibility;
+ private boolean mAttached;
+ private boolean mHiding;
+ private boolean mShowing;
+ private boolean mUserLocked;
+ private int mHoverHeight;
+ private BaseStatusBar mStatusBar;
+ private Context mContext;
+ private DecelerateInterpolator mAnimInterpolator;
+ private FrameLayout mNotificationView;
+ private Handler mHandler;
+ private HoverLayout mHoverLayout;
+ private KeyguardManager mKeyguardManager;
+ private LayoutInflater mInflater;
+ private NotificationHelper mNotificationHelper;
+ private PowerManager mPowerManager;
+ private Runnable mHideRunnable;
+ private Runnable mOverrideRunnable;
+ private TelephonyManager mTelephonyManager;
+ private WindowManager mWindowManager;
+
+ private ArrayList mNotificationList;
+ private ArrayList mStatusBarNotifications;
+
+ /**
+ * Creates a new hover instance
+ * @Param context the current Context
+ * @Param statusBar the current BaseStatusBar
+ */
+ public Hover(BaseStatusBar statusBar, Context context) {
+ mContext = context;
+ mStatusBar = statusBar;
+ mPowerManager = mStatusBar.getPowerManagerInstance(); // get power manager instance from status bar
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mHoverLayout = (HoverLayout) mInflater.inflate(R.layout.hover_container, null);
+ mHoverLayout.setHoverContainer(this);
+ mHoverHeight = mContext.getResources().getDimensionPixelSize(R.dimen.hover_height);
+ mNotificationList = new ArrayList();
+ mStatusBarNotifications = new ArrayList();
+
+ // root hover view
+ mNotificationView = (FrameLayout) mHoverLayout.findViewById(R.id.hover_notification);
+
+ mNotificationHelper = null;
+
+ mHandler = new Handler();
+
+ mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ dismissHover(false, false);
+ }
+ };
+
+ mOverrideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ clearHandlerCallbacks();
+ if (hasMultipleNotifications()) {
+ overrideShowingNotification();
+ } else {
+ startMicroHideCountdown();
+ }
+ }
+ };
+
+ // initialize system services
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+ mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ mAnimInterpolator = new DecelerateInterpolator();
+ }
+
+ public static String getContentDescription(StatusBarNotification content) {
+ if (content != null) {
+ String tag = content.getTag() == null ? "null" : content.getTag();
+ return content.getPackageName() + DELIMITER + content.getId() + DELIMITER + tag;
+ }
+ return null;
+ }
+
+ public static String getEntryDescription(Entry entry) {
+ if (entry != null) {
+ return getContentDescription(entry.notification) + DELIMITER + entry.key;
+ }
+ return null;
+ }
+
+ public BaseStatusBar getStatusBar() {
+ return mStatusBar;
+ }
+
+ public IStatusBarService getStatusBarService() {
+ return mStatusBar.getStatusBarService();
+ }
+
+ public void setNotificationHelper(NotificationHelper notificationHelper) {
+ mNotificationHelper = notificationHelper;
+ }
+
+ public void setHoverActive(boolean active) {
+ mHoverActive = active;
+ if (!active) {
+ if (mShowing && !mHiding) {
+ dismissHover(true, true);
+ } else { // clear everything
+ mNotificationView.removeAllViews();
+ if (mAttached) {
+ mWindowManager.removeView(mHoverLayout);
+ mAttached = false;
+ }
+ mShowing = false;
+ mHiding = false;
+ setTouchOutside(false);
+ setAnimatingVisibility(false);
+ clearHandlerCallbacks();
+ clearNotificationList();
+ }
+ }
+ }
+
+ public void setLocked(boolean locked) {
+ mUserLocked = locked;
+ // on locked if we expand we need to update the layout
+ // to include the expanded view, set to match parent one in the locked interval
+ WindowManager.LayoutParams params
+ = (WindowManager.LayoutParams) mHoverLayout.getLayoutParams();
+ params.height = locked
+ ? WindowManager.LayoutParams.MATCH_PARENT
+ : WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowManager.updateViewLayout(mHoverLayout, params);
+ }
+
+ public void setTouchOutside(boolean touch) {
+ mHoverLayout.setTouchOutside(touch);
+ }
+
+ public View getCurrentLayout() {
+ return getCurrentNotification().getLayout();
+ }
+
+ public HoverNotification getCurrentNotification() {
+ HoverNotification notif = getHoverNotification(INDEX_CURRENT);
+ // use last viewed one, something bad happened if we're here
+ // so this is a safe backdoor.
+ if (notif == null) notif = mLastNotification;
+ return notif;
+ }
+
+ private int getCurrentHeight() {
+ int height = getCurrentLayout().getHeight();
+ return height != 0 ? height : mHoverHeight;
+ }
+
+ private int getNewHeight(View layout) {
+ int height = layout.getHeight();
+ return height != 0 ? height : mHoverHeight;
+ }
+
+ private WindowManager.LayoutParams getHoverLayoutParams() {
+ WindowManager.LayoutParams lp = getLayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+ return lp;
+ }
+
+ private WindowManager.LayoutParams getLayoutParams(int width, int height, int gravity) {
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ width, height, 0, 0,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT
+ );
+ lp.gravity = gravity;
+ return lp;
+ }
+
+ public void updateNotificationLayoutParams(HoverNotification notif) {
+ SizeAdaptiveLayout sal = notif.getLayout();
+ Entry entry = notif.getEntry();
+ if (sal != null && entry != null) {
+ int height = mHoverHeight;
+
+ if (sal.getParent() == null) {
+ mNotificationView.addView(sal);
+ }
+
+ FrameLayout.LayoutParams params
+ = new FrameLayout.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ height, Gravity.CENTER_HORIZONTAL);
+ sal.setLayoutParams(params);
+ sal.setVisibility(!mShowing ? View.GONE : View.VISIBLE);
+
+ // reset all the possible modified parameters
+ notif.resetLayoutProperties(mShowing);
+ }
+ }
+
+ public int getNotificationCount() {
+ return mNotificationList.size();
+ }
+
+ public boolean isStatusBarExpanded() {
+ return mStatusBar.isExpandedVisible();
+ }
+
+ public boolean isKeyguardShowing() {
+ return mKeyguardManager.isKeyguardLocked();
+ }
+
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ public boolean isScreenOn() {
+ return mPowerManager.isScreenOn();
+ }
+
+ public boolean isAnimatingOrUserLocked() {
+ return mAnimatingVisibility || mUserLocked;
+ }
+
+ public boolean isAnimatingVisibility() {
+ return mAnimatingVisibility;
+ }
+
+ public void setAnimatingVisibility(boolean animating) {
+ mAnimatingVisibility = animating;
+ }
+
+ public boolean isHiding() {
+ return mHiding;
+ }
+
+ public boolean isExpanded() {
+ return mHoverLayout.getExpanded();
+ }
+
+ public boolean isSimPanelShowing() {
+ return mNotificationHelper.isSimPanelShowing();
+ }
+
+ public boolean isRingingOrConnected() {
+ return mNotificationHelper.isRingingOrConnected();
+ }
+
+ public boolean isCallUiInBackground() {
+ return Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.CALL_UI_IN_BACKGROUND, 0) != 0;
+ }
+
+ public boolean isDialpadShowing() {
+ return false; //Settings.System.getInt(mContext.getContentResolver(),
+ //Settings.System.DIALPAD_STATE, 0) != 0;
+ }
+
+ public boolean isInCallUINotification(Entry entry) {
+ if (entry != null) return entry.notification.getPackageName().equals(IN_CALL_UI)
+ | entry.notification.getPackageName().equals(DIALER);
+ return false;
+ }
+
+ public boolean hasNotifications() {
+ return getNotificationCount() > 0;
+ }
+
+ public boolean hasMultipleNotifications() {
+ return getNotificationCount() > 1;
+ }
+
+ public boolean isClearable() {
+ return getCurrentNotification().getContent().isClearable();
+ }
+
+ public boolean isClickable() {
+ return getCurrentNotification().getLayout().hasOnClickListeners();
+ }
+
+ public void dismissHover(boolean instant, boolean quit) {
+ hideCurrentNotification(instant, quit);
+ }
+
+ public void showCurrentNotification() {
+ final HoverNotification currentNotification = getHoverNotification(INDEX_CURRENT);
+ if (currentNotification != null && !isKeyguardShowing() && !isStatusBarExpanded()
+ && mHoverActive && !mShowing && isScreenOn() && !isSimPanelShowing()) {
+ if (isRingingOrConnected() && isDialpadShowing()) {
+ // incoming call notification has been already processed,
+ // and since we don't want to show other ones, clear and return.
+ clearNotificationList();
+ return;
+ }
+
+ if (mAnimatingVisibility) return;
+
+ mShowing = true;
+ setAnimatingVisibility(true);
+
+ if (!mAttached) {
+ mWindowManager.addView(mHoverLayout, getHoverLayoutParams());
+ mAttached = true;
+ }
+
+ mNotificationHelper.reparentNotificationToHover(currentNotification);
+
+ // don't pull expanded notifications
+ updateNotificationLayoutParams(currentNotification);
+ currentNotification.getEntry().row.setExpanded(false);
+
+ // hide status bar right before showing hover
+ mStatusBar.animateStatusBarOut();
+
+ final View notificationLayout = getCurrentLayout();
+ notificationLayout.setY(-getCurrentHeight());
+ // safely check if statusbar somehow expanded on starting and on ending
+ notificationLayout.animate()
+ .setDuration(ANIMATION_DURATION)
+ .y(0f).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (isStatusBarExpanded() | (isRingingOrConnected() && isDialpadShowing())
+ | isKeyguardShowing() | !isScreenOn() | isSimPanelShowing()) {
+ clearHandlerCallbacks();
+ setAnimatingVisibility(false);
+ dismissHover(true, true);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (isStatusBarExpanded() | (isRingingOrConnected() && isDialpadShowing())
+ | isKeyguardShowing() | !isScreenOn() | isSimPanelShowing()) {
+ clearHandlerCallbacks();
+ setAnimatingVisibility(false);
+ dismissHover(false, true);
+ return;
+ } else {
+ setAnimatingVisibility(false);
+ }
+ mLastNotification = currentNotification; // backup current notification object
+ // check if there are other notif to override with
+ processOverridingQueue(isExpanded());
+ }
+ }).setInterpolator(mAnimInterpolator);
+ } else {
+ // remove any possible stored notifications
+ clearNotificationList();
+ }
+ }
+
+ private void overrideShowingNotification() {
+ final HoverNotification currentNotification = getCurrentNotification();
+ final HoverNotification nextNotification = getHoverNotification(INDEX_NEXT);
+
+ clearHandlerCallbacks();
+
+ // just to be safe if something bad happened that satisfy these cases
+ if (!isScreenOn() | isKeyguardShowing() | isStatusBarExpanded()
+ | isRingingOrConnected() | isDialpadShowing() | isSimPanelShowing()) {
+ dismissHover(true, true);
+ return;
+ }
+
+ mShowing = true;
+ setAnimatingVisibility(true);
+
+ if (currentNotification != null) {
+ // we asume that old notification is the first children on container
+ final View currentLayout = mNotificationView.getChildAt(0);
+
+ /**
+ * TODO:
+ * Animated (with same show duration) reverse expander.
+ * Reset expanding before overriding (bad UI):
+ * we should animate dexpansion with same duration @ANIMATION_DURATION
+ */
+
+ updateNotificationLayoutParams(currentNotification);
+ currentNotification.getEntry().row.setExpanded(false);
+
+ // animate current notification out
+ currentLayout.animate().setDuration(ANIMATION_DURATION).alpha(0f)
+ .rotationX(90f).yBy(OVERLAY_NOTIFICATION_OFFSET)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mNotificationHelper.reparentNotificationToStatusBar(currentNotification);
+ mNotificationView.removeView(currentLayout);
+ removeNotificationFromList(currentNotification);
+ }
+ });
+ } else {
+ // run forrest
+ }
+
+ if (nextNotification == null) {
+ dismissHover(false, true);
+ return;
+ } else {
+ mNotificationHelper.reparentNotificationToHover(nextNotification);
+ }
+
+ // update next notification parameters
+ updateNotificationLayoutParams(nextNotification);
+
+ final View nextLayout = nextNotification.getLayout();
+ nextLayout.setY(-getNewHeight(nextLayout));
+ nextLayout.animate().setDuration(ANIMATION_DURATION)
+ .y(0f).setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLastNotification = nextNotification; // backup current notification object
+ setAnimatingVisibility(false);
+ setTouchOutside(false);
+ // check if there are other notif to override with
+ processOverridingQueue(isExpanded());
+ }
+ }).setInterpolator(mAnimInterpolator);
+ }
+
+ public void hideCurrentNotification(final boolean instant, final boolean quit) {
+ final HoverNotification currentNotification = getCurrentNotification();
+ if (currentNotification != null && mHoverActive && mShowing && !mAnimatingVisibility
+ && !mHiding) {
+ clearHandlerCallbacks();
+ setAnimatingVisibility(true);
+ mHiding = true;
+ if (mUserLocked) setLocked(false); // unlock if locked
+
+ // show statusbar
+ mStatusBar.animateStatusBarIn();
+
+ // animate container to make sure we hide hover
+ mNotificationView.animate().yBy(-mNotificationView.getHeight())
+ .setDuration(instant ? 0 : ANIMATION_DURATION)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mShowing = false;
+ setAnimatingVisibility(false);
+
+ // relocate view's container
+ mNotificationView.setY(
+ mNotificationView.getY() + mNotificationView.getHeight());
+
+ if (!quit) { // else clearNotificationList() takes care
+ // reparent current to status bar and update expansion
+ mNotificationHelper.reparentNotificationToStatusBar(currentNotification);
+ mStatusBar.updateExpansionStates();
+ // remove current hover object from list
+ removeNotificationFromList(currentNotification);
+ }
+
+ // remove all stored views
+ mNotificationView.removeAllViews();
+
+ // remove hover container
+ if (mAttached) {
+ mWindowManager.removeView(mHoverLayout);
+ mAttached = false;
+ }
+
+ mHiding = false;
+ setTouchOutside(false);
+
+ // check if there are other notif to show
+ if (!quit) {
+ processShowingQueue();
+ } else {
+ // clear everything and go home
+ clearHandlerCallbacks();
+ clearNotificationList();
+ mStatusBar.updateExpansionStates();
+ }
+ }
+ }).setInterpolator(mAnimInterpolator);
+ } else {
+ // take a break
+ }
+ }
+
+ // callbacks to handle the queue
+ public void startLongHideCountdown() {
+ startHideCountdown(LONG_FADE_OUT_DELAY);
+ }
+
+ public void startShortHideCountdown() {
+ startHideCountdown(SHORT_FADE_OUT_DELAY);
+ }
+
+ public void startMicroHideCountdown() {
+ startHideCountdown(MICRO_FADE_OUT_DELAY);
+ }
+
+ public void startLongOverrideCountdown() {
+ startOverrideCountdown(LONG_FADE_OUT_DELAY);
+ }
+
+ public void startShortOverrideCountdown() {
+ startOverrideCountdown(SHORT_FADE_OUT_DELAY);
+ }
+
+ public void startHideCountdown(int delay) {
+ mHandler.postDelayed(mHideRunnable, delay);
+ }
+
+ public void startOverrideCountdown(int delay) {
+ mHandler.postDelayed(mOverrideRunnable, delay);
+ }
+
+ public void clearHandlerCallbacks() {
+ mHandler.removeCallbacksAndMessages(null); // wipe everything
+ }
+
+ // notifications processing
+ public void setNotification(Entry entry, boolean update) {
+ // first, check if current notification's package is blacklisted
+ boolean allowed = true; // default on
+ try {
+ final String packageName = entry.notification.getPackageName();
+ allowed = mStatusBar.getNotificationManager().isPackageAllowedForHover(packageName)
+ || mNotificationHelper.isNotificationBlacklisted(packageName);
+ } catch (android.os.RemoteException ex) {
+ // System is dead
+ }
+
+ boolean clearable = entry.notification.isClearable();
+ boolean lowPriority = entry.notification.getNotification().priority < Notification.PRIORITY_LOW;
+ if (!clearable || lowPriority)
+ allowed = false;
+
+ if (!allowed) {
+ addStatusBarNotification(entry.notification);
+ return;
+ }
+
+ // second, if we've just expanded statusbar or turned screen off return
+ if (!isScreenOn() | isStatusBarExpanded() | isKeyguardShowing()) {
+ if (mShowing) {
+ dismissHover(true, true);
+ } else {
+ clearNotificationList();
+ }
+ addStatusBarNotification(entry.notification);
+ return;
+ }
+
+ // third, when ringing or connected and dialpad is showing don't show notifications
+ if (isScreenOn() && isRingingOrConnected()) {
+ // incoming call in background enabled? continue
+ if (isCallUiInBackground() && !isDialpadShowing()) {
+ if (!isInCallUINotification(entry)) {
+ // return we just need filter phone's notifications if we're here
+ addStatusBarNotification(entry.notification);
+ return;
+ }
+ } else {
+ // we show incoming call notifications only if background mode is
+ // enabled and dialpad is not showing, so exit if we're here.
+ addStatusBarNotification(entry.notification);
+ return;
+ }
+
+ }
+
+ // define params
+ SizeAdaptiveLayout sal =
+ (SizeAdaptiveLayout) entry.row.findViewById(R.id.adaptive);
+ HoverNotification notif = null;
+
+ // figure out which type of notification we have and do our boolean checks
+ boolean show = shouldDisplayNotification(entry);
+ boolean isOnList = isNotificationOnList(entry);
+
+ if (!update && show) {
+ notif = new HoverNotification(entry, sal);
+ addNotificationToList(notif);
+ } else {
+ if (!isOnList && show) {
+ notif = new HoverNotification(entry, sal);
+ addNotificationToList(notif);
+ } else if (isOnList && show) {
+ notif = getNotificationForEntry(entry);
+ // if updates are for current notification update click listener
+ HoverNotification current = getCurrentNotification();
+ if (current != null && getEntryDescription(current.getEntry()).equals(getEntryDescription(entry))) {
+ current.setEntry(entry);
+ View child = mNotificationView.getChildAt(0);
+ if (child != null) {
+ child.setTag(getContentDescription(entry.notification));
+ child.setOnClickListener(null); // remove current
+ child.setOnClickListener(mNotificationHelper.getNotificationClickListener(entry, true));
+ }
+ }
+ } else {
+ // uh spam detected...go away!
+ addStatusBarNotification(entry.notification);
+ return;
+ }
+ }
+
+ // here we have already done spam checks and can finally add
+ // it to the status bar array before we check if we need to show it
+ addStatusBarNotification(entry.notification);
+
+ // call showCurrentNotification() only if is not showing,
+ // if not will clear all notifications, that is even safe
+ // but unneeded (@link showCurrentNotification())
+ if (!mShowing) showCurrentNotification();
+ }
+
+ public void processShowingQueue() {
+ if (!mShowing) {
+ if (hasNotifications()) { // proced
+ showCurrentNotification();
+ }
+ } else if (!mHiding) {
+ startLongHideCountdown();
+ }
+ }
+
+ public void processOverridingQueue(boolean expanded) {
+ clearHandlerCallbacks();
+ if (!mShowing) {
+ showCurrentNotification();
+ } else if (hasMultipleNotifications()) { // proced
+ startOverrideCountdown(expanded ? LONG_FADE_OUT_DELAY : SHORT_FADE_OUT_DELAY);
+ } else if (!mHiding) {
+ startLongHideCountdown();
+ }
+ }
+
+ public void removeNotificationView(HoverNotification notif) {
+ for (int i = 0; i < mNotificationView.getChildCount(); i++) {
+ View child = mNotificationView.getChildAt(i);
+ if (notif.toString().equals(child.getTag())) {
+ mNotificationView.removeView(child);
+ }
+ }
+ }
+
+ public void addStatusBarNotification(StatusBarNotification n) {
+ for (int i = 0; i < mStatusBarNotifications.size(); i++) {
+ if (NotificationHelper.getContentDescription(n).equals(
+ NotificationHelper.getContentDescription(mStatusBarNotifications.get(i)))) {
+ mStatusBarNotifications.set(i, n);
+ return;
+ }
+ }
+ mStatusBarNotifications.add(n);
+ }
+
+ public void addNotificationToList(HoverNotification notif) {
+ for (int i = 0; i < mNotificationList.size(); i++) {
+ if (getContentDescription(notif.getContent())
+ .equals(getContentDescription(mNotificationList.get(i).getContent()))) {
+ mNotificationList.set(i, notif);
+ return;
+ }
+ }
+ mNotificationList.add(notif);
+ }
+
+ public void removeStatusBarNotification(StatusBarNotification n) {
+ for (int i = 0; i < mStatusBarNotifications.size(); i++) {
+ if (NotificationHelper.getContentDescription(n).equals(
+ NotificationHelper.getContentDescription(mStatusBarNotifications.get(i)))) {
+ mStatusBarNotifications.remove(i);
+ i--;
+ }
+ }
+ }
+
+ public void removeNotificationFromList(HoverNotification notif) {
+ for (int i = 0; i < mNotificationList.size(); i++) {
+ if (notif.toString().equals(mNotificationList.get(i).toString())) {
+ mNotificationList.remove(i);
+ break;
+ }
+ }
+ }
+
+ public void removeNotification(Entry entry) {
+ HoverNotification notif = getNotificationForEntry(entry);
+
+ if (notif == null) {
+ if (entry != null) removeStatusBarNotification(entry.notification);
+ return; // notification not added to the list
+ }
+
+ HoverNotification current = getCurrentNotification();
+
+ if (mShowing) {
+ if (current != null &&
+ getContentDescription(current.getEntry().notification)
+ .equals(getContentDescription(notif.getEntry().notification))) {
+ // will be removed after animating just insta-hide
+ clearHandlerCallbacks();
+ dismissHover(false, false);
+ } else {
+ // gotta remove from temp stored list
+ if (DEBUG) Log.d(TAG, "Removing notification: {notification: " + notif + "}");
+ removeNotificationView(notif);
+ removeNotificationFromList(notif);
+ }
+ } else if (!mShowing && isNotificationOnList(entry)) {
+ // gotta remove from temp stored list
+ if (DEBUG) Log.d(TAG, "Removing notification: {notification: " + notif + "}");
+ removeNotificationView(notif);
+ removeNotificationFromList(notif);
+ }
+
+ if (entry != null) removeStatusBarNotification(entry.notification);
+ }
+
+ public void clearNotificationList() {
+ reparentAllNotifications();
+ mNotificationList.clear();
+ }
+
+ public void reparentAllNotifications() {
+ // force reparenting all temp stored notifications to status bar
+ for (HoverNotification stored : mNotificationList) {
+ mNotificationHelper.reparentNotificationToStatusBar(stored);
+ }
+ mStatusBar.updateExpansionStates();
+ }
+
+ public boolean isNotificationOnList(Entry entry) {
+ for (HoverNotification notification : mNotificationList) {
+ if (getContentDescription(notification.getEntry().notification)
+ .equals(getContentDescription(entry.notification))) return true;
+ }
+ return false;
+ }
+
+ public boolean isCurrentNotificationOnList() {
+ return isNotificationOnList(getCurrentNotification().getEntry());
+ }
+
+ public boolean shouldDisplayNotification(Entry newEntry) {
+ // compare with pre-current status bar notification(s) stored in a special array
+ for (StatusBarNotification stored : mStatusBarNotifications) {
+ if (mNotificationHelper.getContentDescription(newEntry.notification).equals(
+ mNotificationHelper.getContentDescription(stored))) {
+ return mNotificationHelper.shouldDisplayNotification(stored, newEntry.notification, true);
+ }
+ }
+ return true;
+ }
+
+ public HoverNotification getHoverNotification(int index) {
+ if (getNotificationCount() > 0) {
+ return mNotificationList.get(index);
+ }
+ return null;
+ }
+
+ public HoverNotification getNotificationForEntry(Entry entry) {
+ for (HoverNotification notif : mNotificationList) {
+ if (getEntryDescription(notif.getEntry()).equals(getEntryDescription(entry))) return notif;
+ }
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverCling.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverCling.java
new file mode 100644
index 000000000000..16f342e094bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverCling.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2014 ParanoidAndroid
+ *
+ * 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.notification;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import com.android.systemui.R;
+
+/**
+ * Helper to manage showing/hiding a confirmation prompt when hover is activated
+ * for first time.
+ */
+public class HoverCling {
+
+ private static final String TAG = "HoverConfirmation";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_SHOW_EVERY_TIME = false;
+
+ private final Context mContext;
+ private final H mHandler;
+ private final long mShowDelayMs;
+
+ private ClingWindowView mClingWindow;
+ private WindowManager mWindowManager;
+ private boolean mFirstRun;
+
+ public HoverCling(Context context) {
+ mContext = context;
+ mHandler = new H();
+ mShowDelayMs = getNavBarExitDuration() * 3;
+ mWindowManager = (WindowManager)
+ mContext.getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ private long getNavBarExitDuration() {
+ Animation exit = AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.dock_bottom_exit);
+ return exit != null ? exit.getDuration() : 0;
+ }
+
+ public boolean loadSetting() {
+ if (DEBUG) Slog.d(TAG, "loadSetting()");
+ mFirstRun = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.HOVER_FIRST_TIME, 0,
+ UserHandle.USER_CURRENT) == 0;
+ if (DEBUG) Slog.d(TAG, "Loaded mFirstRun=" + mFirstRun);
+ return mFirstRun;
+ }
+
+ private void saveSetting() {
+ if (DEBUG) Slog.d(TAG, "saveSetting()");
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.HOVER_FIRST_TIME,
+ mFirstRun ? 0 : 1,
+ UserHandle.USER_CURRENT);
+ if (DEBUG) Slog.d(TAG, "Saved firsttime=" + mFirstRun);
+ }
+
+ public void hoverChanged(boolean isHoverActive) {
+ mHandler.removeMessages(H.SHOW);
+ if (isHoverActive) {
+ if (DEBUG_SHOW_EVERY_TIME || mFirstRun) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H.SHOW), mShowDelayMs);
+ }
+ } else {
+ mHandler.sendEmptyMessage(H.HIDE);
+ }
+ }
+
+ public void confirmCurrentPrompt() {
+ mHandler.post(confirmAction());
+ }
+
+ private void handleHide() {
+ if (mClingWindow != null) {
+ if (DEBUG) Slog.d(TAG, "Hiding hover confirmation");
+ mWindowManager.removeView(mClingWindow);
+ mClingWindow = null;
+ mFirstRun = false;
+ saveSetting();
+ }
+ }
+
+ public WindowManager.LayoutParams getClingWindowLayoutParams() {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_TOAST,
+ 0
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ ,
+ PixelFormat.TRANSLUCENT
+ );
+ lp.setTitle("HoverConfirmation");
+ lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications;
+ lp.gravity = Gravity.FILL;
+ return lp;
+ }
+
+ public FrameLayout.LayoutParams getBubbleLayoutParams() {
+ int gravity = Gravity.CENTER_HORIZONTAL;
+ gravity |= Gravity.TOP;
+ return new FrameLayout.LayoutParams(
+ mContext.getResources().getDimensionPixelSize(
+ R.dimen.hover_cling_width),
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ gravity
+ );
+ }
+
+ private void handleShow() {
+ if (DEBUG) Slog.d(TAG, "Showing hover confirmation");
+
+ mClingWindow = new ClingWindowView(mContext, confirmAction());
+
+ // we will be hiding the nav bar, so layout as if it's already hidden
+ mClingWindow.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ );
+
+ // show the confirmation
+ WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+ mWindowManager.addView(mClingWindow, lp);
+ }
+
+ private Runnable confirmAction() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ if (!mFirstRun) {
+ if (DEBUG) Slog.d(TAG, "Exitting hover confirmation");
+ saveSetting();
+ }
+ handleHide();
+ }
+ };
+ }
+
+ private class ClingWindowView extends FrameLayout {
+ private static final int BGCOLOR = 0x80000000;
+ private static final int OFFSET_DP = 48;
+
+ private final Runnable mConfirm;
+ private final ColorDrawable mColor = new ColorDrawable(0);
+ private ValueAnimator mColorAnim;
+ private ViewGroup mClingLayout;
+
+ private Runnable mUpdateLayoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mClingLayout != null && mClingLayout.getParent() != null) {
+ mClingLayout.setLayoutParams(getBubbleLayoutParams());
+ }
+ }
+ };
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+ post(mUpdateLayoutRunnable);
+ }
+ }
+ };
+
+ public ClingWindowView(Context context, Runnable confirm) {
+ super(context);
+ mConfirm = confirm;
+ setClickable(true);
+ setBackground(mColor);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ mWindowManager.getDefaultDisplay().getMetrics(metrics);
+ float density = metrics.density;
+
+ mClingLayout = (ViewGroup)
+ View.inflate(getContext(), R.layout.hover_cling, null);
+
+ final Button ok = (Button) mClingLayout.findViewById(R.id.ok);
+ ok.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mConfirm.run();
+ }
+ });
+ addView(mClingLayout, getBubbleLayoutParams());
+
+ if (ActivityManager.isHighEndGfx()) {
+ final View bubble = mClingLayout.findViewById(R.id.text);
+ bubble.setAlpha(0f);
+ bubble.setTranslationY(-OFFSET_DP * density);
+ bubble.animate()
+ .alpha(1f)
+ .translationY(0)
+ .setDuration(300)
+ .setInterpolator(new DecelerateInterpolator())
+ .start();
+
+ ok.setAlpha(0f);
+ ok.setTranslationY(-OFFSET_DP * density);
+ ok.animate().alpha(1f)
+ .translationY(0)
+ .setDuration(300)
+ .setStartDelay(200)
+ .setInterpolator(new DecelerateInterpolator())
+ .start();
+
+ mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR);
+ mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final int c = (Integer) animation.getAnimatedValue();
+ mColor.setColor(c);
+ }
+ });
+ mColorAnim.setDuration(1000);
+ mColorAnim.start();
+ } else {
+ mColor.setColor(BGCOLOR);
+ }
+
+ mContext.registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent motion) {
+ Slog.v(TAG, "ClingWindowView.onTouchEvent");
+ return true;
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int SHOW = 0;
+ private static final int HIDE = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW:
+ handleShow();
+ break;
+ case HIDE:
+ handleHide();
+ break;
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverLayout.java
new file mode 100644
index 000000000000..9edd723fbeee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverLayout.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2014 ParanoidAndroid 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.notification;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.widget.RelativeLayout;
+
+import com.android.systemui.ExpandHelper;
+import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
+
+/*
+ * Hover container
+ * Handles touch eventing for hover.
+ */
+public class HoverLayout extends RelativeLayout implements ExpandHelper.Callback {
+
+ private ExpandHelper mExpandHelper; /* Y axis (expander) */
+ private SwipeHelper mSwipeHelper; /* X axis (swiper, to dismiss) */
+
+ private Context mContext;
+ private Hover mHover;
+
+ private boolean mTouchOutside = false;
+ private boolean mExpanded = false;
+
+ public HoverLayout(Context context) {
+ super(context, null);
+ }
+
+ /**
+ * Creates the hover container
+ * @Param context the current Context
+ * @Param attrs the current AttributeSet
+ */
+ public HoverLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ int minHeight = mContext.getResources().getDimensionPixelSize(R.dimen.hover_height);
+ int maxHeight = mContext.getResources().getDimensionPixelSize(R.dimen.notification_row_max_height);
+ mExpandHelper = new ExpandHelper(mContext, this, minHeight, maxHeight);
+ float densityScale = mContext.getResources().getDisplayMetrics().density;
+ float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
+ mSwipeHelper = new SwipeHelper(SwipeHelper.X, new SwipeHelperCallbackX(), densityScale, pagingTouchSlop);
+ }
+
+ public void setHoverContainer(Hover hover) {
+ mHover = hover;
+ }
+
+ public boolean getTouchOutside() {
+ return mTouchOutside;
+ }
+
+ public void setTouchOutside(boolean touch) {
+ mTouchOutside = touch;
+ }
+
+ public boolean getExpanded() {
+ return mExpanded;
+ }
+
+ public void setExpanded(boolean expanded) {
+ mExpanded = expanded;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ boolean intercept = super.onInterceptTouchEvent(event);
+
+ if (!mHover.isAnimatingVisibility() && !mHover.isHiding()) {
+ intercept |= (mExpandHelper.onInterceptTouchEvent(event) |
+ mSwipeHelper.onInterceptTouchEvent(event));
+ }
+
+ return intercept;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ boolean touch = super.onTouchEvent(event);
+
+ int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_OUTSIDE:
+ if (!mTouchOutside) {
+ mHover.clearHandlerCallbacks();
+ if (mExpanded) {
+ // 5 seconds if is expanded, user has time to pause gaming
+ mHover.startLongHideCountdown();
+ } else {
+ // 1.25 if not expanded, user ignores it
+ mHover.startMicroHideCountdown();
+ }
+ mTouchOutside = true;
+ }
+
+ return touch;
+ }
+
+ if (!mHover.isAnimatingVisibility() && !mHover.isHiding()) {
+ touch |= (mExpandHelper.onTouchEvent(event) |
+ mSwipeHelper.onTouchEvent(event));
+ }
+
+ return touch;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ return super.dispatchTouchEvent(event);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ float densityScale = getContext().getResources().getDisplayMetrics().density;
+ mSwipeHelper.setDensityScale(densityScale);
+ float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
+ mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
+ }
+
+ // ExpandHelper.Callback methods
+
+ @Override
+ public View getChildAtRawPosition(float x, float y) {
+ return getChildAtPosition(x, y);
+ }
+
+ @Override
+ public View getChildAtPosition(float x, float y) {
+ return mHover.getCurrentLayout();
+ }
+
+ @Override
+ public boolean canChildBeExpanded(View v) {
+ return mHover.getCurrentNotification().getEntry().row.isExpandable();
+ }
+
+ @Override
+ public void setUserExpandedChild(View v, boolean userExpanded) {
+ mExpanded = userExpanded;
+ }
+
+ @Override
+ public void setUserLockedChild(View v, boolean userLocked) {
+ if (userLocked) { // lock it and clear countdowns
+ mHover.setLocked(userLocked);
+ mHover.clearHandlerCallbacks();
+ } else { // unlock and process next notification
+ mTouchOutside = false; // restart
+ mHover.setLocked(userLocked);
+ mHover.clearHandlerCallbacks();
+ mHover.processOverridingQueue(mExpanded);
+ }
+ }
+
+ // SwipeHelper.Callback methods
+
+ private class SwipeHelperCallbackX implements SwipeHelper.Callback {
+ @Override
+ public View getChildAtPosition(MotionEvent ev) {
+ return getChildContentView(null);
+ }
+
+ @Override
+ public View getChildContentView(View v) {
+ return mHover.getCurrentLayout();
+ }
+
+ @Override
+ public boolean canChildBeDismissed(View v) {
+ return mHover.isClearable();
+ }
+
+ @Override
+ public void onChildDismissed(View v) {
+ mHover.clearHandlerCallbacks();
+ mHover.setAnimatingVisibility(false);
+ mHover.setLocked(false);
+
+ // better to store the current notification from Hover class in another object
+ // so it can't be null if something happens when we get it from the array
+ StatusBarNotification n = mHover.getCurrentNotification().getContent();
+ if (n.isClearable()) { // remove only removable on dismiss
+ final String pkg = n.getPackageName();
+ final String tag = n.getTag();
+ final int id = n.getId();
+ try {
+ mHover.getStatusBarService().onNotificationClear(pkg, tag, id);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ }
+ // quickly remove layout
+ mHover.dismissHover(false, false);
+ }
+
+ @Override
+ public void onBeginDrag(View v) {
+ mHover.setLocked(true);
+ mHover.clearHandlerCallbacks();
+ }
+
+ @Override
+ public void onDragCancelled(View v) {
+ mHover.setLocked(false);
+ mTouchOutside = false; // reset
+ mHover.clearHandlerCallbacks();
+ mHover.processOverridingQueue(mExpanded);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverNotification.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverNotification.java
new file mode 100644
index 000000000000..9278a7a58958
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HoverNotification.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 ParanoidAndroid 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.notification;
+
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.internal.widget.SizeAdaptiveLayout;
+import com.android.systemui.statusbar.LatestItemView;
+import com.android.systemui.statusbar.NotificationData.Entry;
+
+/**
+ * Hover notification builder
+ * Creates hover objects and handles view reparenting
+ */
+public class HoverNotification {
+
+ private static final String TAG = "HoverNotification";
+
+ private Entry entry;
+ private ViewGroup parent;
+ private SizeAdaptiveLayout layout;
+ private StatusBarNotification content;
+
+ /**
+ * Builds a new hoverNotification
+ * @Param entry the current notification Entry
+ * @Param layout the current SizeAdaptiveLayout
+ */
+ public HoverNotification(Entry entry, SizeAdaptiveLayout layout) {
+ this.entry = entry;
+ this.content = entry.notification;
+ this.layout = layout;
+
+ parent = (ViewGroup) layout.getParent();
+ }
+
+ public SizeAdaptiveLayout getLayout() {
+ return layout;
+ }
+
+ public StatusBarNotification getContent() {
+ return content;
+ }
+
+ public void setContent(StatusBarNotification content) {
+ this.content = content;
+ }
+
+ public Entry getEntry() {
+ return entry;
+ }
+
+ public void setEntry(Entry entry) {
+ this.entry = entry;
+ }
+
+ public void reparentToHover() {
+ if (parent != null && parent instanceof LatestItemView) {
+ if (Hover.DEBUG_REPARENT) Log.d(TAG, "reparenting to hover. old parent: " + parent);
+ parent.removeView(layout);
+ }
+ }
+
+ public void reparentToStatusBar() {
+ ViewGroup currParent = (ViewGroup) layout.getParent();
+ if (!(currParent instanceof LatestItemView)) {
+ if (Hover.DEBUG_REPARENT) Log.d(TAG, "reparenting to statusbar."
+ + " old parent: " + currParent + " new parent: " + parent);
+ if (currParent != null) currParent.removeView(layout);
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
+ parent.addView(layout, params);
+ resetLayoutProperties(true);
+ }
+ }
+
+ public void resetLayoutProperties(boolean visible) {
+ layout.setVisibility(visible ? View.VISIBLE : View.GONE);
+ layout.setEnabled(true);
+ layout.setRotationX(0);
+ layout.setAlpha(1f);
+ layout.setX(0f);
+ layout.setY(0f);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHelper.java
new file mode 100644
index 000000000000..dec0f8418730
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHelper.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2014 ParanoidAndroid 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.notification;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.LightingColorFilter;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.DragShadowBuilder;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.widget.SizeAdaptiveLayout;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.BaseStatusBar.NotificationClicker;
+import com.android.systemui.statusbar.NotificationData.Entry;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Helper class
+ * Provides some helper methods
+ * Works as a bridge between Hover, Peek and their surrounding layers
+ */
+public class NotificationHelper {
+
+ public static final String DELIMITER = "|";
+ public static final int HOVER_ALPHA = 175;
+ public static final int DEFAULT_ALPHA = 255;
+
+ private static final String FONT_FAMILY_DEFAULT = "sans-serif";
+ private static final String FONT_FAMILY_LIGHT = "sans-serif-light";
+ private static final String FONT_FAMILY_CONDENSED = "sans-serif-condensed";
+ private static final double DISTANCE_THRESHOLD = 20.0;
+ private static final int HOVER_STYLE = 0;
+ private static final int DEFAULT_STYLE = 1;
+
+ private BaseStatusBar mStatusBar;
+ private Context mContext;
+ private Hover mHover;
+ private TelephonyManager mTelephonyManager;
+ private ActivityManager mActivityManager;
+
+ public boolean mRingingOrConnected = false;
+
+ /**
+ * Creates a new instance
+ * @Param context the current Context
+ * @Param statusBar the current BaseStatusBar
+ */
+ public NotificationHelper(BaseStatusBar statusBar, Context context) {
+ mContext = context;
+ mStatusBar = statusBar;
+ mHover = mStatusBar.getHoverInstance();
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+ mTelephonyManager.listen(new CallStateListener(), PhoneStateListener.LISTEN_CALL_STATE);
+
+ // we need to know which is the foreground app
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ }
+
+ public String getForegroundPackageName() {
+ List taskInfo = mActivityManager.getRunningTasks(1);
+ ComponentName componentInfo = taskInfo.get(0).topActivity;
+ return componentInfo.getPackageName();
+ }
+
+ public Hover getHover() {
+ return mHover;
+ }
+
+ public boolean isHoverEnabled() {
+ return mHover.mHoverActive;
+ }
+
+ public boolean isHoverShowing() {
+ return mHover.isShowing();
+ }
+
+ /**
+ * Hint: = XYZ feature(s) make use of the following
+ * -------------------------------------------------------------
+ *
+ *
+ * Views handling methods
+ */
+ public void reparentNotificationToHover(HoverNotification hoverNotification) {
+ Entry entry = hoverNotification.getEntry();
+
+ SizeAdaptiveLayout sal = hoverNotification.getLayout();
+ sal.setTag(mHover.getContentDescription(entry.notification));
+ sal.setOnClickListener(getNotificationClickListener(entry, true));
+ sal.setVisibility(View.GONE);
+ sal.setEnabled(false);
+
+ applyStyle(sal, HOVER_STYLE);
+
+ // Move to hover
+ hoverNotification.reparentToHover();
+ }
+
+ public void reparentNotificationToStatusBar(HoverNotification hoverNotification) {
+ Entry entry = hoverNotification.getEntry();
+
+ // We move click listener from LatestItemView to SizeAdaptiveLayout.
+ // This is safe to do as content has only one children.
+ entry.content.setOnClickListener(null);
+ SizeAdaptiveLayout sal = hoverNotification.getLayout();
+ sal.setOnClickListener(getNotificationClickListener(entry, false));
+
+ applyStyle(sal, DEFAULT_STYLE);
+
+ // Move back to status bar
+ hoverNotification.reparentToStatusBar();
+ }
+
+ public NotificationClicker getNotificationClickListener(Entry entry, boolean floating) {
+ NotificationClicker intent = null;
+ final PendingIntent contentIntent = entry.notification.getNotification().contentIntent;
+ if (contentIntent != null) {
+ intent = mHover.getStatusBar().makeClicker(contentIntent,
+ entry.notification.getPackageName(), entry.notification.getTag(),
+ entry.notification.getId());
+ boolean makeFloating = floating
+ && !isNotificationBlacklisted(entry.notification.getPackageName())
+ // if the notification is from the foreground app, don't open in floating mode
+ && !entry.notification.getPackageName().equals(getForegroundPackageName());
+ }
+ return intent;
+ }
+
+ public void applyStyle(SizeAdaptiveLayout layout, int style) {
+ boolean forHover = (style == HOVER_STYLE);
+ ViewGroup notificationView = ((ViewGroup) layout);
+ if (notificationView.getBackground() == null) {
+ // if the notification has no background, use default notification background
+ notificationView.setBackgroundResource(com.android.internal.R.drawable.notification_bg);
+ }
+ setViewBackgroundAlpha(notificationView, forHover ? HOVER_ALPHA : DEFAULT_ALPHA);
+
+ List subViews = getAllViewsInLayout(notificationView);
+ for(int i = 0; i < subViews.size(); i++) {
+ View v = subViews.get(i);
+ if (v instanceof ViewGroup) { // apply hover alpha to the view group
+ setViewBackgroundAlpha(v, forHover ? HOVER_ALPHA : DEFAULT_ALPHA);
+ } else if (v instanceof ImageView) { // remove image background
+ setViewBackgroundAlpha(v, forHover ? HOVER_ALPHA : DEFAULT_ALPHA);
+ } else if (v instanceof TextView) { // set font family
+ boolean title = v.getId() == android.R.id.title;
+ boolean bold = title;
+ TextView text = ((TextView) v);
+ text.setTypeface(Typeface.create(
+ forHover ? FONT_FAMILY_CONDENSED :
+ (title ? FONT_FAMILY_LIGHT : FONT_FAMILY_DEFAULT),
+ bold ? Typeface.BOLD : Typeface.NORMAL));
+ }
+ }
+ }
+
+ public void setViewBackgroundAlpha(View v, int alpha) {
+ Drawable bg = v.getBackground();
+ if(bg != null) bg.setAlpha(alpha);
+ }
+
+ private boolean hasButtons(ViewGroup layout) {
+ List views = getAllViewsInLayout(layout);
+ for(View v : views) {
+ if (v instanceof Button) return true;
+ }
+ return false;
+ }
+
+ private List getAllViewsInLayout(ViewGroup layout) {
+ List viewList = new ArrayList();
+ for(int i = 0; i < layout.getChildCount(); i++) {
+ View v = layout.getChildAt(i);
+ if (v instanceof ViewGroup) {
+ viewList.addAll(getAllViewsInLayout((ViewGroup) v));
+ }
+ viewList.add(v);
+ }
+ return viewList;
+ }
+
+ /**
+ *
+ * Main check to verify we need to show this notification or process it
+ */
+ public static boolean shouldDisplayNotification(
+ StatusBarNotification oldNotif, StatusBarNotification newNotif, boolean when) {
+ // First check for ticker text, if they are different, some other parameters will be
+ // checked to determine if we should show the notification.
+ CharSequence oldTickerText = oldNotif.getNotification().tickerText;
+ CharSequence newTickerText = newNotif.getNotification().tickerText;
+
+ if (newTickerText == null ? oldTickerText == null : newTickerText.equals(oldTickerText)) {
+ // If old notification title isn't null, show notification if
+ // new notification title is different. If it is null, show notification
+ // if the new one isn't.
+ String oldNotificationText = getNotificationTitle(oldNotif);
+ String newNotificationText = getNotificationTitle(newNotif);
+ if(newNotificationText == null ? oldNotificationText != null :
+ !newNotificationText.equals(oldNotificationText)) return true;
+
+ // Last chance, check when the notifications were posted. If times
+ // are equal, we shouldn't display the new notification. (Should apply to peek only)
+ if(when && (oldNotif.getNotification().when != newNotif.getNotification().when)) return true;
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ *
+ * Helpers methods
+ */
+ public static String getNotificationTitle(StatusBarNotification n) {
+ String text = null;
+ if (n != null) {
+ Notification notification = n.getNotification();
+ Bundle extras = notification.extras;
+ text = extras.getString(Notification.EXTRA_TITLE);
+ }
+ return text;
+ }
+
+ public static String getContentDescription(StatusBarNotification content) {
+ if (content != null) {
+ String tag = content.getTag() == null ? "null" : content.getTag();
+ return content.getPackageName() + DELIMITER + content.getId() + DELIMITER + tag;
+ }
+ return null;
+ }
+
+ public boolean isNotificationBlacklisted(String packageName) {
+ String[] blackList = mContext.getResources()
+ .getStringArray(R.array.hover_blacklisted_packages);
+ for(String s : blackList) {
+ if (s.equals(packageName)) return true;
+ }
+ return false;
+ }
+
+ /**
+ *
+ * Touch brigde
+ */
+ public static View.OnTouchListener getHighlightTouchListener(final int color) {
+ return new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ Drawable drawable = ((ImageView) view).getDrawable();
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ LightingColorFilter lighten
+ = new LightingColorFilter(color, color);
+ drawable.setColorFilter(lighten);
+ break;
+ case MotionEvent.ACTION_UP:
+ drawable.clearColorFilter();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ Rect rect = new Rect();
+ view.getLocalVisibleRect(rect);
+ if (!rect.contains((int) event.getX(), (int) event.getY())) {
+ drawable.clearColorFilter();
+ }
+ break;
+ case MotionEvent.ACTION_OUTSIDE:
+ case MotionEvent.ACTION_CANCEL:
+ drawable.clearColorFilter();
+ break;
+ }
+ return false;
+ }
+ };
+ }
+
+ /**
+ *
+ * Call state listener
+ * Telephony states booleans
+ */
+ private class CallStateListener extends PhoneStateListener {
+ @Override
+ public void onCallStateChanged(int state, String incomingNumber) {
+ switch (state) {
+ case TelephonyManager.CALL_STATE_RINGING:
+ case TelephonyManager.CALL_STATE_OFFHOOK:
+ mRingingOrConnected = true;
+ break;
+ case TelephonyManager.CALL_STATE_IDLE:
+ mRingingOrConnected = false;
+ break;
+ }
+ }
+ }
+
+ public boolean isRingingOrConnected() {
+ return mRingingOrConnected;
+ }
+
+ public boolean isSimPanelShowing() {
+ int state = mTelephonyManager.getSimState();
+ return state == TelephonyManager.SIM_STATE_PIN_REQUIRED
+ | state == TelephonyManager.SIM_STATE_PUK_REQUIRED
+ | state == TelephonyManager.SIM_STATE_NETWORK_LOCKED;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 830d791c526b..1178c48f9d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -903,6 +903,9 @@ public void onChange(boolean selfChange) {
if (mSettingsButton != null && mHasFlipSettings) {
mSettingsButton.setVisibility(userSetup ? View.VISIBLE : View.INVISIBLE);
}
+ if (mHoverButton != null && mHasFlipSettings) {
+ mHoverButton.setVisibility(userSetup ? View.VISIBLE : View.INVISIBLE);
+ }
if (mSettingsPanel != null) {
mSettingsPanel.setEnabled(userSetup);
}
@@ -1020,6 +1023,9 @@ public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (mExpandedVisible) {
animateCollapsePanels();
+ } else {
+ // ensure to dismiss hover if is about to show while we touch to expand
+ mHover.dismissHover(true, true);
}
}
return mStatusBarWindow.onTouchEvent(event);
@@ -1136,7 +1142,7 @@ public boolean onTouch(View v, MotionEvent event) {
mClearButton = mStatusBarWindow.findViewById(R.id.clear_all_button);
mClearButton.setOnClickListener(mClearButtonListener);
mClearButton.setAlpha(0f);
- mClearButton.setVisibility(View.INVISIBLE);
+ mClearButton.setVisibility(View.GONE);
mClearButton.setEnabled(false);
mDateView = (DateView)mStatusBarWindow.findViewById(R.id.date);
@@ -1189,6 +1195,13 @@ public boolean onTouch(View v, MotionEvent event) {
mSettingsButton.setImageResource(R.drawable.ic_notify_settings);
}
}
+
+ mHoverButton = (ImageView) mStatusBarWindow.findViewById(R.id.hover_button);
+ if (mHoverButton != null) {
+ mHoverButton.setOnClickListener(mHoverButtonListener);
+ mHoverButton.setVisibility(View.VISIBLE);
+ }
+
if (mHasFlipSettings) {
mNotificationButton = (ImageView) mStatusBarWindow.findViewById(R.id.notification_button);
if (mNotificationButton != null) {
@@ -1371,6 +1384,13 @@ public boolean onLongClick(View v) {
mQS.setService(this);
mQS.setBar(mStatusBarView);
mQS.setupQuickSettings();
+
+ if (mHoverButton != null) {
+ mHoverButton.setImageDrawable(null);
+ mHoverButton.setImageResource(mHoverState != HOVER_DISABLED
+ ? R.drawable.ic_notify_hover_pressed
+ : R.drawable.ic_notify_hover_normal);
+ }
} else {
mQS = null; // fly away, be free
}
@@ -1823,6 +1843,12 @@ protected void refreshLayout(int layoutDirection) {
((ImageView)mClearButton).setImageResource(R.drawable.ic_notify_clear);
}
+ if (mHoverButton != null) {
+ // Force asset reloading
+ mHoverButton.setImageDrawable(null);
+ mHoverButton.setImageResource(R.drawable.ic_notify_hover_normal);
+ }
+
if (mSettingsButton != null) {
// Force asset reloading
mSettingsButton.setImageDrawable(null);
@@ -1879,6 +1905,10 @@ private void loadNotificationShade() {
}
}
+ if (mHoverButton != null) {
+ mHoverButton.setEnabled(isDeviceProvisioned());
+ }
+
if (mSettingsButton != null) {
mSettingsButton.setEnabled(isDeviceProvisioned());
}
@@ -2065,7 +2095,7 @@ protected void setAreThereNotifications() {
&& mFlipSettingsView.getVisibility() == View.VISIBLE
&& mScrollView.getVisibility() != View.VISIBLE) {
// the flip settings panel is unequivocally showing; we should not be shown
- mClearButton.setVisibility(View.INVISIBLE);
+ mClearButton.setVisibility(View.GONE);
} else if (mClearButton.isShown()) {
if (clearable != (mClearButton.getAlpha() == 1.0f)) {
ObjectAnimator clearAnimation = ObjectAnimator.ofFloat(
@@ -2074,7 +2104,7 @@ protected void setAreThereNotifications() {
@Override
public void onAnimationEnd(Animator animation) {
if (mClearButton.getAlpha() <= 0.0f) {
- mClearButton.setVisibility(View.INVISIBLE);
+ mClearButton.setVisibility(View.GONE);
}
}
@@ -2089,7 +2119,7 @@ public void onAnimationStart(Animator animation) {
}
} else {
mClearButton.setAlpha(clearable ? 1.0f : 0.0f);
- mClearButton.setVisibility(clearable ? View.VISIBLE : View.INVISIBLE);
+ mClearButton.setVisibility(clearable ? View.VISIBLE : View.GONE);
}
mClearButton.setEnabled(clearable);
@@ -2414,6 +2444,11 @@ boolean panelsEnabled() {
return (mDisabled & StatusBarManager.DISABLE_EXPAND) == 0;
}
+ @Override
+ public boolean isExpandedVisible() {
+ return mExpandedVisible;
+ }
+
void makeExpandedVisible() {
if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
if (mExpandedVisible || !panelsEnabled()) {
@@ -2477,6 +2512,7 @@ public void animateCollapsePanels(int flags) {
mStatusBarWindow.cancelExpandHelper();
mStatusBarView.collapseAllPanels(true);
+ if(mHover.isShowing() && !mHover.isHiding()) mHover.dismissHover(false, false);
}
public ViewPropertyAnimator setVisibilityWhenDone(
@@ -2524,7 +2560,7 @@ public Animator start(Animator a) {
final int FLIP_DURATION = (FLIP_DURATION_IN + FLIP_DURATION_OUT);
Animator mScrollViewAnim, mFlipSettingsViewAnim, mNotificationButtonAnim,
- mSettingsButtonAnim, mClearButtonAnim;
+ mSettingsButtonAnim, mClearButtonAnim, mHoverButtonAnim;
@Override
public void animateExpandNotificationsPanel() {
@@ -2550,6 +2586,7 @@ public void flipToNotifications() {
if (mScrollViewAnim != null) mScrollViewAnim.cancel();
if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel();
if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel();
+ if (mHoverButtonAnim != null) mHoverButtonAnim.cancel();
if (mClearButtonAnim != null) mClearButtonAnim.cancel();
final boolean halfWayDone = mScrollView.getVisibility() == View.VISIBLE;
@@ -2583,6 +2620,10 @@ public void flipToNotifications() {
mSettingsButtonAnim = start(
ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 1f)
.setDuration(FLIP_DURATION));
+ mHoverButton.setVisibility(View.VISIBLE);
+ mHoverButtonAnim = start(
+ ObjectAnimator.ofFloat(mHoverButton, View.ALPHA, 1f)
+ .setDuration(FLIP_DURATION));
mClearButton.setVisibility(View.VISIBLE);
mClearButton.setAlpha(0f);
setAreThereNotifications(); // this will show/hide the button as necessary
@@ -2632,6 +2673,7 @@ public void switchToSettings() {
mFlipSettingsView.setScaleX(1f);
mFlipSettingsView.setVisibility(View.VISIBLE);
mSettingsButton.setVisibility(View.GONE);
+ mHoverButton.setVisibility(View.GONE);
mScrollView.setVisibility(View.GONE);
mScrollView.setScaleX(0f);
mNotificationButton.setVisibility(View.VISIBLE);
@@ -2693,6 +2735,7 @@ public void flipToSettings() {
if (mScrollViewAnim != null) mScrollViewAnim.cancel();
if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel();
if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel();
+ if (mHoverButtonAnim != null) mHoverButtonAnim.cancel();
if (mClearButtonAnim != null) mClearButtonAnim.cancel();
final boolean halfWayDone = mFlipSettingsView.getVisibility() == View.VISIBLE;
@@ -2723,6 +2766,11 @@ public void flipToSettings() {
ObjectAnimator.ofFloat(mSettingsButton, View.ALPHA, 0f)
.setDuration(FLIP_DURATION),
mScrollView, View.INVISIBLE));
+ mHoverButtonAnim = start(
+ setVisibilityWhenDone(
+ ObjectAnimator.ofFloat(mHoverButton, View.ALPHA, 0f)
+ .setDuration(FLIP_DURATION),
+ mScrollView, View.INVISIBLE));
mNotificationButton.setVisibility(View.VISIBLE);
mNotificationButtonAnim = start(
ObjectAnimator.ofFloat(mNotificationButton, View.ALPHA, 1f)
@@ -2759,7 +2807,6 @@ void makeExpandedInvisibleSoon() {
void makeExpandedInvisible() {
if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
+ " mExpandedVisible=" + mExpandedVisible);
-
if (!mExpandedVisible) {
return;
}
@@ -2773,12 +2820,15 @@ void makeExpandedInvisible() {
if (mScrollViewAnim != null) mScrollViewAnim.cancel();
if (mSettingsButtonAnim != null) mSettingsButtonAnim.cancel();
if (mNotificationButtonAnim != null) mNotificationButtonAnim.cancel();
+ if (mHoverButtonAnim != null) mHoverButtonAnim.cancel();
if (mClearButtonAnim != null) mClearButtonAnim.cancel();
mScrollView.setScaleX(1f);
mScrollView.setVisibility(View.VISIBLE);
mSettingsButton.setAlpha(1f);
mSettingsButton.setVisibility(View.VISIBLE);
+ mHoverButton.setAlpha(1f);
+ mHoverButton.setVisibility(View.VISIBLE);
mNotificationPanel.setVisibility(View.GONE);
mFlipSettingsView.setVisibility(View.GONE);
mNotificationButton.setVisibility(View.GONE);
@@ -3329,19 +3379,42 @@ protected void tick(IBinder key, StatusBarNotification n, boolean firstTime) {
&& mStatusBarWindow.getWindowToken() != null) {
if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
| StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
- mTicker.addEntry(n);
+ boolean blacklisted = false;
+ // don't pass notifications that run in Hover to Ticker
+ if (mHoverState == HOVER_ENABLED) {
+ try {
+ blacklisted = getNotificationManager().isPackageAllowedForHover(n.getPackageName());
+ } catch (android.os.RemoteException ex) {
+ // System is dead
+ }
+ }
+ if (!blacklisted) mTicker.addEntry(n);
}
}
}
+ @Override
+ public void animateStatusBarOut() {
+ mStatusBarView.setVisibility(View.GONE);
+ mStatusBarView.startAnimation(loadAnim(com.android.internal.R.anim.push_up_out, null));
+ }
+
+ @Override
+ public void animateStatusBarIn() {
+ mStatusBarView.setVisibility(View.VISIBLE);
+ mStatusBarView.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
+ }
+
private class MyTicker extends Ticker {
+ private boolean hasTicked = false;
+
MyTicker(Context context, View sb) {
super(context, sb);
}
@Override
public void tickerStarting() {
- mTicking = true;
+ if (mHoverState == HOVER_DISABLED) mTicking = true;
mStatusBarContents.setVisibility(View.GONE);
mCenterClockLayout.setVisibility(View.GONE);
mTickerView.setVisibility(View.VISIBLE);
@@ -3350,10 +3423,12 @@ public void tickerStarting() {
mCenterClockLayout.startAnimation(
loadAnim(com.android.internal.R.anim.push_up_out,
null));
+ hasTicked = true;
}
@Override
public void tickerDone() {
+ if (!hasTicked) return;
mStatusBarContents.setVisibility(View.VISIBLE);
mCenterClockLayout.setVisibility(View.VISIBLE);
mTickerView.setVisibility(View.GONE);
@@ -3363,6 +3438,7 @@ public void tickerDone() {
mCenterClockLayout.startAnimation(
loadAnim(com.android.internal.R.anim.push_down_in,
null));
+ hasTicked = false;
}
public void tickerHalting() {
@@ -3733,6 +3809,23 @@ public boolean onLongClick(View v) {
}
};
+ private View.OnClickListener mHoverButtonListener = new View.OnClickListener() {
+ public void onClick(View v) {
+ if (mHoverCling != null) {
+ boolean firstRun = mHoverCling.loadSetting();
+ // we're pushing the button, so use inverse logic
+ mHoverCling.hoverChanged(mHoverState == HOVER_DISABLED);
+ if (firstRun) {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ }
+ }
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.HOVER_STATE,
+ mHoverState != HOVER_DISABLED ? HOVER_DISABLED : HOVER_ENABLED);
+ updateHoverState();
+ }
+ };
+
private View.OnClickListener mClockClickListener = new View.OnClickListener() {
public void onClick(View v) {
startActivityDismissingKeyguard(
@@ -3810,6 +3903,8 @@ public void onReceive(Context context, Intent intent) {
notifyHeadsUpScreenOn(false);
unregisterShakeListener();
finishBarAnimations();
+ // detach hover when screen is turned off
+ if (mHover.isShowing()) mHover.dismissHover(false, true);
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
mScreenOn = true;
// work around problem where mDisplay.getRotation() is not stable while screen is off (bug 7086018)
@@ -3983,6 +4078,8 @@ private void recreateStatusBar(boolean recreateNavigationBar) {
iconSlots.add(iconView.getStatusBarSlot());
}
+ removeAllViews(mStatusBarWindow);
+
// extract notifications.
int nNotifs = mNotificationData.size();
ArrayList> notifications =
@@ -4023,6 +4120,17 @@ private void recreateStatusBar(boolean recreateNavigationBar) {
mRecreating = false;
}
+ private void removeAllViews(ViewGroup parent) {
+ int N = parent.getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = parent.getChildAt(i);
+ if (child instanceof ViewGroup) {
+ removeAllViews((ViewGroup) child);
+ }
+ }
+ parent.removeAllViews();
+ }
+
/**
* Reload some of our resources when the configuration changes.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryBar.java
new file mode 100644
index 000000000000..bedb0f7646ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryBar.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2012 Slimroms & CyanogenMod
+ *
+ * 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 android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.graphics.drawable.Animatable;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+public class BatteryBar extends RelativeLayout implements Animatable {
+
+ private static final String TAG = BatteryBar.class.getSimpleName();
+
+ // Total animation duration
+ private static final int ANIM_DURATION = 1000; // 5 seconds
+
+ private boolean mAttached = false;
+ private int mBatteryLevel = 0;
+ private int mChargingLevel = -1;
+ private boolean mBatteryCharging = false;
+ private boolean shouldAnimateCharging = true;
+ private boolean isAnimating = false;
+
+ private SettingsObserver mSettingsObserver;
+
+ LinearLayout mBatteryBarLayout;
+ View mBatteryBar;
+
+ LinearLayout mChargerLayout;
+ View mCharger;
+
+ public static final int STYLE_REGULAR = 0;
+ public static final int STYLE_SYMMETRIC = 1;
+
+ boolean vertical = false;
+
+ class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.STATUSBAR_BATTERY_BAR_COLOR),
+ false, this);
+ resolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.STATUSBAR_BATTERY_BAR_ANIMATE),
+ false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ updateSettings();
+ }
+ }
+
+ public BatteryBar(Context context) {
+ this(context, null);
+ }
+
+ public BatteryBar(Context context, boolean isCharging, int currentCharge) {
+ this(context, null);
+ mBatteryLevel = currentCharge;
+ mBatteryCharging = isCharging;
+ }
+
+ public BatteryBar(Context context, boolean isCharging, int currentCharge, boolean isVertical) {
+ this(context, null);
+ mBatteryLevel = currentCharge;
+ mBatteryCharging = isCharging;
+ vertical = isVertical;
+ }
+
+ public BatteryBar(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BatteryBar(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (!mAttached) {
+ mAttached = true;
+
+ mBatteryBarLayout = new LinearLayout(mContext);
+ addView(mBatteryBarLayout, new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+
+ mBatteryBar = new View(mContext);
+ mBatteryBarLayout.addView(mBatteryBar, new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ float dp = 4f;
+ int pixels = (int) (metrics.density * dp + 0.5f);
+
+ // Charger
+ mChargerLayout = new LinearLayout(mContext);
+
+ if (vertical) {
+ addView(mChargerLayout, new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ pixels));
+ } else {
+ addView(mChargerLayout, new RelativeLayout.LayoutParams(pixels,
+ LayoutParams.MATCH_PARENT));
+ }
+
+ mCharger = new View(mContext);
+ mChargerLayout.setVisibility(View.GONE);
+ mChargerLayout.addView(mCharger, new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ getContext().registerReceiver(mIntentReceiver, filter, null, getHandler());
+
+ mSettingsObserver = new SettingsObserver(new Handler());
+ mSettingsObserver.observe();
+ updateSettings();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mAttached) {
+ mAttached = false;
+ getContext().unregisterReceiver(mIntentReceiver);
+ getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
+ }
+ }
+
+ private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ mBatteryCharging = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 0) == BatteryManager.BATTERY_STATUS_CHARGING;
+ if (mBatteryCharging && mBatteryLevel < 100) {
+ start();
+ } else {
+ stop();
+ }
+ setProgress(mBatteryLevel);
+ } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ stop();
+ } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ if (mBatteryCharging && mBatteryLevel < 100) {
+ start();
+ }
+ }
+ }
+ };
+
+ private void updateSettings() {
+ ContentResolver resolver = getContext().getContentResolver();
+
+ int color = Settings.System.getInt(resolver, Settings.System.STATUSBAR_BATTERY_BAR_COLOR,
+ 0xFF33B5E5);
+
+ shouldAnimateCharging = Settings.System.getInt(resolver,
+ Settings.System.STATUSBAR_BATTERY_BAR_ANIMATE, 0) == 1;
+
+ if (mBatteryCharging && mBatteryLevel < 100 && shouldAnimateCharging) {
+ start();
+ } else {
+ stop();
+ }
+ setProgress(mBatteryLevel);
+ mBatteryBar.setBackgroundColor(color);
+ mCharger.setBackgroundColor(color);
+ }
+
+ private void setProgress(int n) {
+ if (vertical) {
+ int w = (int) (((getHeight() / 100.0) * n) + 0.5);
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mBatteryBarLayout
+ .getLayoutParams();
+ params.height = w;
+ mBatteryBarLayout.setLayoutParams(params);
+ } else {
+ int w = (int) (((getWidth() / 100.0) * n) + 0.5);
+ RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) mBatteryBarLayout
+ .getLayoutParams();
+ params.width = w;
+ mBatteryBarLayout.setLayoutParams(params);
+ }
+ }
+
+ @Override
+ public void start() {
+ if (!shouldAnimateCharging) {
+ return;
+ }
+
+ if (vertical) {
+ TranslateAnimation a = new TranslateAnimation(getX(), getX(), getHeight(),
+ mBatteryBarLayout.getHeight());
+ a.setInterpolator(new AccelerateInterpolator());
+ a.setDuration(ANIM_DURATION);
+ a.setRepeatCount(Animation.INFINITE);
+ mChargerLayout.startAnimation(a);
+ mChargerLayout.setVisibility(View.VISIBLE);
+ } else {
+ TranslateAnimation a = new TranslateAnimation(getWidth(), mBatteryBarLayout.getWidth(),
+ getTop(), getTop());
+ a.setInterpolator(new AccelerateInterpolator());
+ a.setDuration(ANIM_DURATION);
+ a.setRepeatCount(Animation.INFINITE);
+ mChargerLayout.startAnimation(a);
+ mChargerLayout.setVisibility(View.VISIBLE);
+ }
+ isAnimating = true;
+ }
+
+ @Override
+ public void stop() {
+ mChargerLayout.clearAnimation();
+ mChargerLayout.setVisibility(View.GONE);
+ isAnimating = false;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return isAnimating;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryBarController.java
new file mode 100644
index 000000000000..7186c75c60e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryBarController.java
@@ -0,0 +1,258 @@
+ /*
+ * Copyright (C) 2012 Slimroms & CyanogenMod
+ *
+ * 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 android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+public class BatteryBarController extends LinearLayout {
+
+ private static final String TAG = "BatteryBarController";
+
+ BatteryBar mainBar;
+ BatteryBar alternateStyleBar;
+ GlobalSettingsObserver mSettingsObserver;
+
+ public static final int STYLE_REGULAR = 0;
+ public static final int STYLE_SYMMETRIC = 1;
+
+ static int mStyle = STYLE_REGULAR;
+ static int mLocation = 0;
+
+ protected final static int CURRENT_LOC = 1;
+ int mLocationToLookFor = 0;
+
+ private int mBatteryLevel = 0;
+ private boolean mBatteryCharging = false;
+
+ boolean isAttached = false;
+ boolean isVertical = false;
+
+ static class GlobalSettingsObserver extends ContentObserver {
+ private static GlobalSettingsObserver sInstance;
+ private ArrayList mBatteryBarControllers = new ArrayList();
+ private Context mContext;
+
+ public GlobalSettingsObserver(Handler handler, Context context) {
+ super(handler);
+ mContext = context.getApplicationContext();
+ }
+
+ static GlobalSettingsObserver getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new GlobalSettingsObserver(new Handler(), context);
+ }
+ return sInstance;
+ }
+
+ void attach(BatteryBarController bbc) {
+ if (mBatteryBarControllers.isEmpty()) {
+ observe();
+ }
+ mBatteryBarControllers.add(bbc);
+ }
+
+ void detach(BatteryBarController bbc) {
+ mBatteryBarControllers.remove(bbc);
+ if (mBatteryBarControllers.isEmpty()) {
+ unobserve();
+ }
+ }
+
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.STATUSBAR_BATTERY_BAR), false, this);
+ resolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.STATUSBAR_BATTERY_BAR_STYLE), false,
+ this);
+ resolver.registerContentObserver(
+ Settings.System.getUriFor(Settings.System.STATUSBAR_BATTERY_BAR_THICKNESS),
+ false, this);
+ }
+
+ void unobserve() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ this.updateSettings();
+ }
+
+ void updateSettings() {
+ mStyle = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.STATUSBAR_BATTERY_BAR_STYLE, 0);
+ mLocation = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.STATUSBAR_BATTERY_BAR, 0);
+
+ for (BatteryBarController bbc : mBatteryBarControllers) {
+ if (bbc.mLocationToLookFor == mLocation) {
+ bbc.removeBars();
+ bbc.addBars();
+ bbc.setVisibility(View.VISIBLE);
+ } else {
+ bbc.removeBars();
+ bbc.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+
+ public BatteryBarController(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ if (attrs != null) {
+ String ns = "http://schemas.android.com/apk/res/com.android.systemui";
+ mLocationToLookFor = attrs.getAttributeIntValue(ns, "viewLocation", 0);
+ }
+ mSettingsObserver = GlobalSettingsObserver.getInstance(context);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (!isAttached) {
+ isVertical = (getLayoutParams().height == LayoutParams.MATCH_PARENT);
+
+ isAttached = true;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ getContext().registerReceiver(mIntentReceiver, filter);
+
+ mSettingsObserver.attach(this);
+ updateSettings();
+ }
+ }
+
+ private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
+ mBatteryCharging = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 0) == BatteryManager.BATTERY_STATUS_CHARGING;
+ Prefs.setLastBatteryLevel(context, mBatteryLevel);
+ }
+ }
+ };
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (isAttached) {
+ isAttached = false;
+ removeBars();
+ mSettingsObserver.detach(this);
+ }
+ super.onDetachedFromWindow();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (isAttached) {
+ getHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ updateSettings();
+ }
+ }, 500);
+ }
+ }
+
+ public void addBars() {
+ // Set heights
+ DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ float dp = (float) Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.STATUSBAR_BATTERY_BAR_THICKNESS, 1);
+ int pixels = (int) ((metrics.density * dp) + 0.5);
+ ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) getLayoutParams();
+
+ if (isVertical) {
+ params.width = pixels;
+ } else {
+ params.height = pixels;
+ }
+
+ setLayoutParams(params);
+ mBatteryLevel = Prefs.getLastBatteryLevel(getContext());
+
+ if (mStyle == STYLE_REGULAR) {
+ addView(new BatteryBar(mContext, mBatteryCharging, mBatteryLevel, isVertical),
+ new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT, 1));
+ } else if (mStyle == STYLE_SYMMETRIC) {
+ BatteryBar bar1 = new BatteryBar(mContext, mBatteryCharging, mBatteryLevel, isVertical);
+ BatteryBar bar2 = new BatteryBar(mContext, mBatteryCharging, mBatteryLevel, isVertical);
+
+ if (isVertical) {
+ bar2.setRotationY(180f);
+ addView(bar2, (new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT, 1)));
+ addView(bar1, (new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT, 1)));
+ } else {
+ bar1.setRotationY(180f);
+ addView(bar1, (new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT, 1)));
+ addView(bar2, (new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT, 1)));
+ }
+ }
+ }
+
+ public void removeBars() {
+ removeAllViews();
+ }
+
+ public void updateSettings() {
+ mStyle = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.STATUSBAR_BATTERY_BAR_STYLE, 0);
+ mLocation = Settings.System.getInt(getContext().getContentResolver(),
+ Settings.System.STATUSBAR_BATTERY_BAR, 0);
+
+ if (isLocationValid(mLocation)) {
+ removeBars();
+ addBars();
+ setVisibility(View.VISIBLE);
+ } else {
+ removeBars();
+ setVisibility(View.GONE);
+ }
+ }
+
+ protected boolean isLocationValid(int location) {
+ return mLocationToLookFor == location;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index f1fda784ad85..afb4a3ff1658 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -234,4 +234,4 @@ public View getChildAtPosition(MotionEvent ev) {
public View getChildContentView(View v) {
return mContentSlider;
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
index f339401dce18..573c40a6c022 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Prefs.java
@@ -22,6 +22,8 @@
public class Prefs {
private static final String SHARED_PREFS_NAME = "status_bar";
+ public static final String LAST_BATTERY_LEVEL = "last_battery_level";
+
public static SharedPreferences read(Context context) {
return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
}
@@ -29,4 +31,13 @@ public static SharedPreferences read(Context context) {
public static SharedPreferences.Editor edit(Context context) {
return context.getSharedPreferences(Prefs.SHARED_PREFS_NAME, Context.MODE_PRIVATE).edit();
}
+
+ public static void setLastBatteryLevel(Context context, int level) {
+ edit(context).putInt(LAST_BATTERY_LEVEL, level).commit();
+ }
+
+ public static int getLastBatteryLevel(Context context) {
+ return read(context).getInt(LAST_BATTERY_LEVEL, 50);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index eb6da20651b8..a5c2635ec99d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -150,6 +150,12 @@ protected boolean shouldDisableNavbarGestures() {
return true;
}
+ @Override
+ public boolean isExpandedVisible() {
+ return false;
+ }
+
+ @Override
public View getStatusBarView() {
return null;
}
diff --git a/services/input/InputWindow.cpp b/services/input/InputWindow.cpp
index fe61918e1d86..5910b6043249 100644
--- a/services/input/InputWindow.cpp
+++ b/services/input/InputWindow.cpp
@@ -36,7 +36,8 @@ bool InputWindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
bool InputWindowInfo::isTrustedOverlay() const {
return layoutParamsType == TYPE_INPUT_METHOD
|| layoutParamsType == TYPE_INPUT_METHOD_DIALOG
- || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
+ || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY
+ || layoutParamsType == TYPE_STATUS_BAR_PANEL;
}
bool InputWindowInfo::supportsSplitTouch() const {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 1ac54f7f472a..2728935f2f24 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -80,14 +80,17 @@
import com.android.internal.R;
import com.android.internal.notification.NotificationScorer;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.slim.QuietHoursHelper;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Array;
@@ -111,8 +114,12 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final String TAG = "NotificationService";
private static final boolean DBG = false;
+ private static final String SYSTEM_FOLDER = "/data/system";
+
private static final int MAX_PACKAGE_NOTIFICATIONS = 50;
+ private static final int DEFAULT_RESULT = 0;
+
// message codes
private static final int MESSAGE_TIMEOUT = 2;
@@ -200,8 +207,9 @@ public class NotificationManagerService extends INotificationManager.Stub
private HashSet mEnabledListenerPackageNames = new HashSet();
// Notification control database. For now just contains disabled packages.
- private AtomicFile mPolicyFile;
+ private AtomicFile mPolicyFile, mPeekPolicyFile, mFloatingModePolicyFile, mHoverPolicyFile;
private HashSet mBlockedPackages = new HashSet();
+ private HashSet mHoverBlacklist = new HashSet();
private static final int DB_VERSION = 1;
@@ -212,6 +220,9 @@ public class NotificationManagerService extends INotificationManager.Stub
private static final String TAG_PACKAGE = "package";
private static final String ATTR_NAME = "name";
+ private static final String NOTIFICATION_POLICY = "notification_policy.xml";
+ private static final String HOVER_POLICY = "hover_policy.xml";
+
private final ArrayList mScorers = new ArrayList();
private class NotificationListenerInfo implements DeathRecipient {
@@ -390,55 +401,143 @@ public StatusBarNotification[] getArray(int count, String pkg, int userId) {
Archive mArchive = new Archive();
+ private int readPolicy(AtomicFile file, String lookUpTag, HashSet db) {
+ int result = DEFAULT_RESULT;
+ FileInputStream infile = null;
+ try {
+ infile = file.openRead();
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(infile, null);
+
+ int type;
+ String tag;
+ int version = DB_VERSION;
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == START_TAG) {
+ if (TAG_BODY.equals(tag)) {
+ version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
+ } else if (lookUpTag.equals(tag)) {
+ while ((type = parser.next()) != END_DOCUMENT) {
+ tag = parser.getName();
+ if (TAG_PACKAGE.equals(tag)) {
+ db.add(parser.getAttributeValue(null, ATTR_NAME));
+ } else if (lookUpTag.equals(tag) && type == END_TAG) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ // Unable to read
+ } finally {
+ IoUtils.closeQuietly(infile);
+ }
+ return result;
+ }
+
private void loadBlockDb() {
synchronized(mBlockedPackages) {
if (mPolicyFile == null) {
- File dir = new File("/data/system");
- mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
-
+ mPolicyFile = new AtomicFile(new File(SYSTEM_FOLDER, NOTIFICATION_POLICY));
mBlockedPackages.clear();
+ readPolicy(mPolicyFile, TAG_BLOCKED_PKGS, mBlockedPackages);
+ }
+ }
+ }
- FileInputStream infile = null;
- try {
- infile = mPolicyFile.openRead();
- final XmlPullParser parser = Xml.newPullParser();
- parser.setInput(infile, null);
-
- int type;
- String tag;
- int version = DB_VERSION;
- while ((type = parser.next()) != END_DOCUMENT) {
- tag = parser.getName();
- if (type == START_TAG) {
- if (TAG_BODY.equals(tag)) {
- version = Integer.parseInt(parser.getAttributeValue(null, ATTR_VERSION));
- } else if (TAG_BLOCKED_PKGS.equals(tag)) {
- while ((type = parser.next()) != END_DOCUMENT) {
- tag = parser.getName();
- if (TAG_PACKAGE.equals(tag)) {
- mBlockedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
- } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
- break;
- }
- }
- }
+ private void loadHoverBlockDb() {
+ synchronized(mHoverBlacklist) {
+ if (mHoverPolicyFile == null) {
+ mHoverPolicyFile = new AtomicFile(new File(SYSTEM_FOLDER, HOVER_POLICY));
+ mHoverBlacklist.clear();
+ readPolicy(mHoverPolicyFile, TAG_BLOCKED_PKGS, mHoverBlacklist);
+ }
+ }
+ }
+
+ private void writeBlockDb() {
+ synchronized(mBlockedPackages) {
+ FileOutputStream outfile = null;
+ try {
+ outfile = mPolicyFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(outfile, "utf-8");
+
+ out.startDocument(null, true);
+
+ out.startTag(null, TAG_BODY); {
+ out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
+ out.startTag(null, TAG_BLOCKED_PKGS); {
+ // write all known network policies
+ for (String pkg : mBlockedPackages) {
+ out.startTag(null, TAG_PACKAGE); {
+ out.attribute(null, ATTR_NAME, pkg);
+ } out.endTag(null, TAG_PACKAGE);
}
- }
- } catch (FileNotFoundException e) {
- // No data yet
- } catch (IOException e) {
- Log.wtf(TAG, "Unable to read blocked notifications database", e);
- } catch (NumberFormatException e) {
- Log.wtf(TAG, "Unable to parse blocked notifications database", e);
- } catch (XmlPullParserException e) {
- Log.wtf(TAG, "Unable to parse blocked notifications database", e);
- } finally {
- IoUtils.closeQuietly(infile);
+ } out.endTag(null, TAG_BLOCKED_PKGS);
+ } out.endTag(null, TAG_BODY);
+
+ out.endDocument();
+
+ mPolicyFile.finishWrite(outfile);
+ } catch (IOException e) {
+ if (outfile != null) {
+ mPolicyFile.failWrite(outfile);
+ }
+ }
+ }
+ }
+
+ private void writeHoverBlockDb() {
+ synchronized(mHoverBlacklist) {
+ FileOutputStream outfile = null;
+ try {
+ outfile = mHoverPolicyFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(outfile, "utf-8");
+
+ out.startDocument(null, true);
+
+ out.startTag(null, TAG_BODY); {
+ out.attribute(null, ATTR_VERSION, String.valueOf(DB_VERSION));
+ out.startTag(null, TAG_BLOCKED_PKGS); {
+ // write all known network policies
+ for (String pkg : mHoverBlacklist) {
+ out.startTag(null, TAG_PACKAGE); {
+ out.attribute(null, ATTR_NAME, pkg);
+ } out.endTag(null, TAG_PACKAGE);
+ }
+ } out.endTag(null, TAG_BLOCKED_PKGS);
+ } out.endTag(null, TAG_BODY);
+
+ out.endDocument();
+
+ mHoverPolicyFile.finishWrite(outfile);
+ } catch (IOException e) {
+ if (outfile != null) {
+ mHoverPolicyFile.failWrite(outfile);
}
}
}
}
+ public void setHoverBlacklistStatus(String pkg, boolean status) {
+ if (status) {
+ mHoverBlacklist.add(pkg);
+ } else {
+ mHoverBlacklist.remove(pkg);
+ }
+ writeHoverBlockDb();
+ }
+
+ public boolean isPackageAllowedForHover(String pkg) {
+ return !mHoverBlacklist.contains(pkg);
+ }
+
/**
* Use this when you just want to know if notifications are OK for this package.
*/
@@ -473,6 +572,8 @@ public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabl
if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
}
+ writeBlockDb();
+ writeHoverBlockDb();
}
@@ -1476,6 +1577,7 @@ static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
*/
private void importOldBlockDb() {
loadBlockDb();
+ loadHoverBlockDb();
PackageManager pm = mContext.getPackageManager();
for (String pkg : mBlockedPackages) {