blob: d11aba67854c95d155782d8037bbea8a4dc5187c [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.tablet;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import android.app.ActivityManagerNative;
import android.app.PendingIntent;
import android.app.Notification;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Slog;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.ScrollView;
import android.widget.TextSwitcher;
import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.statusbar.*;
import com.android.systemui.recent.RecentApplicationsActivity;
import com.android.systemui.R;
public class TabletStatusBarService extends StatusBarService {
public static final boolean DEBUG = false;
public static final String TAG = "TabletStatusBarService";
public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000;
public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001;
public static final int MSG_OPEN_SYSTEM_PANEL = 1010;
public static final int MSG_CLOSE_SYSTEM_PANEL = 1011;
private static final int MAX_IMAGE_LEVEL = 10000;
int mIconSize;
H mHandler = new H();
// tracking all current notifications
private NotificationData mNotns = new NotificationData();
TabletStatusBarView mStatusBarView;
ImageView mNotificationTrigger;
NotificationIconArea mNotificationIconArea;
View mNotificationButtons;
View mSystemInfo;
View mNavigationArea;
View mMenuButton;
View mRecentButton;
NotificationPanel mNotificationPanel;
SystemPanel mSystemPanel;
ViewGroup mPile;
TextView mClearButton;
TextView mDoNotDisturbButton;
ImageView mBatteryMeter;
ImageView mSignalMeter;
ImageView mSignalIcon;
View mBarContents;
View mCurtains;
NotificationIconArea.IconLayout mIconLayout;
TabletTicker mTicker;
View mTickerView;
boolean mTicking;
// for disabling the status bar
int mDisabled = 0;
boolean mNotificationsOn = true;
protected void addPanelWindows() {
final Context context = mContext;
final Resources res = context.getResources();
final int barHeight= res.getDimensionPixelSize(
com.android.internal.R.dimen.status_bar_height);
mNotificationPanel = (NotificationPanel)View.inflate(context,
R.layout.sysbar_panel_notifications, null);
mNotificationPanel.setVisibility(View.GONE);
mNotificationPanel.setOnTouchListener(
new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel));
mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
400, // ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
PixelFormat.TRANSLUCENT);
lp.gravity = Gravity.BOTTOM | Gravity.RIGHT;
lp.setTitle("NotificationPanel");
lp.windowAnimations = com.android.internal.R.style.Animation_SlidingCard;
WindowManagerImpl.getDefault().addView(mNotificationPanel, lp);
mSystemPanel = (SystemPanel) View.inflate(context, R.layout.sysbar_panel_system, null);
mSystemPanel.setVisibility(View.GONE);
mSystemPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_SYSTEM_PANEL,
mSystemPanel));
mStatusBarView.setIgnoreChildren(1, mSystemInfo, mSystemPanel);
lp = new WindowManager.LayoutParams(
800,
ViewGroup.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
PixelFormat.TRANSLUCENT);
lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
lp.setTitle("SystemPanel");
lp.windowAnimations = com.android.internal.R.style.Animation_SlidingCard;
WindowManagerImpl.getDefault().addView(mSystemPanel, lp);
mSystemPanel.setBar(this);
}
@Override
public void start() {
super.start(); // will add the main bar view
}
protected View makeStatusBarView() {
final Context context = mContext;
final Resources res = context.getResources();
mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
final TabletStatusBarView sb = (TabletStatusBarView)View.inflate(
context, R.layout.status_bar, null);
mStatusBarView = sb;
sb.setHandler(mHandler);
mBarContents = sb.findViewById(R.id.bar_contents);
mCurtains = sb.findViewById(R.id.lights_out);
mSystemInfo = sb.findViewById(R.id.systemInfo);
mSystemInfo.setOnClickListener(mOnClickListener);
mSystemInfo.setOnLongClickListener(new SetLightsOnListener(false));
mRecentButton = sb.findViewById(R.id.recent);
mRecentButton.setOnClickListener(mOnClickListener);
SetLightsOnListener on = new SetLightsOnListener(true);
mCurtains.setOnClickListener(on);
mCurtains.setOnLongClickListener(on);
// the button to open the notification area
mNotificationTrigger = (ImageView) sb.findViewById(R.id.notificationTrigger);
mNotificationTrigger.setOnClickListener(mOnClickListener);
// the more notifications icon
mNotificationIconArea = (NotificationIconArea)sb.findViewById(R.id.notificationIcons);
// the clear and dnd buttons
mNotificationButtons = sb.findViewById(R.id.notificationButtons);
mClearButton = (TextView)mNotificationButtons.findViewById(R.id.clear_all_button);
mClearButton.setOnClickListener(mOnClickListener);
mDoNotDisturbButton = (TextView)mNotificationButtons.findViewById(R.id.do_not_disturb);
mDoNotDisturbButton.setOnClickListener(mOnClickListener);
// where the icons go
mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
mTicker = new TabletTicker(context, (FrameLayout)sb.findViewById(R.id.ticker));
// System info (center)
mBatteryMeter = (ImageView) sb.findViewById(R.id.battery);
mSignalMeter = (ImageView) sb.findViewById(R.id.signal);
mSignalIcon = (ImageView) sb.findViewById(R.id.signal_icon);
// The navigation buttons
mNavigationArea = sb.findViewById(R.id.navigationArea);
mMenuButton = mNavigationArea.findViewById(R.id.menu);
// set the initial view visibility
setAreThereNotifications();
refreshNotificationTrigger();
// Add the windows
addPanelWindows();
mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
mPile.removeAllViews();
ScrollView scroller = (ScrollView)mPile.getParent();
scroller.setFillViewport(true);
return sb;
}
protected int getStatusBarGravity() {
return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL;
}
private class H extends Handler {
public void handleMessage(Message m) {
switch (m.what) {
case MSG_OPEN_NOTIFICATION_PANEL:
if (DEBUG) Slog.d(TAG, "opening notifications panel");
if (mNotificationPanel.getVisibility() == View.GONE) {
mDoNotDisturbButton.setText(mNotificationsOn
? R.string.status_bar_do_not_disturb_button
: R.string.status_bar_please_disturb_button);
mNotificationPanel.setVisibility(View.VISIBLE);
setViewVisibility(mNotificationIconArea, View.GONE,
R.anim.notification_icons_out);
setViewVisibility(mNotificationButtons, View.VISIBLE,
R.anim.notification_buttons_in);
refreshNotificationTrigger();
}
break;
case MSG_CLOSE_NOTIFICATION_PANEL:
if (DEBUG) Slog.d(TAG, "closing notifications panel");
if (mNotificationPanel.getVisibility() == View.VISIBLE) {
mNotificationPanel.setVisibility(View.GONE);
setViewVisibility(mNotificationIconArea, View.VISIBLE,
R.anim.notification_icons_in);
setViewVisibility(mNotificationButtons, View.GONE,
R.anim.notification_buttons_out);
refreshNotificationTrigger();
}
break;
case MSG_OPEN_SYSTEM_PANEL:
if (DEBUG) Slog.d(TAG, "opening system panel");
mSystemPanel.setVisibility(View.VISIBLE);
break;
case MSG_CLOSE_SYSTEM_PANEL:
if (DEBUG) Slog.d(TAG, "closing system panel");
mSystemPanel.setVisibility(View.GONE);
break;
}
}
}
public void refreshNotificationTrigger() {
if (mNotificationTrigger == null) return;
int resId;
boolean panel = (mNotificationPanel != null
&& mNotificationPanel.getVisibility() == View.VISIBLE);
if (!mNotificationsOn) {
resId = R.drawable.ic_sysbar_noti_dnd;
} else if (mNotns.size() > 0) {
resId = panel ? R.drawable.ic_sysbar_noti_avail_open : R.drawable.ic_sysbar_noti_avail;
} else {
resId = panel ? R.drawable.ic_sysbar_noti_none_open : R.drawable.ic_sysbar_noti_none;
}
mNotificationTrigger.setImageResource(resId);
}
public void setBatteryMeter(int level, boolean plugged) {
if (DEBUG) Slog.d(TAG, "battery=" + level + (plugged ? " - plugged" : " - unplugged"));
mBatteryMeter.setImageResource(R.drawable.sysbar_batterymini);
// adjust percent to permyriad for ClipDrawable's sake
mBatteryMeter.setImageLevel(level * (MAX_IMAGE_LEVEL / 100));
}
public void setSignalMeter(int level, boolean isWifi) {
if (DEBUG) Slog.d(TAG, "signal=" + level);
if (level < 0) {
mSignalMeter.setImageDrawable(null);
mSignalMeter.setImageLevel(0);
mSignalIcon.setImageDrawable(null);
} else {
mSignalMeter.setImageResource(R.drawable.sysbar_wifimini);
// adjust to permyriad
mSignalMeter.setImageLevel(level * (MAX_IMAGE_LEVEL / 100));
mSignalIcon.setImageResource(isWifi ? R.drawable.ic_sysbar_wifi_mini
: R.drawable.ic_sysbar_wifi_mini); // XXX
}
}
public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
if (DEBUG) Slog.d(TAG, "addIcon(" + slot + ") -> " + icon);
}
public void updateIcon(String slot, int index, int viewIndex,
StatusBarIcon old, StatusBarIcon icon) {
if (DEBUG) Slog.d(TAG, "updateIcon(" + slot + ") -> " + icon);
}
public void removeIcon(String slot, int index, int viewIndex) {
if (DEBUG) Slog.d(TAG, "removeIcon(" + slot + ")");
}
public void addNotification(IBinder key, StatusBarNotification notification) {
if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
addNotificationViews(key, notification);
boolean immersive = false;
try {
immersive = ActivityManagerNative.getDefault().isTopActivityImmersive();
Slog.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive"));
} catch (RemoteException ex) {
}
if (immersive) {
// TODO: immersive mode popups for tablet
} else if (notification.notification.fullScreenIntent != null) {
// not immersive & a full-screen alert should be shown
Slog.d(TAG, "Notification has fullScreenIntent and activity is not immersive;"
+ " sending fullScreenIntent");
try {
notification.notification.fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
} else {
tick(notification);
}
setAreThereNotifications();
}
public void updateNotification(IBinder key, StatusBarNotification notification) {
if (DEBUG) Slog.d(TAG, "updateNotification(" + key + " -> " + notification + ") // TODO");
final NotificationData.Entry oldEntry = mNotns.findByKey(key);
if (oldEntry == null) {
Slog.w(TAG, "updateNotification for unknown key: " + key);
return;
}
final StatusBarNotification oldNotification = oldEntry.notification;
final RemoteViews oldContentView = oldNotification.notification.contentView;
final RemoteViews contentView = notification.notification.contentView;
if (false) {
Slog.d(TAG, "old notification: when=" + oldNotification.notification.when
+ " ongoing=" + oldNotification.isOngoing()
+ " expanded=" + oldEntry.expanded
+ " contentView=" + oldContentView);
Slog.d(TAG, "new notification: when=" + notification.notification.when
+ " ongoing=" + oldNotification.isOngoing()
+ " contentView=" + contentView);
}
// Can we just reapply the RemoteViews in place? If when didn't change, the order
// didn't change.
if (notification.notification.when == oldNotification.notification.when
&& notification.isOngoing() == oldNotification.isOngoing()
&& oldEntry.expanded != null
&& contentView != null
&& oldContentView != null
&& contentView.getPackage() != null
&& oldContentView.getPackage() != null
&& oldContentView.getPackage().equals(contentView.getPackage())
&& oldContentView.getLayoutId() == contentView.getLayoutId()) {
if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
oldEntry.notification = notification;
try {
// Reapply the RemoteViews
contentView.reapply(mContext, oldEntry.content);
// update the contentIntent
final PendingIntent contentIntent = notification.notification.contentIntent;
if (contentIntent != null) {
oldEntry.content.setOnClickListener(new NotificationClicker(contentIntent,
notification.pkg, notification.tag, notification.id));
} else {
oldEntry.content.setOnClickListener(null);
}
// Update the icon.
final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
notification.notification.icon, notification.notification.iconLevel,
notification.notification.number);
if (!oldEntry.icon.set(ic)) {
handleNotificationError(key, notification, "Couldn't update icon: " + ic);
return;
}
}
catch (RuntimeException e) {
// It failed to add cleanly. Log, and remove the view from the panel.
Slog.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e);
removeNotificationViews(key);
addNotificationViews(key, notification);
}
} else {
if (DEBUG) Slog.d(TAG, "not reusing notification for key: " + key);
removeNotificationViews(key);
addNotificationViews(key, notification);
}
// TODO: ticker; immersive mode
setAreThereNotifications();
}
public void removeNotification(IBinder key) {
if (DEBUG) Slog.d(TAG, "removeNotification(" + key + ") // TODO");
removeNotificationViews(key);
setAreThereNotifications();
}
public void disable(int state) {
int old = mDisabled;
int diff = state ^ old;
Slog.d(TAG, "disable... old=0x" + Integer.toHexString(old)
+ " diff=0x" + Integer.toHexString(diff)
+ " state=0x" + Integer.toHexString(state));
mDisabled = state;
// act accordingly
if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
Slog.d(TAG, "DISABLE_EXPAND: yes");
animateCollapse();
}
}
if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
setViewVisibility(mNotificationTrigger, View.GONE,
R.anim.notification_icons_out);
setViewVisibility(mNotificationIconArea, View.GONE,
R.anim.notification_icons_out);
mTicker.halt();
} else {
Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
setViewVisibility(mNotificationTrigger, View.VISIBLE,
R.anim.notification_icons_in);
setViewVisibility(mNotificationIconArea, View.VISIBLE,
R.anim.notification_icons_in);
}
} else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
mTicker.halt();
}
}
if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
Slog.d(TAG, "DISABLE_SYSTEM_INFO: yes");
setViewVisibility(mSystemInfo, View.GONE, R.anim.navigation_out);
} else {
Slog.d(TAG, "DISABLE_SYSTEM_INFO: no");
setViewVisibility(mSystemInfo, View.VISIBLE, R.anim.navigation_in);
}
}
if ((diff & StatusBarManager.DISABLE_NAVIGATION) != 0) {
if ((state & StatusBarManager.DISABLE_NAVIGATION) != 0) {
Slog.d(TAG, "DISABLE_NAVIGATION: yes");
setViewVisibility(mNavigationArea, View.GONE, R.anim.navigation_out);
} else {
Slog.d(TAG, "DISABLE_NAVIGATION: no");
setViewVisibility(mNavigationArea, View.VISIBLE, R.anim.navigation_in);
}
}
}
private boolean hasTicker(Notification n) {
return !TextUtils.isEmpty(n.tickerText)
|| !TextUtils.isEmpty(n.tickerTitle)
|| !TextUtils.isEmpty(n.tickerSubtitle);
}
private void tick(StatusBarNotification n) {
// Don't show the ticker when the windowshade is open.
if (mNotificationPanel.getVisibility() == View.VISIBLE) {
return;
}
// Show the ticker if one is requested. Also don't do this
// until status bar window is attached to the window manager,
// because... well, what's the point otherwise? And trying to
// run a ticker without being attached will crash!
if (hasTicker(n.notification) && mStatusBarView.getWindowToken() != null) {
if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
| StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
mTicker.add(n);
}
}
}
public void animateExpand() {
mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL);
mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL);
}
public void animateCollapse() {
mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL);
mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL);
mHandler.removeMessages(MSG_CLOSE_SYSTEM_PANEL);
mHandler.sendEmptyMessage(MSG_CLOSE_SYSTEM_PANEL);
}
public void setLightsOn(boolean on) {
if (on) {
setViewVisibility(mCurtains, View.GONE, R.anim.lights_out_out);
setViewVisibility(mBarContents, View.VISIBLE, R.anim.status_bar_in);
} else {
animateCollapse();
setViewVisibility(mCurtains, View.VISIBLE, R.anim.lights_out_in);
setViewVisibility(mBarContents, View.GONE, R.anim.status_bar_out);
}
}
public void setMenuKeyVisible(boolean visible) {
if (DEBUG) {
Slog.d(TAG, (visible?"showing":"hiding") + " the MENU button");
}
setViewVisibility(mMenuButton,
visible ? View.VISIBLE : View.INVISIBLE,
visible ? R.anim.navigation_in : R.anim.navigation_out);
}
private void setAreThereNotifications() {
final boolean hasClearable = mNotns.hasClearableItems();
//Slog.d(TAG, "setAreThereNotifications hasClerable=" + hasClearable);
// Show or hide the "Clear all" button. Note that we don't do an animation
// if it's not on screen, so that if someone opens the bar right then they
// don't see the animation in progress.
// (no ongoing notifications are clearable)
if (hasClearable) {
if (mNotificationButtons.getVisibility() == View.VISIBLE) {
setViewVisibility(mClearButton, View.VISIBLE, R.anim.notification_buttons_in);
} else {
mClearButton.setVisibility(View.VISIBLE);
}
} else {
if (mNotificationButtons.getVisibility() == View.VISIBLE) {
setViewVisibility(mClearButton, View.GONE, R.anim.notification_buttons_out);
} else {
mClearButton.setVisibility(View.GONE);
}
}
/*
mOngoingTitle.setVisibility(ongoing ? View.VISIBLE : View.GONE);
mLatestTitle.setVisibility(latest ? View.VISIBLE : View.GONE);
if (ongoing || latest) {
mNoNotificationsTitle.setVisibility(View.GONE);
} else {
mNoNotificationsTitle.setVisibility(View.VISIBLE);
}
*/
}
/**
* Cancel this notification and tell the status bar service about the failure. Hold no locks.
*/
void handleNotificationError(IBinder key, StatusBarNotification n, String message) {
removeNotification(key);
try {
mBarService.onNotificationError(n.pkg, n.tag, n.id, n.uid, n.initialPid, message);
} catch (RemoteException ex) {
// The end is nigh.
}
}
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
public void onClick(View v) {
if (v == mClearButton) {
onClickClearButton();
} else if (v == mDoNotDisturbButton) {
onClickDoNotDisturb();
} else if (v == mNotificationTrigger) {
onClickNotificationTrigger();
} else if (v == mSystemInfo) {
onClickSystemInfo();
} else if (v == mRecentButton) {
onClickRecentButton();
}
}
};
void onClickClearButton() {
try {
mBarService.onClearAllNotifications();
} catch (RemoteException ex) {
// system process is dead if we're here.
}
animateCollapse();
refreshNotificationTrigger();
}
void onClickDoNotDisturb() {
mNotificationsOn = !mNotificationsOn;
mIconLayout.setVisibility(mNotificationsOn ? View.VISIBLE : View.INVISIBLE); // TODO: animation
animateCollapse();
refreshNotificationTrigger();
}
public void onClickNotificationTrigger() {
if (DEBUG) Slog.d(TAG, "clicked notification icons");
if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
if (!mNotificationsOn) {
mNotificationsOn = true;
mIconLayout.setVisibility(View.VISIBLE); // TODO: animation
refreshNotificationTrigger();
} else {
int msg = (mNotificationPanel.getVisibility() == View.GONE)
? MSG_OPEN_NOTIFICATION_PANEL
: MSG_CLOSE_NOTIFICATION_PANEL;
mHandler.removeMessages(msg);
mHandler.sendEmptyMessage(msg);
}
}
}
public void onClickSystemInfo() {
if (DEBUG) Slog.d(TAG, "clicked system info");
if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) {
int msg = (mSystemPanel.getVisibility() == View.GONE)
? MSG_OPEN_SYSTEM_PANEL
: MSG_CLOSE_SYSTEM_PANEL;
mHandler.removeMessages(msg);
mHandler.sendEmptyMessage(msg);
}
}
public void onClickRecentButton() {
if (DEBUG) Slog.d(TAG, "clicked recent apps");
Intent intent = new Intent();
intent.setClass(mContext, RecentApplicationsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
mContext.startActivity(intent);
}
private class NotificationClicker implements View.OnClickListener {
private PendingIntent mIntent;
private String mPkg;
private String mTag;
private int mId;
NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
mIntent = intent;
mPkg = pkg;
mTag = tag;
mId = id;
}
public void onClick(View v) {
try {
// The intent we are sending is for the application, which
// won't have permission to immediately start an activity after
// the user switches to home. We know it is safe to do at this
// point, so make sure new activity switches are now allowed.
ActivityManagerNative.getDefault().resumeAppSwitches();
} catch (RemoteException e) {
}
if (mIntent != null) {
int[] pos = new int[2];
v.getLocationOnScreen(pos);
Intent overlay = new Intent();
overlay.setSourceBounds(
new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
try {
mIntent.send(mContext, 0, overlay);
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here. Just log the exception message.
Slog.w(TAG, "Sending contentIntent failed: " + e);
}
}
try {
mBarService.onNotificationClick(mPkg, mTag, mId);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
// close the shade if it was open
animateCollapse();
// If this click was on the intruder alert, hide that instead
// mHandler.sendEmptyMessage(MSG_HIDE_INTRUDER);
}
}
StatusBarNotification removeNotificationViews(IBinder key) {
NotificationData.Entry entry = mNotns.remove(key);
if (entry == null) {
Slog.w(TAG, "removeNotification for unknown key: " + key);
return null;
}
// Remove the expanded view.
ViewGroup rowParent = (ViewGroup)entry.row.getParent();
if (rowParent != null) rowParent.removeView(entry.row);
// Remove the icon.
// ViewGroup iconParent = (ViewGroup)entry.icon.getParent();
// if (iconParent != null) iconParent.removeView(entry.icon);
refreshIcons();
return entry.notification;
}
StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
if (DEBUG) {
Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
}
// Construct the icon.
final StatusBarIconView iconView = new StatusBarIconView(mContext,
notification.pkg + "/0x" + Integer.toHexString(notification.id));
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
notification.notification.icon,
notification.notification.iconLevel,
notification.notification.number);
if (!iconView.set(ic)) {
handleNotificationError(key, notification, "Couldn't attach StatusBarIcon: " + ic);
return null;
}
// Construct the expanded view.
NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
if (!inflateViews(entry, mPile)) {
handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
+ notification);
return null;
}
// Add the icon.
mNotns.add(entry);
refreshIcons();
return iconView;
}
private void refreshIcons() {
// XXX: need to implement a new limited linear layout class
// to avoid removing & readding everything
int N = mNotns.size();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize, mIconSize);
if (DEBUG) {
Slog.d(TAG, "refreshing icons (" + N + " notifications, mIconLayout="
+ mIconLayout + ", mPile=" + mPile);
}
mIconLayout.removeAllViews();
for (int i=0; i<4; i++) {
if (i>=N) break;
mIconLayout.addView(mNotns.get(N-i-1).icon, i, params);
}
mPile.removeAllViews();
for (int i=0; i<N; i++) {
mPile.addView(mNotns.get(N-i-1).row);
}
refreshNotificationTrigger();
}
private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
StatusBarNotification sbn = entry.notification;
RemoteViews remoteViews = sbn.notification.contentView;
if (remoteViews == null) {
return false;
}
// create the row view
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View row = inflater.inflate(R.layout.status_bar_latest_event, parent, false);
View vetoButton = row.findViewById(R.id.veto);
if (entry.notification.isClearable()) {
final String _pkg = sbn.pkg;
final String _tag = sbn.tag;
final int _id = sbn.id;
vetoButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
try {
mBarService.onNotificationClear(_pkg, _tag, _id);
} catch (RemoteException ex) {
// system process is dead if we're here.
}
// animateCollapse();
}
});
} else {
vetoButton.setVisibility(View.INVISIBLE);
}
// bind the click event to the content area
ViewGroup content = (ViewGroup)row.findViewById(R.id.content);
// XXX: update to allow controls within notification views
content.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
// content.setOnFocusChangeListener(mFocusChangeListener);
PendingIntent contentIntent = sbn.notification.contentIntent;
if (contentIntent != null) {
content.setOnClickListener(new NotificationClicker(contentIntent,
sbn.pkg, sbn.tag, sbn.id));
} else {
content.setOnClickListener(null);
}
View expanded = null;
Exception exception = null;
try {
expanded = remoteViews.apply(mContext, content);
}
catch (RuntimeException e) {
exception = e;
}
if (expanded == null) {
String ident = sbn.pkg + "/0x" + Integer.toHexString(sbn.id);
Slog.e(TAG, "couldn't inflate view for notification " + ident, exception);
return false;
} else {
content.addView(expanded);
row.setDrawingCacheEnabled(true);
}
entry.row = row;
entry.content = content;
entry.expanded = expanded;
return true;
}
public class SetLightsOnListener implements View.OnLongClickListener,
View.OnClickListener {
private boolean mOn;
SetLightsOnListener(boolean on) {
mOn = on;
}
public void onClick(View v) {
try {
mBarService.setLightsOn(mOn);
} catch (RemoteException ex) {
// system process
}
}
public boolean onLongClick(View v) {
try {
mBarService.setLightsOn(mOn);
} catch (RemoteException ex) {
// system process
}
return true;
}
}
public class TouchOutsideListener implements View.OnTouchListener {
private int mMsg;
private StatusBarPanel mPanel;
public TouchOutsideListener(int msg, StatusBarPanel panel) {
mMsg = msg;
mPanel = panel;
}
public boolean onTouch(View v, MotionEvent ev) {
final int action = ev.getAction();
if (action == MotionEvent.ACTION_OUTSIDE
|| (action == MotionEvent.ACTION_DOWN
&& !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) {
mHandler.removeMessages(mMsg);
mHandler.sendEmptyMessage(mMsg);
return true;
}
return false;
}
}
private void setViewVisibility(View v, int vis, int anim) {
if (v.getVisibility() != vis) {
//Slog.d(TAG, "setViewVisibility vis=" + (vis == View.VISIBLE) + " v=" + v);
v.setAnimation(AnimationUtils.loadAnimation(mContext, anim));
v.setVisibility(vis);
}
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("mDisabled=0x");
pw.println(Integer.toHexString(mDisabled));
}
}